Bläddra i källkod

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

Beta 8.992
Marius Stanciu 5 år sedan
förälder
incheckning
bea3fbe488
76 ändrade filer med 35230 tillägg och 13321 borttagningar
  1. 48 31
      CHANGELOG.md
  2. 154 358
      FlatCAMApp.py
  3. 381 0
      FlatCAMBookmark.py
  4. 9 2757
      FlatCAMCommon.py
  5. 2399 0
      FlatCAMDB.py
  6. 0 8244
      FlatCAMObj.py
  7. 4 2
      FlatCAMPostProc.py
  8. 1 0
      FlatCAMTranslation.py
  9. 42 30
      ObjectCollection.py
  10. 231 204
      camlib.py
  11. 38 36
      flatcamEditors/FlatCAMExcEditor.py
  12. 7 7
      flatcamEditors/FlatCAMGeoEditor.py
  13. 34 34
      flatcamEditors/FlatCAMGrbEditor.py
  14. 2 2
      flatcamGUI/FlatCAMGUI.py
  15. 2 1
      flatcamGUI/ObjectUI.py
  16. 6 8
      flatcamGUI/PlotCanvasLegacy.py
  17. 1 1
      flatcamGUI/PreferencesUI.py
  18. 1220 0
      flatcamObjects/FlatCAMCNCJob.py
  19. 314 0
      flatcamObjects/FlatCAMDocument.py
  20. 1622 0
      flatcamObjects/FlatCAMExcellon.py
  21. 2622 0
      flatcamObjects/FlatCAMGeometry.py
  22. 1870 0
      flatcamObjects/FlatCAMGerber.py
  23. 505 0
      flatcamObjects/FlatCAMObj.py
  24. 231 0
      flatcamObjects/FlatCAMScript.py
  25. 0 0
      flatcamObjects/__init__.py
  26. 62 63
      flatcamParsers/ParseExcellon.py
  27. 24 25
      flatcamParsers/ParseGerber.py
  28. 3 3
      flatcamParsers/ParseHPGL2.py
  29. 3 3
      flatcamTools/ToolAlignObjects.py
  30. 7 8
      flatcamTools/ToolCopperThieving.py
  31. 44 39
      flatcamTools/ToolCutOut.py
  32. 4 5
      flatcamTools/ToolDblSided.py
  33. 6 7
      flatcamTools/ToolMove.py
  34. 30 30
      flatcamTools/ToolNCC.py
  35. 9 10
      flatcamTools/ToolOptimal.py
  36. 8 10
      flatcamTools/ToolPDF.py
  37. 23 23
      flatcamTools/ToolPaint.py
  38. 18 20
      flatcamTools/ToolPanelize.py
  39. 4 4
      flatcamTools/ToolPunchGerber.py
  40. 184 45
      flatcamTools/ToolShell.py
  41. 7 6
      flatcamTools/ToolSolderPaste.py
  42. 14 19
      flatcamTools/ToolTransform.py
  43. BIN
      locale/de/LC_MESSAGES/strings.mo
  44. 327 267
      locale/de/LC_MESSAGES/strings.po
  45. BIN
      locale/en/LC_MESSAGES/strings.mo
  46. 230 233
      locale/en/LC_MESSAGES/strings.po
  47. BIN
      locale/es/LC_MESSAGES/strings.mo
  48. 230 233
      locale/es/LC_MESSAGES/strings.po
  49. BIN
      locale/fr/LC_MESSAGES/strings.mo
  50. 229 232
      locale/fr/LC_MESSAGES/strings.po
  51. BIN
      locale/hu/LC_MESSAGES/strings.mo
  52. 21717 0
      locale/hu/LC_MESSAGES/strings.po
  53. 3 3
      locale/it/LC_MESSAGES/strings.po
  54. 7 7
      locale/pt_BR/LC_MESSAGES/strings.po
  55. BIN
      locale/ro/LC_MESSAGES/strings.mo
  56. 229 232
      locale/ro/LC_MESSAGES/strings.po
  57. 10 10
      locale/ru/LC_MESSAGES/strings.po
  58. 3 3
      locale_template/strings.pot
  59. 1 1
      requirements.txt
  60. 7 7
      tclCommands/TclCommand.py
  61. 1 4
      tclCommands/TclCommandAlignDrill.py
  62. 2 3
      tclCommands/TclCommandBbox.py
  63. 2 3
      tclCommands/TclCommandCncjob.py
  64. 2 3
      tclCommands/TclCommandDrillcncjob.py
  65. 2 3
      tclCommands/TclCommandFollow.py
  66. 2 3
      tclCommands/TclCommandGeoCutout.py
  67. 12 9
      tclCommands/TclCommandHelp.py
  68. 2 3
      tclCommands/TclCommandIsolate.py
  69. 2 2
      tclCommands/TclCommandJoinExcellon.py
  70. 2 2
      tclCommands/TclCommandJoinGeometry.py
  71. 1 2
      tclCommands/TclCommandMillDrills.py
  72. 1 2
      tclCommands/TclCommandMillSlots.py
  73. 1 4
      tclCommands/TclCommandMirror.py
  74. 3 4
      tclCommands/TclCommandNregions.py
  75. 2 3
      tclCommands/TclCommandOpenGerber.py
  76. 7 8
      tclCommands/TclCommandPanelize.py

+ 48 - 31
CHANGELOG.md

@@ -7,11 +7,28 @@ CHANGELOG for FlatCAM beta
 
 =================================================
 
+27.04.2020
+
+- finished the moving of all Tcl Shell stuff out of the FlatCAAMApp class to flatcamTools.ToolShell class
+- updated the requirements.txt file to request that the Shapely package needs to be at least version 1.7.0 as it is needed in the latest versions of FlatCAM beta
+- some TOOD cleanups
+- minor changes
+- replaced the testing if instance of FlatCAMObj with testing the obj.kind attribute
+- removed the import of the whole FlatCAMApp file only for the usage of GracefulException
+- remove the import of FlatCAMApp and used alternate ways
+- optimized the imports in some files
+- moved the Bookmarksmanager and ToolDB classes into their own files
+- solved some bugs that were not so visible in the Editors and HPGL parser
+- split the FlatCAMObj file into multiple files located in the flatcamObjects folder and renamed the contained classes with names more suggestive
+- updated the Google Translation for the German language
+- added support for Hungarian language - no translation for now
+
 25.04.2020
 
 - ensured that on Graceful Exit (CTRL+ALT+X key combo) if using Progressive Plotting, the eventual residual plotted lines are deleted. This apply for Tool NCC and Tool Paint
 - fixed links in Attributions tab in Help -> About FlatCAM to be able to open external links.
 - updated Google Translations for French and Spanish languages
+- added some '\n' chars in the Help Tcl command to make the help more readable
 
 24.04.2020
 
@@ -282,7 +299,7 @@ CHANGELOG for FlatCAM beta
 
 12.02.2020
 
-- working on fixing a bug in FlatCAMGeometry.merge() - FIXED issue #380
+- working on fixing a bug in GeometryObject.merge() - FIXED issue #380
 - fixed bug: when deleting a FlatCAMCNCJob with annotations enabled, the annotations are not deleted from canvas; fixed issue #379
 - fixed bug: creating a new project while a project is open and it contain CNCJob annotations and/or Gerber mark shapes, did not delete them from canvas
 
@@ -637,7 +654,7 @@ CHANGELOG for FlatCAM beta
 - modified the Jump To method such that now allows relative jump from the current mouse location
 - fixed the Defaults upgrade overwriting the new version number with the old one
 - fixed issue with clear_polygon3() - the one who makes 'lines' and fixed the NCC Tool
-- some small changes in the FlatCAMGeometry.on_tool_add() method
+- some small changes in the GeometryObject.on_tool_add() method
 - made sure that in Geometry Editor the self.app.mouse attribute is updated with the current mouse position (x, y)
 - updated the preprocessor files
 - fixed the HPGL preprocessor
@@ -680,7 +697,7 @@ CHANGELOG for FlatCAM beta
 - changed the Scale Entry in Object UI to FCEntry() GUI element in order to allow expressions to be entered. E.g: 1/25.4
 - some small changes in the Scale button handler in FlatCAMObj() class
 - added option to save objects as PDF files in File -> Save menu
-- optimized the FlatCAMGerber.clear_plot_apertures() method
+- optimized the GerberObject.clear_plot_apertures() method
 - some changes in the ObjectUI and for the Geometry UI
 - finished a very rough and limited HPGL2 file import 
 
@@ -710,7 +727,7 @@ CHANGELOG for FlatCAM beta
 - reverted this change: "selected object in Project used to ask twice for UI build" because it will not build the UI when a tab is closed for Document object and the object is selected
 - fixed issue after Geometry object edit; the GCode made from an edited object did not reflect the changes in the object
 - in Object UI, the Scale FCDoubleSpinner will no longer work for Return key press due of issues of unwanted scaling on focusOut event
-- in FlatCAMGeometry fixed the scale and offset methods to always process the self.solid_geometry
+- in GeometryObject fixed the scale and offset methods to always process the self.solid_geometry
 - Calibration Tool - finished the calibrated object creation method
 - updated the POT file
 - fixed an error in the German PO file
@@ -801,7 +818,7 @@ CHANGELOG for FlatCAM beta
 
 28.11.2019
 
-- small fixes in NCC Tool and in the FlatCAMGeometry class
+- small fixes in NCC Tool and in the GeometryObject class
 
 27.11.2019
 
@@ -819,7 +836,7 @@ CHANGELOG for FlatCAM beta
 
 - In Gerber isolation changed the UI
 - in Gerber isolation added the option to selectively isolate only certain polygons
-- made some optimizations in FlatCAMGerber.isolate() method
+- made some optimizations in GerberObject.isolate() method
 - updated the 'single' isolation of Gerber polygons to remove the polygon if clicked on it and it is already in the list of single polygons to be isolated
 - clicking to add a polygon when doing Single type isolation will add a blue shape marking the selected polygon, second click will remove that shape
 - fixed bugs in Paint Tool when painting single polygon
@@ -830,7 +847,7 @@ CHANGELOG for FlatCAM beta
 
 - in Tool Fiducials added a new fiducial type: chess pattern
 - work in Calibrate Excellon Tool
-- fixed the line numbers in the TextPlainEdit to fit all digits of the line number; activated the line numbers for FlatCAMScript objects too
+- fixed the line numbers in the TextPlainEdit to fit all digits of the line number; activated the line numbers for ScriptObject objects too
 - line numbers in the TextPlainEdit for the selected line are bold
 - made sure that the self.defaults dictionary is deepcopy-ed in the self.options dictionary
 - made sure that the units are read from the self.defaults and not from the GUI
@@ -842,7 +859,7 @@ CHANGELOG for FlatCAM beta
 - Tool Fiducials - updated the source_file object for the modified Gerber files
 - working on adding line numbers to the TextPlainEdit
 - GCode view now has line numbers
-- solved a bug that made selection of objects on canvas impossible if there is an object of type FlatCAMScript or FlatCAMDocument opened
+- solved a bug that made selection of objects on canvas impossible if there is an object of type ScriptObject or DocumentObject opened
 
 21.11.2019
 
@@ -899,7 +916,7 @@ CHANGELOG for FlatCAM beta
 - trying to improve the performance of View CNC Code command by using QPlainTextEdit; made the mods for it
 - when using the Find function in the TextEditor and the result reach the bottom of the document, the next find will be the first in the document (before it defaulted to the beginning of the document)
 - finished improving the show of text files in FlatCAM (CNC Code, Source files)
-- fixed an issue in the FlatCAMObj.FlatCAMGerber.convert_units() which needed to be updated after changes elsewhere
+- fixed an issue in the FlatCAMObj.GerberObject.convert_units() which needed to be updated after changes elsewhere
 
 12.11.2019
 
@@ -937,7 +954,7 @@ CHANGELOG for FlatCAM beta
 
 - the "CRTL+S" key combo when the Preferences Tab is in focus will save the Preferences instead of saving the Project
 - fixed bug in the Paint Tool that did not allow choosing a Paint Method that was not Standard
-- made sure that in the FlatCAMGeometry.merge() all the source data is deepcopy-ed in the final object
+- made sure that in the GeometryObject.merge() all the source data is deepcopy-ed in the final object
 - the font color of the Preferences tab will change to red if settings are not saved and it will revert to default when saved
 - fixed issue #333. The Geometry Editor Paint tool was not working and using it resulted in an error
 
@@ -1149,7 +1166,7 @@ CHANGELOG for FlatCAM beta
 - added a dark theme to FlatCAM (only for canvas). The selection is done in Edit -> Preferences -> General -> GUI Settings
 - updated the .POT file and worked a bit in the romanian translation
 - small changes: reduced the thickness of the axis in 3D mode from 3 pixels to 1 pixel
-- made sure that is the text in the source file of a FlatCAMDocument is HTML is loaded as such
+- made sure that is the text in the source file of a DocumentObject is HTML is loaded as such
 - added inverted icons
 
 6.10.2019
@@ -1193,20 +1210,20 @@ CHANGELOG for FlatCAM beta
 
 3.10.2019
 
-- previously I've added the initial layout for the FlatCAMDocument object
-- added more editing features in the Selected Tab for the FlatCAMDocument object
+- previously I've added the initial layout for the DocumentObject object
+- added more editing features in the Selected Tab for the DocumentObject object
 
 2.10.2019
 
 - fixed bug in Geometry Editor that did not allow the copy of geometric elements
 - created a new class that holds all the Code Editor functionality and integrated as a Editor in FlatCAM, the location is in flatcamEditors folder
 - remade all the functions for view_source, scripts and view_code to use the new TextEditor class; now all the Code Editor tabs are being kept alive, before only one could be in an open state
-- changed the name of the new object FlatCAMNotes to a more general one FlatCAMDocument
-- changed the way a new FlatCAMScript object is made, the method that is processing the Tcl commands when the Run button is clicked is moved to the FlatCAMObj.FlatCAMScript() class
+- changed the name of the new object FlatCAMNotes to a more general one DocumentObject
+- changed the way a new ScriptObject object is made, the method that is processing the Tcl commands when the Run button is clicked is moved to the FlatCAMObj.ScriptObject() class
 - reused the Multiprocessing Pool declared in the App for the ToolRulesCheck() class
 - adapted the Project context menu for the new types of FLatCAM objects
 - modified the setup_recent_files to accommodate the new FlatCAM objects
-- made sure that when an FlatCAMScript object is deleted, it's associated Tab is closed
+- made sure that when an ScriptObject object is deleted, it's associated Tab is closed
 - fixed the FlatCMAScript object saving when project is saved (loading a project with this script object is not working yet)
 - fixed the FlatCMAScript object when loading it from a project
 
@@ -1220,7 +1237,7 @@ CHANGELOG for FlatCAM beta
 - added new settings for the Gerber newly introduced feature to isolate with the V-Shape tools (tip dia, tip angle, tool_type and cut Z) in Edit -> Preferences -> Gerber Advanced
 - made those settings just added for Gerber, to be updated on object creation
 - added the Geo Tolerance parameter to those that are converted from MM to INCH
-- added two new FlatCAM objects: FlatCAMScript and FlatCAMNotes
+- added two new FlatCAM objects: ScriptObject and FlatCAMNotes
 
 30.09.2019
 
@@ -1443,7 +1460,7 @@ CHANGELOG for FlatCAM beta
 
 15.09.2019
 
-- refactored FlatCAMGeometry.mtool_gen_cncjob() method
+- refactored GeometryObject.mtool_gen_cncjob() method
 - fixed the TclCommandCncjob to work for multigeometry Geometry objects; still I had to fix the list of tools parameter, right now I am setting it to an empty list
 - update the Tcl Command isolate to be able to isolate exteriors, interiors besides the full isolation, using the iso_type parameter
 - fixed issue in ToolPaint that could not allow area painting of a geometry that was a list and not a Geometric element (polygon or MultiPolygon)
@@ -1819,7 +1836,7 @@ CHANGELOG for FlatCAM beta
 - done regression to solve the bug with multiple passes cutting from the copper features (I should remember not to make mods here)
 - if 'combine' is checked in Gerber isolation but there is only one pass, the resulting geometry will still be single geo
 - the 'passes' entry was changed to a IntSpinner so it will allow passes to be entered only in range (1, 999) - it will not allow entry of 0 which may create some issues
-- improved the FlatCAMGerber.isolate() function to work for geometry in the form of list and also in case that the elements of the list are LinearRings (like when doing the Exterior Isolation)
+- improved the GerberObject.isolate() function to work for geometry in the form of list and also in case that the elements of the list are LinearRings (like when doing the Exterior Isolation)
 - in NCC Tool made sure that at each run the old objects are deleted
 - fixed bug in camlib.Gerber.parse_lines() Gerber parser where for Allegro Gerber files the Gerber units were incorrectly detected
 - improved Mark Area Tool in Gerber Editor such that at each launch the previous markings are deleted
@@ -1914,7 +1931,7 @@ CHANGELOG for FlatCAM beta
 
 19.07.2019
 
-- fixed bug in FlatCAMObj.FlatCAMGeometry.ui_disconnect(); the widgets signals were not disconnected from handlers when required therefore the signals were connected in an exponential way
+- fixed bug in FlatCAMObj.GeometryObject.ui_disconnect(); the widgets signals were not disconnected from handlers when required therefore the signals were connected in an exponential way
 - some changes in the widgets used in the Selected tab for Geometry object
 - some PEP8 cleanup in FlatCAMObj.py
 - updated languages
@@ -2045,7 +2062,7 @@ CHANGELOG for FlatCAM beta
 
 - fixed bug in ToolCutout where creating a cutout object geometry from another external isolation geometry failed
 - fixed bug in cncjob TclCommand where the gcode could not be correctly generated due of missing bounds params in obj.options dict
-- fixed a hardcoded tolerance in FlatCAMGeometry.generatecncjob() and in FlatCAMGeometry.mtool_gen_cncjob() to use the parameter from Preferences
+- fixed a hardcoded tolerance in GeometryObject.generatecncjob() and in GeometryObject.mtool_gen_cncjob() to use the parameter from Preferences
 - updated translations
 
 5.06.2019
@@ -2219,7 +2236,7 @@ CHANGELOG for FlatCAM beta
 - fixed some bugs related to moving an Gerber object with the aperture table in view
 - added a new parameter in the Edit -> Preferences -> App Preferences named Geo Tolerance. This parameter control the level of geometric detail throughout FlatCAM. It directly influence the effect of Circle Steps parameter.
 - solved a bug in Excellon Editor that caused app crash when trying to edit a tool in Tool Table due of missing a tool offset
-- updated the ToolPanelize tool so the Gerber panel of type FlatCAMGerber can be isolated like any other FlatCAMGerber object
+- updated the ToolPanelize tool so the Gerber panel of type GerberObject can be isolated like any other GerberObject object
 - updated the ToolPanelize tool so it can be edited
 - modified the default values for toolchangez and endz parameters so they are now safe in all cases
 
@@ -2703,7 +2720,7 @@ CHANGELOG for FlatCAM beta
 - added ability to mark individual apertures in Gerber file using the Gerber Aperture Table
 - more modifications for the Gerber UI layout; made 'follow' an advanced Gerber option
 - added in Preferences a new Category: Gerber Advanced Options. For now it controls the display of Gerber Aperture Table and the "follow" attribute4
-- fixed FlatCAMGerber.merge() to merge the self.apertures[ap]['solid_geometry'] too
+- fixed GerberObject.merge() to merge the self.apertures[ap]['solid_geometry'] too
 - started to work on a new feature that allow adding a ToolChange GCode macro - GUI added both in CNCJob Selected tab and in CNCJob Preferences
 - added a limited 'sort-of' Gerber Editor: it allows buffering and scaling of apertures
 
@@ -2846,7 +2863,7 @@ CHANGELOG for FlatCAM beta
 - added total travel distance for CNCJob object created from Excellon Object in the CNCJob Selected tab
 - added 'FlatCAM ' prefix to any detached tab, for easy identification
 - remade the Grids context menu (right mouse button click on canvas). Now it has values linked to the units type (inch or mm). Added ability to add or delete grid values and they are persistent.
-- updated the function for the project context menu 'Generate CNC' menu entry (Action) to use the modernized function FlatCAMObj.FlatCAMGeometry.on_generatecnc_button_click()
+- updated the function for the project context menu 'Generate CNC' menu entry (Action) to use the modernized function FlatCAMObj.GeometryObject.on_generatecnc_button_click()
 - when linked, the grid snap on Y will copy the value in grid snap on X in real time
 - in Gerber aperture table now the values are displayed in the current units set in FlatCAM
 - added shortcut key 'J' (jump to location) in Editors and added an icon to the dialog popup window
@@ -2863,7 +2880,7 @@ CHANGELOG for FlatCAM beta
 - finished Gerber aperture table display
 - made the Gerber aperture table not visible as default and added a checkbox that can toggle the visibility
 - fixed issue with plotting in CNCJob; with Plot kind set to something else than 'all' when toggling Plot, it was defaulting to kind = 'all'
-- added (and commented) an experimental FlatCAMObj.FlatCAMGerber.plot_aperture()
+- added (and commented) an experimental FlatCAMObj.GerberObject.plot_aperture()
 
 12.02.2019
 
@@ -3052,7 +3069,7 @@ CHANGELOG for FlatCAM beta
 
 28.01.2018
 
-- fixed the FlatCAMGerber.merge() function
+- fixed the GerberObject.merge() function
 - added a new menu entry for the Gerber Join function: Edit -> Conversions -> "Join Gerber(s) to Gerber" allowing joining Gerber objects into a final Gerber object
 - moved Paint Tool defaults from Geometry section to the Tools section in Edit -> Preferences
 - added key shortcuts for Open Manual = F1 and for Open Online VideoHelp = F2
@@ -3075,13 +3092,13 @@ CHANGELOG for FlatCAM beta
 - added new entries to the Canvas context menu (Copy, Delete, Edit/Save, Move, New Excellon, New Geometry, New Project)
 - fixed GRBL_laser preprocessor file
 - updated function for copy of an Excellon object for the case when the object has slots
-- updated FlatCAMExcellon.merge() function to work in case some (or all) of the merged objects have slots  
+- updated ExcellonObject.merge() function to work in case some (or all) of the merged objects have slots  
 
 25.01.2019
 
 - deleted junk folders
 - remade the Panelize Tool: now it is much faster, it is multi-threaded, it works with multitool geometries and it works with multigeo geometries too.
-- made sure to copy the options attribute to the final object in the case of: FlatCAMGeometry.merge(), FlatCAMGerber.merge() and for the Panelize Tool
+- made sure to copy the options attribute to the final object in the case of: GeometryObject.merge(), GerberObject.merge() and for the Panelize Tool
 - modified the panelize TclCommand to take advantage of the new panelize() function; added a 'threaded' parameter (default value is 1) which controls the execution of the panelize TclCommand: threaded or non-threaded
 - fixed TclCommand Cutout
 - added a new TclCommand named CutoutAny. Keyword: cutout_any
@@ -3164,7 +3181,7 @@ CHANGELOG for FlatCAM beta
 - fixed the initial text in the ToolShell
 - reactivated the version check in case the release is not BETA; FlatCAMApp.App has now a beta object that when set True the application will show in the Title and help-> About that is Beta (and it disable version checking)
 - added a new name (mine: for good and/or bad) to the contributors list
-- fixed the Join function to work on Gerber and Excellon, Gerber and Gerber, Excellon and Excelon combination of objects. The merged property is the solid_geometry and the result is a FlatCAMGeometry object.
+- fixed the Join function to work on Gerber and Excellon, Gerber and Gerber, Excellon and Excelon combination of objects. The merged property is the solid_geometry and the result is a GeometryObject object.
 
 3.01.2019
 
@@ -3246,8 +3263,8 @@ CHANGELOG for FlatCAM beta
 
 18.12.2018
 
-- small changes in FlatCAMGeometry.plot()
-- updated the FlatCAMGeometry.merge() function and the Join Geometry feature to accommodate the different types of geometries: singlegeo and multigeo type
+- small changes in GeometryObject.plot()
+- updated the GeometryObject.merge() function and the Join Geometry feature to accommodate the different types of geometries: singlegeo and multigeo type
 - added Conversion submenu in Edit where I moved the Join features and added the Convert from MultiGeo to SingleGeo type and the reverse
 - added Copy Tool (on a selection of tools) feature in Geometry Object UI 
 - fixed the bounds() method for the MultiGeo geometry object so the canvas selection is working and also the Properties Tool

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 154 - 358
FlatCAMApp.py


+ 381 - 0
FlatCAMBookmark.py

@@ -0,0 +1,381 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCFileSaveDialog
+
+import sys
+import webbrowser
+
+from copy import deepcopy
+from datetime import datetime
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class BookmarkManager(QtWidgets.QWidget):
+
+    mark_rows = QtCore.pyqtSignal()
+
+    def __init__(self, app, storage, parent=None):
+        super(BookmarkManager, self).__init__(parent)
+
+        self.app = app
+
+        assert isinstance(storage, dict), "Storage argument is not a dictionary"
+
+        self.bm_dict = deepcopy(storage)
+
+        # Icon and title
+        # self.setWindowIcon(parent.app_icon)
+        # self.setWindowTitle(_("Bookmark Manager"))
+        # self.resize(600, 400)
+
+        # title = QtWidgets.QLabel(
+        #     "<font size=8><B>FlatCAM</B></font><BR>"
+        # )
+        # title.setOpenExternalLinks(True)
+
+        # layouts
+        layout = QtWidgets.QVBoxLayout()
+        self.setLayout(layout)
+
+        table_hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(table_hlay)
+
+        self.table_widget = FCTable(drag_drop=True, protected_rows=[0, 1])
+        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        table_hlay.addWidget(self.table_widget)
+
+        self.table_widget.setColumnCount(3)
+        self.table_widget.setColumnWidth(0, 20)
+        self.table_widget.setHorizontalHeaderLabels(
+            [
+                '#',
+                _('Title'),
+                _('Web Link')
+            ]
+        )
+        self.table_widget.horizontalHeaderItem(0).setToolTip(
+            _("Index.\n"
+              "The rows in gray color will populate the Bookmarks menu.\n"
+              "The number of gray colored rows is set in Preferences."))
+        self.table_widget.horizontalHeaderItem(1).setToolTip(
+            _("Description of the link that is set as an menu action.\n"
+              "Try to keep it short because it is installed as a menu item."))
+        self.table_widget.horizontalHeaderItem(2).setToolTip(
+            _("Web Link. E.g: https://your_website.org "))
+
+        # pal = QtGui.QPalette()
+        # pal.setColor(QtGui.QPalette.Background, Qt.white)
+
+        # New Bookmark
+        new_vlay = QtWidgets.QVBoxLayout()
+        layout.addLayout(new_vlay)
+
+        new_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Bookmark"))
+        new_vlay.addWidget(new_title_lbl)
+
+        form0 = QtWidgets.QFormLayout()
+        new_vlay.addLayout(form0)
+
+        title_lbl = QtWidgets.QLabel('%s:' % _("Title"))
+        self.title_entry = FCEntry()
+        form0.addRow(title_lbl, self.title_entry)
+
+        link_lbl = QtWidgets.QLabel('%s:' % _("Web Link"))
+        self.link_entry = FCEntry()
+        self.link_entry.set_value('http://')
+        form0.addRow(link_lbl, self.link_entry)
+
+        # Buttons Layout
+        button_hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(button_hlay)
+
+        add_entry_btn = FCButton(_("Add Entry"))
+        remove_entry_btn = FCButton(_("Remove Entry"))
+        export_list_btn = FCButton(_("Export List"))
+        import_list_btn = FCButton(_("Import List"))
+        # closebtn = QtWidgets.QPushButton(_("Close"))
+
+        # button_hlay.addStretch()
+        button_hlay.addWidget(add_entry_btn)
+        button_hlay.addWidget(remove_entry_btn)
+
+        button_hlay.addWidget(export_list_btn)
+        button_hlay.addWidget(import_list_btn)
+        # button_hlay.addWidget(closebtn)
+        # ##############################################################################
+        # ######################## SIGNALS #############################################
+        # ##############################################################################
+
+        add_entry_btn.clicked.connect(self.on_add_entry)
+        remove_entry_btn.clicked.connect(self.on_remove_entry)
+        export_list_btn.clicked.connect(self.on_export_bookmarks)
+        import_list_btn.clicked.connect(self.on_import_bookmarks)
+        self.title_entry.returnPressed.connect(self.on_add_entry)
+        self.link_entry.returnPressed.connect(self.on_add_entry)
+        # closebtn.clicked.connect(self.accept)
+
+        self.table_widget.drag_drop_sig.connect(self.mark_table_rows_for_actions)
+        self.build_bm_ui()
+
+    def build_bm_ui(self):
+
+        self.table_widget.setRowCount(len(self.bm_dict))
+
+        nr_crt = 0
+        sorted_bookmarks = sorted(list(self.bm_dict.items()), key=lambda x: int(x[0]))
+        for entry, bookmark in sorted_bookmarks:
+            row = nr_crt
+            nr_crt += 1
+
+            title = bookmark[0]
+            weblink = bookmark[1]
+
+            id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
+            # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+            self.table_widget.setItem(row, 0, id_item)  # Tool name/id
+
+            title_item = QtWidgets.QTableWidgetItem(title)
+            self.table_widget.setItem(row, 1, title_item)
+
+            weblink_txt = QtWidgets.QTextBrowser()
+            weblink_txt.setOpenExternalLinks(True)
+            weblink_txt.setFrameStyle(QtWidgets.QFrame.NoFrame)
+            weblink_txt.document().setDefaultStyleSheet("a{ text-decoration: none; }")
+
+            weblink_txt.setHtml('<a href=%s>%s</a>' % (weblink, weblink))
+
+            self.table_widget.setCellWidget(row, 2, weblink_txt)
+
+            vertical_header = self.table_widget.verticalHeader()
+            vertical_header.hide()
+
+            horizontal_header = self.table_widget.horizontalHeader()
+            horizontal_header.setMinimumSectionSize(10)
+            horizontal_header.setDefaultSectionSize(70)
+            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+            horizontal_header.resizeSection(0, 20)
+            horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
+            horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+
+        self.mark_table_rows_for_actions()
+
+        self.app.defaults["global_bookmarks"].clear()
+        for key, val in self.bm_dict.items():
+            self.app.defaults["global_bookmarks"][key] = deepcopy(val)
+
+    def on_add_entry(self, **kwargs):
+        """
+        Add a entry in the Bookmark Table and in the menu actions
+        :return: None
+        """
+        if 'title' in kwargs:
+            title = kwargs['title']
+        else:
+            title = self.title_entry.get_value()
+        if title == '':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Title entry is empty."))
+            return 'fail'
+
+        if 'link' in kwargs:
+            link = kwargs['link']
+        else:
+            link = self.link_entry.get_value()
+
+        if link == 'http://':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Web link entry is empty."))
+            return 'fail'
+
+        # if 'http' not in link or 'https' not in link:
+        #     link = 'http://' + link
+
+        for bookmark in self.bm_dict.values():
+            if title == bookmark[0] or link == bookmark[1]:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Either the Title or the Weblink already in the table."))
+                return 'fail'
+
+        # for some reason if the last char in the weblink is a slash it does not make the link clickable
+        # so I remove it
+        if link[-1] == '/':
+            link = link[:-1]
+        # add the new entry to storage
+        new_entry = len(self.bm_dict) + 1
+        self.bm_dict[str(new_entry)] = [title, link]
+
+        # add the link to the menu but only if it is within the set limit
+        bm_limit = int(self.app.defaults["global_bookmarks_limit"])
+        if len(self.bm_dict) < bm_limit:
+            act = QtWidgets.QAction(parent=self.app.ui.menuhelp_bookmarks)
+            act.setText(title)
+            act.setIcon(QtGui.QIcon(self.app.resource_location + '/link16.png'))
+            act.triggered.connect(lambda: webbrowser.open(link))
+            self.app.ui.menuhelp_bookmarks.insertAction(self.app.ui.menuhelp_bookmarks_manager, act)
+
+        self.app.inform.emit('[success] %s' % _("Bookmark added."))
+
+        # add the new entry to the bookmark manager table
+        self.build_bm_ui()
+
+    def on_remove_entry(self):
+        """
+        Remove an Entry in the Bookmark table and from the menu actions
+        :return:
+        """
+        index_list = []
+        for model_index in self.table_widget.selectionModel().selectedRows():
+            index = QtCore.QPersistentModelIndex(model_index)
+            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':
+                self.app.inform.emit('[WARNING_NOTCL] %s.' % _("This bookmark can not be removed"))
+                self.build_bm_ui()
+                return
+            else:
+                for k, bookmark in list(self.bm_dict.items()):
+                    if title_to_remove == bookmark[0]:
+                        # remove from the storage
+                        self.bm_dict.pop(k, None)
+
+                        for act in self.app.ui.menuhelp_bookmarks.actions():
+                            if act.text() == title_to_remove:
+                                # disconnect the signal
+                                try:
+                                    act.triggered.disconnect()
+                                except TypeError:
+                                    pass
+                                # remove the action from the menu
+                                self.app.ui.menuhelp_bookmarks.removeAction(act)
+
+        # house keeping: it pays to have keys increased by one
+        new_key = 0
+        new_dict = {}
+        for k, v in self.bm_dict.items():
+            # we start with key 1 so we can use the len(self.bm_dict)
+            # when adding bookmarks (keys in bm_dict)
+            new_key += 1
+            new_dict[str(new_key)] = v
+
+        self.bm_dict = deepcopy(new_dict)
+        new_dict.clear()
+
+        self.app.inform.emit('[success] %s' % _("Bookmark removed."))
+
+        # for index in index_list:
+        #     self.table_widget.model().removeRow(index.row())
+        self.build_bm_ui()
+
+    def on_export_bookmarks(self):
+        self.app.report_usage("on_export_bookmarks")
+        self.app.log.debug("on_export_bookmarks()")
+
+        date = str(datetime.today()).rpartition('.')[0]
+        date = ''.join(c for c in date if c not in ':-')
+        date = date.replace(' ', '_')
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export FlatCAM Bookmarks"),
+                                                             directory='{l_save}/FlatCAM_{n}_{date}'.format(
+                                                                 l_save=str(self.app.get_last_save_folder()),
+                                                                 n=_("Bookmarks"),
+                                                                 date=date),
+                                                             filter=filter__)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            try:
+                f = open(filename, 'w')
+                f.close()
+            except PermissionError:
+                self.app.inform.emit('[WARNING] %s' %
+                                     _("Permission denied, saving not possible.\n"
+                                       "Most likely another app is holding the file open and not accessible."))
+                return
+            except IOError:
+                self.app.log.debug('Creating a new bookmarks file ...')
+                f = open(filename, 'w')
+                f.close()
+            except Exception:
+                e = sys.exc_info()[0]
+                self.app.log.error("Could not load defaults file.")
+                self.app.log.error(str(e))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
+                return
+
+            # Save Bookmarks to a file
+            try:
+                with open(filename, "w") as f:
+                    for title, link in self.bm_dict.items():
+                        line2write = str(title) + ':' + str(link) + '\n'
+                        f.write(line2write)
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write bookmarks to file."))
+                return
+        self.app.inform.emit('[success] %s: %s' % (_("Exported bookmarks to"), filename))
+
+    def on_import_bookmarks(self):
+        self.app.log.debug("on_import_bookmarks()")
+
+        filter_ = "Text File (*.txt);;All Files (*.*)"
+        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Bookmarks"), filter=filter_)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+        else:
+            try:
+                with open(filename) as f:
+                    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."))
+                return
+
+            for line in bookmarks:
+                proc_line = line.replace(' ', '').partition(':')
+                self.on_add_entry(title=proc_line[0], link=proc_line[2])
+
+            self.app.inform.emit('[success] %s: %s' % (_("Imported Bookmarks from"), filename))
+
+    def mark_table_rows_for_actions(self):
+        for row in range(self.table_widget.rowCount()):
+            item_to_paint = self.table_widget.item(row, 0)
+            if row < self.app.defaults["global_bookmarks_limit"]:
+                item_to_paint.setBackground(QtGui.QColor('gray'))
+                # item_to_paint.setForeground(QtGui.QColor('black'))
+            else:
+                item_to_paint.setBackground(QtGui.QColor('white'))
+                # item_to_paint.setForeground(QtGui.QColor('black'))
+
+    def rebuild_actions(self):
+        # rebuild the storage to reflect the order of the lines
+        self.bm_dict.clear()
+        for row in range(self.table_widget.rowCount()):
+            title = self.table_widget.item(row, 1).text()
+            wlink = self.table_widget.cellWidget(row, 2).toPlainText()
+
+            entry = int(row) + 1
+            self.bm_dict.update(
+                {
+                    str(entry): [title, wlink]
+                }
+            )
+
+        self.app.install_bookmarks(book_dict=self.bm_dict)
+
+    # def accept(self):
+    #     self.rebuild_actions()
+    #     super().accept()
+
+    def closeEvent(self, QCloseEvent):
+        self.rebuild_actions()
+        super().closeEvent(QCloseEvent)

+ 9 - 2757
FlatCAMCommon.py

@@ -11,17 +11,6 @@
 # Date: 11/4/2019                                          #
 # ##########################################################
 
-from PyQt5 import QtGui, QtCore, QtWidgets
-from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
-    FCTree, RadioSet, FCFileSaveDialog
-from camlib import to_dict
-
-import sys
-import webbrowser
-import json
-
-from copy import deepcopy
-from datetime import datetime
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -31,6 +20,15 @@ if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
 
+class GracefulException(Exception):
+    # Graceful Exception raised when the user is requesting to cancel the current threaded task
+    def __init__(self):
+        super().__init__()
+
+    def __str__(self):
+        return '\n\n%s' % _("The user requested a graceful exit of the current task.")
+
+
 class LoudDict(dict):
     """
     A Dictionary with a callback for
@@ -95,2752 +93,6 @@ class FCSignal:
                   'from signal %s' % (func, self))
 
 
-class BookmarkManager(QtWidgets.QWidget):
-
-    mark_rows = QtCore.pyqtSignal()
-
-    def __init__(self, app, storage, parent=None):
-        super(BookmarkManager, self).__init__(parent)
-
-        self.app = app
-
-        assert isinstance(storage, dict), "Storage argument is not a dictionary"
-
-        self.bm_dict = deepcopy(storage)
-
-        # Icon and title
-        # self.setWindowIcon(parent.app_icon)
-        # self.setWindowTitle(_("Bookmark Manager"))
-        # self.resize(600, 400)
-
-        # title = QtWidgets.QLabel(
-        #     "<font size=8><B>FlatCAM</B></font><BR>"
-        # )
-        # title.setOpenExternalLinks(True)
-
-        # layouts
-        layout = QtWidgets.QVBoxLayout()
-        self.setLayout(layout)
-
-        table_hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(table_hlay)
-
-        self.table_widget = FCTable(drag_drop=True, protected_rows=[0, 1])
-        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
-        table_hlay.addWidget(self.table_widget)
-
-        self.table_widget.setColumnCount(3)
-        self.table_widget.setColumnWidth(0, 20)
-        self.table_widget.setHorizontalHeaderLabels(
-            [
-                '#',
-                _('Title'),
-                _('Web Link')
-            ]
-        )
-        self.table_widget.horizontalHeaderItem(0).setToolTip(
-            _("Index.\n"
-              "The rows in gray color will populate the Bookmarks menu.\n"
-              "The number of gray colored rows is set in Preferences."))
-        self.table_widget.horizontalHeaderItem(1).setToolTip(
-            _("Description of the link that is set as an menu action.\n"
-              "Try to keep it short because it is installed as a menu item."))
-        self.table_widget.horizontalHeaderItem(2).setToolTip(
-            _("Web Link. E.g: https://your_website.org "))
-
-        # pal = QtGui.QPalette()
-        # pal.setColor(QtGui.QPalette.Background, Qt.white)
-
-        # New Bookmark
-        new_vlay = QtWidgets.QVBoxLayout()
-        layout.addLayout(new_vlay)
-
-        new_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Bookmark"))
-        new_vlay.addWidget(new_title_lbl)
-
-        form0 = QtWidgets.QFormLayout()
-        new_vlay.addLayout(form0)
-
-        title_lbl = QtWidgets.QLabel('%s:' % _("Title"))
-        self.title_entry = FCEntry()
-        form0.addRow(title_lbl, self.title_entry)
-
-        link_lbl = QtWidgets.QLabel('%s:' % _("Web Link"))
-        self.link_entry = FCEntry()
-        self.link_entry.set_value('http://')
-        form0.addRow(link_lbl, self.link_entry)
-
-        # Buttons Layout
-        button_hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(button_hlay)
-
-        add_entry_btn = FCButton(_("Add Entry"))
-        remove_entry_btn = FCButton(_("Remove Entry"))
-        export_list_btn = FCButton(_("Export List"))
-        import_list_btn = FCButton(_("Import List"))
-        # closebtn = QtWidgets.QPushButton(_("Close"))
-
-        # button_hlay.addStretch()
-        button_hlay.addWidget(add_entry_btn)
-        button_hlay.addWidget(remove_entry_btn)
-
-        button_hlay.addWidget(export_list_btn)
-        button_hlay.addWidget(import_list_btn)
-        # button_hlay.addWidget(closebtn)
-        # ##############################################################################
-        # ######################## SIGNALS #############################################
-        # ##############################################################################
-
-        add_entry_btn.clicked.connect(self.on_add_entry)
-        remove_entry_btn.clicked.connect(self.on_remove_entry)
-        export_list_btn.clicked.connect(self.on_export_bookmarks)
-        import_list_btn.clicked.connect(self.on_import_bookmarks)
-        self.title_entry.returnPressed.connect(self.on_add_entry)
-        self.link_entry.returnPressed.connect(self.on_add_entry)
-        # closebtn.clicked.connect(self.accept)
-
-        self.table_widget.drag_drop_sig.connect(self.mark_table_rows_for_actions)
-        self.build_bm_ui()
-
-    def build_bm_ui(self):
-
-        self.table_widget.setRowCount(len(self.bm_dict))
-
-        nr_crt = 0
-        sorted_bookmarks = sorted(list(self.bm_dict.items()), key=lambda x: int(x[0]))
-        for entry, bookmark in sorted_bookmarks:
-            row = nr_crt
-            nr_crt += 1
-
-            title = bookmark[0]
-            weblink = bookmark[1]
-
-            id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
-            # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-            self.table_widget.setItem(row, 0, id_item)  # Tool name/id
-
-            title_item = QtWidgets.QTableWidgetItem(title)
-            self.table_widget.setItem(row, 1, title_item)
-
-            weblink_txt = QtWidgets.QTextBrowser()
-            weblink_txt.setOpenExternalLinks(True)
-            weblink_txt.setFrameStyle(QtWidgets.QFrame.NoFrame)
-            weblink_txt.document().setDefaultStyleSheet("a{ text-decoration: none; }")
-
-            weblink_txt.setHtml('<a href=%s>%s</a>' % (weblink, weblink))
-
-            self.table_widget.setCellWidget(row, 2, weblink_txt)
-
-            vertical_header = self.table_widget.verticalHeader()
-            vertical_header.hide()
-
-            horizontal_header = self.table_widget.horizontalHeader()
-            horizontal_header.setMinimumSectionSize(10)
-            horizontal_header.setDefaultSectionSize(70)
-            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-            horizontal_header.resizeSection(0, 20)
-            horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
-            horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
-
-        self.mark_table_rows_for_actions()
-
-        self.app.defaults["global_bookmarks"].clear()
-        for key, val in self.bm_dict.items():
-            self.app.defaults["global_bookmarks"][key] = deepcopy(val)
-
-    def on_add_entry(self, **kwargs):
-        """
-        Add a entry in the Bookmark Table and in the menu actions
-        :return: None
-        """
-        if 'title' in kwargs:
-            title = kwargs['title']
-        else:
-            title = self.title_entry.get_value()
-        if title == '':
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Title entry is empty."))
-            return 'fail'
-
-        if 'link' in kwargs:
-            link = kwargs['link']
-        else:
-            link = self.link_entry.get_value()
-
-        if link == 'http://':
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Web link entry is empty."))
-            return 'fail'
-
-        # if 'http' not in link or 'https' not in link:
-        #     link = 'http://' + link
-
-        for bookmark in self.bm_dict.values():
-            if title == bookmark[0] or link == bookmark[1]:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Either the Title or the Weblink already in the table."))
-                return 'fail'
-
-        # for some reason if the last char in the weblink is a slash it does not make the link clickable
-        # so I remove it
-        if link[-1] == '/':
-            link = link[:-1]
-        # add the new entry to storage
-        new_entry = len(self.bm_dict) + 1
-        self.bm_dict[str(new_entry)] = [title, link]
-
-        # add the link to the menu but only if it is within the set limit
-        bm_limit = int(self.app.defaults["global_bookmarks_limit"])
-        if len(self.bm_dict) < bm_limit:
-            act = QtWidgets.QAction(parent=self.app.ui.menuhelp_bookmarks)
-            act.setText(title)
-            act.setIcon(QtGui.QIcon(self.app.resource_location + '/link16.png'))
-            act.triggered.connect(lambda: webbrowser.open(link))
-            self.app.ui.menuhelp_bookmarks.insertAction(self.app.ui.menuhelp_bookmarks_manager, act)
-
-        self.app.inform.emit('[success] %s' % _("Bookmark added."))
-
-        # add the new entry to the bookmark manager table
-        self.build_bm_ui()
-
-    def on_remove_entry(self):
-        """
-        Remove an Entry in the Bookmark table and from the menu actions
-        :return:
-        """
-        index_list = []
-        for model_index in self.table_widget.selectionModel().selectedRows():
-            index = QtCore.QPersistentModelIndex(model_index)
-            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':
-                self.app.inform.emit('[WARNING_NOTCL] %s.' % _("This bookmark can not be removed"))
-                self.build_bm_ui()
-                return
-            else:
-                for k, bookmark in list(self.bm_dict.items()):
-                    if title_to_remove == bookmark[0]:
-                        # remove from the storage
-                        self.bm_dict.pop(k, None)
-
-                        for act in self.app.ui.menuhelp_bookmarks.actions():
-                            if act.text() == title_to_remove:
-                                # disconnect the signal
-                                try:
-                                    act.triggered.disconnect()
-                                except TypeError:
-                                    pass
-                                # remove the action from the menu
-                                self.app.ui.menuhelp_bookmarks.removeAction(act)
-
-        # house keeping: it pays to have keys increased by one
-        new_key = 0
-        new_dict = {}
-        for k, v in self.bm_dict.items():
-            # we start with key 1 so we can use the len(self.bm_dict)
-            # when adding bookmarks (keys in bm_dict)
-            new_key += 1
-            new_dict[str(new_key)] = v
-
-        self.bm_dict = deepcopy(new_dict)
-        new_dict.clear()
-
-        self.app.inform.emit('[success] %s' % _("Bookmark removed."))
-
-        # for index in index_list:
-        #     self.table_widget.model().removeRow(index.row())
-        self.build_bm_ui()
-
-    def on_export_bookmarks(self):
-        self.app.report_usage("on_export_bookmarks")
-        self.app.log.debug("on_export_bookmarks()")
-
-        date = str(datetime.today()).rpartition('.')[0]
-        date = ''.join(c for c in date if c not in ':-')
-        date = date.replace(' ', '_')
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export FlatCAM Bookmarks"),
-                                                             directory='{l_save}/FlatCAM_{n}_{date}'.format(
-                                                                 l_save=str(self.app.get_last_save_folder()),
-                                                                 n=_("Bookmarks"),
-                                                                 date=date),
-                                                             filter=filter__)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            try:
-                f = open(filename, 'w')
-                f.close()
-            except PermissionError:
-                self.app.inform.emit('[WARNING] %s' %
-                                     _("Permission denied, saving not possible.\n"
-                                       "Most likely another app is holding the file open and not accessible."))
-                return
-            except IOError:
-                self.app.log.debug('Creating a new bookmarks file ...')
-                f = open(filename, 'w')
-                f.close()
-            except Exception:
-                e = sys.exc_info()[0]
-                self.app.log.error("Could not load defaults file.")
-                self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
-                return
-
-            # Save Bookmarks to a file
-            try:
-                with open(filename, "w") as f:
-                    for title, link in self.bm_dict.items():
-                        line2write = str(title) + ':' + str(link) + '\n'
-                        f.write(line2write)
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write bookmarks to file."))
-                return
-        self.app.inform.emit('[success] %s: %s' % (_("Exported bookmarks to"), filename))
-
-    def on_import_bookmarks(self):
-        self.app.log.debug("on_import_bookmarks()")
-
-        filter_ = "Text File (*.txt);;All Files (*.*)"
-        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Bookmarks"), filter=filter_)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-        else:
-            try:
-                with open(filename) as f:
-                    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."))
-                return
-
-            for line in bookmarks:
-                proc_line = line.replace(' ', '').partition(':')
-                self.on_add_entry(title=proc_line[0], link=proc_line[2])
-
-            self.app.inform.emit('[success] %s: %s' % (_("Imported Bookmarks from"), filename))
-
-    def mark_table_rows_for_actions(self):
-        for row in range(self.table_widget.rowCount()):
-            item_to_paint = self.table_widget.item(row, 0)
-            if row < self.app.defaults["global_bookmarks_limit"]:
-                item_to_paint.setBackground(QtGui.QColor('gray'))
-                # item_to_paint.setForeground(QtGui.QColor('black'))
-            else:
-                item_to_paint.setBackground(QtGui.QColor('white'))
-                # item_to_paint.setForeground(QtGui.QColor('black'))
-
-    def rebuild_actions(self):
-        # rebuild the storage to reflect the order of the lines
-        self.bm_dict.clear()
-        for row in range(self.table_widget.rowCount()):
-            title = self.table_widget.item(row, 1).text()
-            wlink = self.table_widget.cellWidget(row, 2).toPlainText()
-
-            entry = int(row) + 1
-            self.bm_dict.update(
-                {
-                    str(entry): [title, wlink]
-                }
-            )
-
-        self.app.install_bookmarks(book_dict=self.bm_dict)
-
-    # def accept(self):
-    #     self.rebuild_actions()
-    #     super().accept()
-
-    def closeEvent(self, QCloseEvent):
-        self.rebuild_actions()
-        super().closeEvent(QCloseEvent)
-
-
-class ToolsDB(QtWidgets.QWidget):
-
-    mark_tools_rows = QtCore.pyqtSignal()
-
-    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
-        super(ToolsDB, self).__init__(parent)
-
-        self.app = app
-        self.decimals = 4
-        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"]
-
-        '''
-        dict to hold all the tools in the Tools DB
-        format:
-        {
-            tool_id: {
-                'name': 'new_tool'
-                'tooldia': self.app.defaults["geometry_cnctooldia"]
-                'offset': 'Path'
-                'offset_value': 0.0
-                'type':  _('Rough'),
-                'tool_type': 'C1'
-                'data': dict()
-            }
-        }
-        '''
-        self.db_tool_dict = {}
-
-        # layouts
-        layout = QtWidgets.QVBoxLayout()
-        self.setLayout(layout)
-
-        table_hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(table_hlay)
-
-        self.table_widget = FCTable(drag_drop=True)
-        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
-        table_hlay.addWidget(self.table_widget)
-
-        # set the number of columns and the headers tool tips
-        self.configure_table()
-
-        # pal = QtGui.QPalette()
-        # pal.setColor(QtGui.QPalette.Background, Qt.white)
-
-        # New Bookmark
-        new_vlay = QtWidgets.QVBoxLayout()
-        layout.addLayout(new_vlay)
-
-        # new_tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Tool"))
-        # new_vlay.addWidget(new_tool_lbl, alignment=QtCore.Qt.AlignBottom)
-
-        self.buttons_frame = QtWidgets.QFrame()
-        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
-        layout.addWidget(self.buttons_frame)
-        self.buttons_box = QtWidgets.QHBoxLayout()
-        self.buttons_box.setContentsMargins(0, 0, 0, 0)
-        self.buttons_frame.setLayout(self.buttons_box)
-        self.buttons_frame.show()
-
-        add_entry_btn = FCButton(_("Add Geometry Tool in DB"))
-        add_entry_btn.setToolTip(
-            _("Add a new tool in the Tools Database.\n"
-              "It will be used in the Geometry UI.\n"
-              "You can edit it after it is added.")
-        )
-        self.buttons_box.addWidget(add_entry_btn)
-
-        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
-        # add_fct_entry_btn.setToolTip(
-        #     _("Add a new tool in the Tools Database.\n"
-        #       "It will be used in the Paint/NCC Tools UI.\n"
-        #       "You can edit it after it is added.")
-        # )
-        # self.buttons_box.addWidget(add_fct_entry_btn)
-
-        remove_entry_btn = FCButton(_("Delete Tool from DB"))
-        remove_entry_btn.setToolTip(
-            _("Remove a selection of tools in the Tools Database.")
-        )
-        self.buttons_box.addWidget(remove_entry_btn)
-
-        export_db_btn = FCButton(_("Export DB"))
-        export_db_btn.setToolTip(
-            _("Save the Tools Database to a custom text file.")
-        )
-        self.buttons_box.addWidget(export_db_btn)
-
-        import_db_btn = FCButton(_("Import DB"))
-        import_db_btn.setToolTip(
-            _("Load the Tools Database information's from a custom text file.")
-        )
-        self.buttons_box.addWidget(import_db_btn)
-
-        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
-        self.add_tool_from_db.setToolTip(
-            _("Add a new tool in the Tools Table of the\n"
-              "active Geometry object after selecting a tool\n"
-              "in the Tools Database.")
-        )
-        self.add_tool_from_db.hide()
-
-        self.cancel_tool_from_db = FCButton(_("Cancel"))
-        self.cancel_tool_from_db.hide()
-
-        hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(hlay)
-        hlay.addWidget(self.add_tool_from_db)
-        hlay.addWidget(self.cancel_tool_from_db)
-        hlay.addStretch()
-
-        # ##############################################################################
-        # ######################## SIGNALS #############################################
-        # ##############################################################################
-
-        add_entry_btn.clicked.connect(self.on_tool_add)
-        remove_entry_btn.clicked.connect(self.on_tool_delete)
-        export_db_btn.clicked.connect(self.on_export_tools_db_file)
-        import_db_btn.clicked.connect(self.on_import_tools_db_file)
-        # closebtn.clicked.connect(self.accept)
-
-        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
-        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
-
-        self.setup_db_ui()
-
-    def configure_table(self):
-        self.table_widget.setColumnCount(27)
-        # self.table_widget.setColumnWidth(0, 20)
-        self.table_widget.setHorizontalHeaderLabels(
-            [
-                '#',
-                _("Tool Name"),
-                _("Tool Dia"),
-                _("Tool Offset"),
-                _("Custom Offset"),
-                _("Tool Type"),
-                _("Tool Shape"),
-                _("Cut Z"),
-                _("MultiDepth"),
-                _("DPP"),
-                _("V-Dia"),
-                _("V-Angle"),
-                _("Travel Z"),
-                _("FR"),
-                _("FR Z"),
-                _("FR Rapids"),
-                _("Spindle Speed"),
-                _("Dwell"),
-                _("Dwelltime"),
-                _("Preprocessor"),
-                _("ExtraCut"),
-                _("E-Cut Length"),
-                _("Toolchange"),
-                _("Toolchange XY"),
-                _("Toolchange Z"),
-                _("Start Z"),
-                _("End Z"),
-            ]
-        )
-        self.table_widget.horizontalHeaderItem(0).setToolTip(
-            _("Tool Index."))
-        self.table_widget.horizontalHeaderItem(1).setToolTip(
-            _("Tool name.\n"
-              "This is not used in the app, it's function\n"
-              "is to serve as a note for the user."))
-        self.table_widget.horizontalHeaderItem(2).setToolTip(
-            _("Tool Diameter."))
-        self.table_widget.horizontalHeaderItem(3).setToolTip(
-            _("Tool Offset.\n"
-              "Can be of a few types:\n"
-              "Path = zero offset\n"
-              "In = offset inside by half of tool diameter\n"
-              "Out = offset outside by half of tool diameter\n"
-              "Custom = custom offset using the Custom Offset value"))
-        self.table_widget.horizontalHeaderItem(4).setToolTip(
-            _("Custom Offset.\n"
-              "A value to be used as offset from the current path."))
-        self.table_widget.horizontalHeaderItem(5).setToolTip(
-            _("Tool Type.\n"
-              "Can be:\n"
-              "Iso = isolation cut\n"
-              "Rough = rough cut, low feedrate, multiple passes\n"
-              "Finish = finishing cut, high feedrate"))
-        self.table_widget.horizontalHeaderItem(6).setToolTip(
-            _("Tool Shape. \n"
-              "Can be:\n"
-              "C1 ... C4 = circular tool with x flutes\n"
-              "B = ball tip milling tool\n"
-              "V = v-shape milling tool"))
-        self.table_widget.horizontalHeaderItem(7).setToolTip(
-            _("Cutting Depth.\n"
-              "The depth at which to cut into material."))
-        self.table_widget.horizontalHeaderItem(8).setToolTip(
-            _("Multi Depth.\n"
-              "Selecting this will allow cutting in multiple passes,\n"
-              "each pass adding a DPP parameter depth."))
-        self.table_widget.horizontalHeaderItem(9).setToolTip(
-            _("DPP. Depth per Pass.\n"
-              "The value used to cut into material on each pass."))
-        self.table_widget.horizontalHeaderItem(10).setToolTip(
-            _("V-Dia.\n"
-              "Diameter of the tip for V-Shape Tools."))
-        self.table_widget.horizontalHeaderItem(11).setToolTip(
-            _("V-Agle.\n"
-              "Angle at the tip for the V-Shape Tools."))
-        self.table_widget.horizontalHeaderItem(12).setToolTip(
-            _("Clearance Height.\n"
-              "Height at which the milling bit will travel between cuts,\n"
-              "above the surface of the material, avoiding all fixtures."))
-        self.table_widget.horizontalHeaderItem(13).setToolTip(
-            _("FR. Feedrate\n"
-              "The speed on XY plane used while cutting into material."))
-        self.table_widget.horizontalHeaderItem(14).setToolTip(
-            _("FR Z. Feedrate Z\n"
-              "The speed on Z plane."))
-        self.table_widget.horizontalHeaderItem(15).setToolTip(
-            _("FR Rapids. Feedrate Rapids\n"
-              "Speed used while moving as fast as possible.\n"
-              "This is used only by some devices that can't use\n"
-              "the G0 g-code command. Mostly 3D printers."))
-        self.table_widget.horizontalHeaderItem(16).setToolTip(
-            _("Spindle Speed.\n"
-              "If it's left empty it will not be used.\n"
-              "The speed of the spindle in RPM."))
-        self.table_widget.horizontalHeaderItem(17).setToolTip(
-            _("Dwell.\n"
-              "Check this if a delay is needed to allow\n"
-              "the spindle motor to reach it's set speed."))
-        self.table_widget.horizontalHeaderItem(18).setToolTip(
-            _("Dwell Time.\n"
-              "A delay used to allow the motor spindle reach it's set speed."))
-        self.table_widget.horizontalHeaderItem(19).setToolTip(
-            _("Preprocessor.\n"
-              "A selection of files that will alter the generated G-code\n"
-              "to fit for a number of use cases."))
-        self.table_widget.horizontalHeaderItem(20).setToolTip(
-            _("Extra Cut.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation."))
-        self.table_widget.horizontalHeaderItem(21).setToolTip(
-            _("Extra Cut length.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation. This is the length of\n"
-              "the extra cut."))
-        self.table_widget.horizontalHeaderItem(22).setToolTip(
-            _("Toolchange.\n"
-              "It will create a toolchange event.\n"
-              "The kind of toolchange is determined by\n"
-              "the preprocessor file."))
-        self.table_widget.horizontalHeaderItem(23).setToolTip(
-            _("Toolchange XY.\n"
-              "A set of coordinates in the format (x, y).\n"
-              "Will determine the cartesian position of the point\n"
-              "where the tool change event take place."))
-        self.table_widget.horizontalHeaderItem(24).setToolTip(
-            _("Toolchange Z.\n"
-              "The position on Z plane where the tool change event take place."))
-        self.table_widget.horizontalHeaderItem(25).setToolTip(
-            _("Start Z.\n"
-              "If it's left empty it will not be used.\n"
-              "A position on Z plane to move immediately after job start."))
-        self.table_widget.horizontalHeaderItem(26).setToolTip(
-            _("End Z.\n"
-              "A position on Z plane to move immediately after job stop."))
-
-    def setup_db_ui(self):
-        filename = self.app.data_path + '/geo_tools_db.FlatDB'
-
-        # 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."))
-            return
-
-        try:
-            self.db_tool_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."))
-            return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-
-        self.build_db_ui()
-
-        self.table_widget.setupContextMenu()
-        self.table_widget.addContextMenu(
-            _("Add to DB"), self.on_tool_add, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png"))
-        self.table_widget.addContextMenu(
-            _("Copy from DB"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png"))
-        self.table_widget.addContextMenu(
-            _("Delete from DB"), self.on_tool_delete, icon=QtGui.QIcon(self.app.resource_location + "/delete32.png"))
-
-    def build_db_ui(self):
-        self.ui_disconnect()
-        self.table_widget.setRowCount(len(self.db_tool_dict))
-
-        nr_crt = 0
-
-        for toolid, dict_val in self.db_tool_dict.items():
-            row = nr_crt
-            nr_crt += 1
-
-            t_name = dict_val['name']
-            try:
-                self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val)
-            except Exception as e:
-                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
-            vertical_header = self.table_widget.verticalHeader()
-            vertical_header.hide()
-
-            horizontal_header = self.table_widget.horizontalHeader()
-            horizontal_header.setMinimumSectionSize(10)
-            horizontal_header.setDefaultSectionSize(70)
-
-            self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
-            for x in range(27):
-                self.table_widget.resizeColumnToContents(x)
-
-            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
-            # horizontal_header.setSectionResizeMode(13, QtWidgets.QHeaderView.Fixed)
-
-            horizontal_header.resizeSection(0, 20)
-            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
-            # horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
-
-        self.ui_connect()
-
-    def add_tool_table_line(self, row, name, widget, tooldict):
-        data = tooldict['data']
-
-        nr_crt = row + 1
-        id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
-        # id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        flags = id_item.flags() & ~QtCore.Qt.ItemIsEditable
-        id_item.setFlags(flags)
-        widget.setItem(row, 0, id_item)  # Tool name/id
-
-        tool_name_item = QtWidgets.QTableWidgetItem(name)
-        widget.setItem(row, 1, tool_name_item)
-
-        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_value(float(tooldict['tooldia']))
-        widget.setCellWidget(row, 2, dia_item)
-
-        tool_offset_item = FCComboBox()
-        for item in self.offset_item_options:
-            tool_offset_item.addItem(item)
-        tool_offset_item.set_value(tooldict['offset'])
-        widget.setCellWidget(row, 3, tool_offset_item)
-
-        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_value(float(tooldict['offset_value']))
-        widget.setCellWidget(row, 4, c_offset_item)
-
-        tt_item = FCComboBox()
-        for item in self.type_item_options:
-            tt_item.addItem(item)
-        tt_item.set_value(tooldict['type'])
-        widget.setCellWidget(row, 5, tt_item)
-
-        tshape_item = FCComboBox()
-        for item in self.tool_type_item_options:
-            tshape_item.addItem(item)
-        tshape_item.set_value(tooldict['tool_type'])
-        widget.setCellWidget(row, 6, tshape_item)
-
-        cutz_item = FCDoubleSpinner()
-        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)
-        else:
-            cutz_item.set_range(-9999.9999, -0.0000)
-
-        cutz_item.set_value(float(data['cutz']))
-        widget.setCellWidget(row, 7, cutz_item)
-
-        multidepth_item = FCCheckBox()
-        multidepth_item.set_value(data['multidepth'])
-        widget.setCellWidget(row, 8, multidepth_item)
-
-        # to make the checkbox centered but it can no longer have it's value accessed - needs a fix using findchild()
-        # multidepth_item = QtWidgets.QWidget()
-        # cb = FCCheckBox()
-        # cb.set_value(data['multidepth'])
-        # qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item)
-        # qhboxlayout.addWidget(cb)
-        # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
-        # qhboxlayout.setContentsMargins(0, 0, 0, 0)
-        # widget.setCellWidget(row, 8, multidepth_item)
-
-        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_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_value(float(data['vtipdia']))
-        widget.setCellWidget(row, 10, vtip_dia_item)
-
-        vtip_angle_item = FCDoubleSpinner()
-        vtip_angle_item.set_precision(self.decimals)
-        vtip_angle_item.setSingleStep(0.1)
-        vtip_angle_item.set_range(-360.0, 360.0)
-        vtip_angle_item.set_value(float(data['vtipangle']))
-        widget.setCellWidget(row, 11, vtip_angle_item)
-
-        travelz_item = FCDoubleSpinner()
-        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)
-        else:
-            travelz_item.set_range(0.0000, 9999.9999)
-
-        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_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_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_value(float(data['feedrate_rapid']))
-        widget.setCellWidget(row, 15, frrapids_item)
-
-        spindlespeed_item = FCSpinner()
-        spindlespeed_item.set_range(0, 1000000)
-        spindlespeed_item.set_value(int(data['spindlespeed']))
-        spindlespeed_item.set_step(100)
-        widget.setCellWidget(row, 16, spindlespeed_item)
-
-        dwell_item = FCCheckBox()
-        dwell_item.set_value(data['dwell'])
-        widget.setCellWidget(row, 17, dwell_item)
-
-        dwelltime_item = FCDoubleSpinner()
-        dwelltime_item.set_precision(self.decimals)
-        dwelltime_item.set_range(0.0000, 9999.9999)
-        dwelltime_item.set_value(float(data['dwelltime']))
-        widget.setCellWidget(row, 18, dwelltime_item)
-
-        pp_item = FCComboBox()
-        for item in self.app.preprocessors:
-            pp_item.addItem(item)
-        pp_item.set_value(data['ppname_g'])
-        widget.setCellWidget(row, 19, pp_item)
-
-        ecut_item = FCCheckBox()
-        ecut_item.set_value(data['extracut'])
-        widget.setCellWidget(row, 20, ecut_item)
-
-        ecut_length_item = FCDoubleSpinner()
-        ecut_length_item.set_precision(self.decimals)
-        ecut_length_item.set_range(0.0000, 9999.9999)
-        ecut_length_item.set_value(data['extracut_length'])
-        widget.setCellWidget(row, 21, ecut_length_item)
-
-        toolchange_item = FCCheckBox()
-        toolchange_item.set_value(data['toolchange'])
-        widget.setCellWidget(row, 22, toolchange_item)
-
-        toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '')
-        widget.setItem(row, 23, toolchangexy_item)
-
-        toolchangez_item = FCDoubleSpinner()
-        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)
-        else:
-            toolchangez_item.set_range(0.0000, 9999.9999)
-
-        toolchangez_item.set_value(float(data['toolchangez']))
-        widget.setCellWidget(row, 24, toolchangez_item)
-
-        startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '')
-        widget.setItem(row, 25, startz_item)
-
-        endz_item = FCDoubleSpinner()
-        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)
-        else:
-            endz_item.set_range(0.0000, 9999.9999)
-
-        endz_item.set_value(float(data['endz']))
-        widget.setCellWidget(row, 26, endz_item)
-
-    def on_tool_add(self):
-        """
-        Add a tool in the DB Tool Table
-        :return: None
-        """
-
-        default_data = {}
-        default_data.update({
-            "cutz": float(self.app.defaults["geometry_cutz"]),
-            "multidepth": self.app.defaults["geometry_multidepth"],
-            "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
-            "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
-            "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
-            "travelz": float(self.app.defaults["geometry_travelz"]),
-            "feedrate": float(self.app.defaults["geometry_feedrate"]),
-            "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
-            "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
-            "spindlespeed": self.app.defaults["geometry_spindlespeed"],
-            "dwell": self.app.defaults["geometry_dwell"],
-            "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
-            "ppname_g": self.app.defaults["geometry_ppname_g"],
-            "extracut": self.app.defaults["geometry_extracut"],
-            "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
-            "toolchange": self.app.defaults["geometry_toolchange"],
-            "toolchangexy": self.app.defaults["geometry_toolchangexy"],
-            "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
-            "startz": self.app.defaults["geometry_startz"],
-            "endz": float(self.app.defaults["geometry_endz"])
-        })
-
-        dict_elem = {}
-        dict_elem['name'] = 'new_tool'
-        if type(self.app.defaults["geometry_cnctooldia"]) == float:
-            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
-        else:
-            try:
-                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
-                tools_diameters = [eval(a) for a in tools_string if a != '']
-                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
-            except Exception as e:
-                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
-                return
-
-        dict_elem['offset'] = 'Path'
-        dict_elem['offset_value'] = 0.0
-        dict_elem['type'] = 'Rough'
-        dict_elem['tool_type'] = 'C1'
-        dict_elem['data'] = default_data
-
-        new_toolid = len(self.db_tool_dict) + 1
-        self.db_tool_dict[new_toolid] = deepcopy(dict_elem)
-
-        # add the new entry to the Tools DB table
-        self.build_db_ui()
-        self.callback_on_edited()
-        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
-
-    def on_tool_copy(self):
-        """
-        Copy a selection of Tools in the Tools DB table
-        :return:
-        """
-        new_tool_id = self.table_widget.rowCount() + 1
-        for model_index in self.table_widget.selectionModel().selectedRows():
-            # index = QtCore.QPersistentModelIndex(model_index)
-            old_tool_id = self.table_widget.item(model_index.row(), 0).text()
-            new_tool_id += 1
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(old_tool_id) == int(toolid):
-                    self.db_tool_dict.update({
-                        new_tool_id: deepcopy(dict_val)
-                    })
-
-        self.build_db_ui()
-        self.callback_on_edited()
-        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
-
-    def on_tool_delete(self):
-        """
-        Delete a selection of Tools in the Tools DB table
-        :return:
-        """
-        for model_index in self.table_widget.selectionModel().selectedRows():
-            # index = QtCore.QPersistentModelIndex(model_index)
-            toolname_to_remove = self.table_widget.item(model_index.row(), 0).text()
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(toolname_to_remove) == int(toolid):
-                    # remove from the storage
-                    self.db_tool_dict.pop(toolid, None)
-
-        self.build_db_ui()
-        self.callback_on_edited()
-        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
-
-    def on_export_tools_db_file(self):
-        self.app.report_usage("on_export_tools_db_file")
-        self.app.log.debug("on_export_tools_db_file()")
-
-        date = str(datetime.today()).rpartition('.')[0]
-        date = ''.join(c for c in date if c not in ':-')
-        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),
-                                                             filter=filter__)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            try:
-                f = open(filename, 'w')
-                f.close()
-            except PermissionError:
-                self.app.inform.emit('[WARNING] %s' %
-                                     _("Permission denied, saving not possible.\n"
-                                       "Most likely another app is holding the file open and not accessible."))
-                return
-            except IOError:
-                self.app.log.debug('Creating a new Tools DB file ...')
-                f = open(filename, 'w')
-                f.close()
-            except Exception:
-                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."))
-                return
-
-            # Save update options
-            try:
-                # Save Tools DB in a file
-                try:
-                    with open(filename, "w") as f:
-                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                except Exception as e:
-                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
-                    self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
-
-    def on_import_tools_db_file(self):
-        self.app.report_usage("on_import_tools_db_file")
-        self.app.log.debug("on_import_tools_db_file()")
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-        else:
-            try:
-                with open(filename) as f:
-                    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."))
-                return
-
-            try:
-                self.db_tool_dict = json.loads(tools_in_db)
-            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."))
-                return
-
-            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-            self.build_db_ui()
-            self.callback_on_edited()
-
-    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 + "/geo_tools_db.FlatDB"
-
-        # 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'))
-
-                # Save Tools DB in a file
-                try:
-                    f = open(filename, "w")
-                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                    f.close()
-                except Exception as e:
-                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-
-                if not silent:
-                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
-
-    def ui_connect(self):
-        try:
-            try:
-                self.table_widget.itemChanged.disconnect(self.callback_on_edited)
-            except (TypeError, AttributeError):
-                pass
-            self.table_widget.itemChanged.connect(self.callback_on_edited)
-        except AttributeError:
-            pass
-
-        for row in range(self.table_widget.rowCount()):
-            for col in range(self.table_widget.columnCount()):
-                # ComboBox
-                try:
-                    try:
-                        self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
-                    except (TypeError, AttributeError):
-                        pass
-                    self.table_widget.cellWidget(row, col).currentIndexChanged.connect(self.callback_on_edited)
-                except AttributeError:
-                    pass
-
-                # CheckBox
-                try:
-                    try:
-                        self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
-                    except (TypeError, AttributeError):
-                        pass
-                    self.table_widget.cellWidget(row, col).toggled.connect(self.callback_on_edited)
-                except AttributeError:
-                    pass
-
-                # SpinBox, DoubleSpinBox
-                try:
-                    try:
-                        self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
-                    except (TypeError, AttributeError):
-                        pass
-                    self.table_widget.cellWidget(row, col).valueChanged.connect(self.callback_on_edited)
-                except AttributeError:
-                    pass
-
-    def ui_disconnect(self):
-        try:
-            self.table_widget.itemChanged.disconnect(self.callback_on_edited)
-        except (TypeError, AttributeError):
-            pass
-
-        for row in range(self.table_widget.rowCount()):
-            for col in range(self.table_widget.columnCount()):
-                # ComboBox
-                try:
-                    self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
-                except (TypeError, AttributeError):
-                    pass
-
-                # CheckBox
-                try:
-                    self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
-                except (TypeError, AttributeError):
-                    pass
-
-                # SpinBox, DoubleSpinBox
-                try:
-                    self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
-                except (TypeError, AttributeError):
-                    pass
-
-    def callback_on_edited(self):
-
-        # update the dictionary storage self.db_tool_dict
-        self.db_tool_dict.clear()
-        dict_elem = {}
-        default_data = {}
-
-        for row in range(self.table_widget.rowCount()):
-            new_toolid = row + 1
-            for col in range(self.table_widget.columnCount()):
-                column_header_text = self.table_widget.horizontalHeaderItem(col).text()
-                if column_header_text == _('Tool Name'):
-                    dict_elem['name'] = self.table_widget.item(row, col).text()
-                elif column_header_text == _('Tool Dia'):
-                    dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Tool Offset'):
-                    dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Custom Offset'):
-                    dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Tool Type'):
-                    dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Tool Shape'):
-                    dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value()
-                else:
-                    if column_header_text == _('Cut Z'):
-                        default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('MultiDepth'):
-                        default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('DPP'):
-                        default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('V-Dia'):
-                        default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('V-Angle'):
-                        default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Travel Z'):
-                        default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('FR'):
-                        default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('FR Z'):
-                        default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('FR Rapids'):
-                        default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Spindle Speed'):
-                        default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Dwell'):
-                        default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Dwelltime'):
-                        default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Preprocessor'):
-                        default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('ExtraCut'):
-                        default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _("E-Cut Length"):
-                        default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Toolchange'):
-                        default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Toolchange XY'):
-                        default_data['toolchangexy'] = self.table_widget.item(row, col).text()
-                    elif column_header_text == _('Toolchange Z'):
-                        default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Start Z'):
-                        default_data['startz'] = float(self.table_widget.item(row, col).text()) \
-                            if self.table_widget.item(row, col).text() != '' else None
-                    elif column_header_text == _('End Z'):
-                        default_data['endz'] = self.table_widget.cellWidget(row, col).get_value()
-
-            dict_elem['data'] = default_data
-            self.db_tool_dict.update(
-                {
-                    new_toolid: deepcopy(dict_elem)
-                }
-            )
-
-        self.callback_app()
-
-    def on_tool_requested_from_app(self):
-        if not self.table_widget.selectionModel().selectedRows():
-            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
-            return
-
-        model_index_list = self.table_widget.selectionModel().selectedRows()
-        for model_index in model_index_list:
-            selected_row = model_index.row()
-            tool_uid = selected_row + 1
-            for key in self.db_tool_dict.keys():
-                if str(key) == str(tool_uid):
-                    selected_tool = self.db_tool_dict[key]
-                    self.on_tool_request(tool=selected_tool)
-
-    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"):
-                wdg = self.app.ui.plot_tab_area.widget(idx)
-                wdg.deleteLater()
-                self.app.ui.plot_tab_area.removeTab(idx)
-        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
-
-    def resize_new_tool_table_widget(self, min_size, max_size):
-        """
-        Resize the table widget responsible for adding new tool in the Tool Database
-
-        :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :return:
-        """
-        t_height = self.t_height
-        if max_size > min_size:
-            t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
-
-        self.new_tool_table_widget.setMaximumHeight(t_height)
-
-    def closeEvent(self, QCloseEvent):
-        super().closeEvent(QCloseEvent)
-
-
-class ToolsDB2(QtWidgets.QWidget):
-
-    mark_tools_rows = QtCore.pyqtSignal()
-
-    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
-        super(ToolsDB2, self).__init__(parent)
-
-        self.app = app
-        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"]
-
-        '''
-        dict to hold all the tools in the Tools DB
-        format:
-        {
-            tool_id: {
-                'name': 'new_tool'
-                'tooldia': self.app.defaults["geometry_cnctooldia"]
-                'offset': 'Path'
-                'offset_value': 0.0
-                'type':  _('Rough'),
-                'tool_type': 'C1'
-                'data': dict()
-            }
-        }
-        '''
-        self.db_tool_dict = {}
-
-        # layouts
-        grid_layout = QtWidgets.QGridLayout()
-        grid_layout.setColumnStretch(0, 0)
-        grid_layout.setColumnStretch(1, 1)
-
-        self.setLayout(grid_layout)
-
-        tree_layout = QtWidgets.QVBoxLayout()
-        grid_layout.addLayout(tree_layout, 0, 0)
-
-        self.tree_widget = FCTree(columns=2, header_hidden=False, protected_column=[0])
-        self.tree_widget.setHeaderLabels(["ID", "Tool Name"])
-        self.tree_widget.setIndentation(0)
-        self.tree_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
-        self.tree_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
-
-        # set alternating colors
-        # self.tree_widget.setAlternatingRowColors(True)
-        # p = QtGui.QPalette()
-        # p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(226, 237, 253) )
-        # self.tree_widget.setPalette(p)
-
-        self.tree_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
-        tree_layout.addWidget(self.tree_widget)
-
-        param_hlay = QtWidgets.QHBoxLayout()
-        param_area = QtWidgets.QScrollArea()
-        param_widget = QtWidgets.QWidget()
-        param_widget.setLayout(param_hlay)
-
-        param_area.setWidget(param_widget)
-        param_area.setWidgetResizable(True)
-
-        grid_layout.addWidget(param_area, 0, 1)
-
-        # ###########################################################################
-        # ############## The UI form ################################################
-        # ###########################################################################
-        self.basic_box = QtWidgets.QGroupBox()
-        self.basic_box.setStyleSheet("""
-        QGroupBox
-        {
-            font-size: 16px;
-            font-weight: bold;
-        }
-        """)
-        self.basic_vlay = QtWidgets.QVBoxLayout()
-        self.basic_box.setTitle(_("Basic Geo Parameters"))
-        self.basic_box.setFixedWidth(250)
-
-        self.advanced_box = QtWidgets.QGroupBox()
-        self.advanced_box.setStyleSheet("""
-                QGroupBox
-                {
-                    font-size: 16px;
-                    font-weight: bold;
-                }
-                """)
-        self.advanced_vlay = QtWidgets.QVBoxLayout()
-        self.advanced_box.setTitle(_("Advanced Geo Parameters"))
-        self.advanced_box.setFixedWidth(250)
-
-        self.ncc_box = QtWidgets.QGroupBox()
-        self.ncc_box.setStyleSheet("""
-                        QGroupBox
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.ncc_vlay = QtWidgets.QVBoxLayout()
-        self.ncc_box.setTitle(_("NCC Parameters"))
-        self.ncc_box.setFixedWidth(250)
-
-        self.paint_box = QtWidgets.QGroupBox()
-        self.paint_box.setStyleSheet("""
-                        QGroupBox
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.paint_vlay = QtWidgets.QVBoxLayout()
-        self.paint_box.setTitle(_("Paint Parameters"))
-        self.paint_box.setFixedWidth(250)
-
-        self.basic_box.setLayout(self.basic_vlay)
-        self.advanced_box.setLayout(self.advanced_vlay)
-        self.ncc_box.setLayout(self.ncc_vlay)
-        self.paint_box.setLayout(self.paint_vlay)
-
-        geo_vlay = QtWidgets.QVBoxLayout()
-        geo_vlay.addWidget(self.basic_box)
-        geo_vlay.addWidget(self.advanced_box)
-        geo_vlay.addStretch()
-
-        tools_vlay = QtWidgets.QVBoxLayout()
-        tools_vlay.addWidget(self.ncc_box)
-        tools_vlay.addWidget(self.paint_box)
-        tools_vlay.addStretch()
-
-        param_hlay.addLayout(geo_vlay)
-        param_hlay.addLayout(tools_vlay)
-        param_hlay.addStretch()
-
-        # ###########################################################################
-        # ############### BASIC UI form #############################################
-        # ###########################################################################
-
-        self.grid0 = QtWidgets.QGridLayout()
-        self.basic_vlay.addLayout(self.grid0)
-        self.grid0.setColumnStretch(0, 0)
-        self.grid0.setColumnStretch(1, 1)
-        self.basic_vlay.addStretch()
-
-        # Tool Name
-        self.name_label = QtWidgets.QLabel('<span style="color:red;"><b>%s:</b></span>' % _('Tool Name'))
-        self.name_label.setToolTip(
-            _("Tool name.\n"
-              "This is not used in the app, it's function\n"
-              "is to serve as a note for the user."))
-
-        self.name_entry = FCEntry()
-        self.name_entry.setObjectName('gdb_name')
-
-        self.grid0.addWidget(self.name_label, 0, 0)
-        self.grid0.addWidget(self.name_entry, 0, 1)
-
-        # Tool Dia
-        self.dia_label = QtWidgets.QLabel('%s:' % _('Tool Dia'))
-        self.dia_label.setToolTip(
-            _("Tool Diameter."))
-
-        self.dia_entry = FCDoubleSpinner()
-        self.dia_entry.set_range(-9999.9999, 9999.9999)
-        self.dia_entry.set_precision(self.decimals)
-        self.dia_entry.setObjectName('gdb_dia')
-
-        self.grid0.addWidget(self.dia_label, 1, 0)
-        self.grid0.addWidget(self.dia_entry, 1, 1)
-
-        # Tool Shape
-        self.shape_label = QtWidgets.QLabel('%s:' % _('Tool Shape'))
-        self.shape_label.setToolTip(
-            _("Tool Shape. \n"
-              "Can be:\n"
-              "C1 ... C4 = circular tool with x flutes\n"
-              "B = ball tip milling tool\n"
-              "V = v-shape milling tool"))
-
-        self.shape_combo = FCComboBox()
-        self.shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"])
-        self.shape_combo.setObjectName('gdb_shape')
-
-        self.grid0.addWidget(self.shape_label, 2, 0)
-        self.grid0.addWidget(self.shape_combo, 2, 1)
-
-        # Cut Z
-        self.cutz_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
-        self.cutz_label.setToolTip(
-            _("Cutting Depth.\n"
-              "The depth at which to cut into material."))
-
-        self.cutz_entry = FCDoubleSpinner()
-        self.cutz_entry.set_range(-9999.9999, 9999.9999)
-        self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.setObjectName('gdb_cutz')
-
-        self.grid0.addWidget(self.cutz_label, 4, 0)
-        self.grid0.addWidget(self.cutz_entry, 4, 1)
-
-        # Multi Depth
-        self.multidepth_label = QtWidgets.QLabel('%s:' % _("MultiDepth"))
-        self.multidepth_label.setToolTip(
-            _("Multi Depth.\n"
-              "Selecting this will allow cutting in multiple passes,\n"
-              "each pass adding a DPP parameter depth."))
-
-        self.multidepth_cb = FCCheckBox()
-        self.multidepth_cb.setObjectName('gdb_multidepth')
-
-        self.grid0.addWidget(self.multidepth_label, 5, 0)
-        self.grid0.addWidget(self.multidepth_cb, 5, 1)
-
-        # Depth Per Pass
-        self.dpp_label = QtWidgets.QLabel('%s:' % _("DPP"))
-        self.dpp_label.setToolTip(
-            _("DPP. Depth per Pass.\n"
-              "The value used to cut into material on each pass."))
-
-        self.multidepth_entry = FCDoubleSpinner()
-        self.multidepth_entry.set_range(-9999.9999, 9999.9999)
-        self.multidepth_entry.set_precision(self.decimals)
-        self.multidepth_entry.setObjectName('gdb_multidepth_entry')
-
-        self.grid0.addWidget(self.dpp_label, 7, 0)
-        self.grid0.addWidget(self.multidepth_entry, 7, 1)
-
-        # Travel Z
-        self.travelz_label = QtWidgets.QLabel('%s:' % _("Travel Z"))
-        self.travelz_label.setToolTip(
-            _("Clearance Height.\n"
-              "Height at which the milling bit will travel between cuts,\n"
-              "above the surface of the material, avoiding all fixtures."))
-
-        self.travelz_entry = FCDoubleSpinner()
-        self.travelz_entry.set_range(-9999.9999, 9999.9999)
-        self.travelz_entry.set_precision(self.decimals)
-        self.travelz_entry.setObjectName('gdb_travel')
-
-        self.grid0.addWidget(self.travelz_label, 9, 0)
-        self.grid0.addWidget(self.travelz_entry, 9, 1)
-
-        # Feedrate X-Y
-        self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
-        self.frxy_label.setToolTip(
-            _("Feedrate X-Y. Feedrate\n"
-              "The speed on XY plane used while cutting into material."))
-
-        self.frxy_entry = FCDoubleSpinner()
-        self.frxy_entry.set_range(-999999.9999, 999999.9999)
-        self.frxy_entry.set_precision(self.decimals)
-        self.frxy_entry.setObjectName('gdb_frxy')
-
-        self.grid0.addWidget(self.frxy_label, 12, 0)
-        self.grid0.addWidget(self.frxy_entry, 12, 1)
-
-        # Feedrate Z
-        self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
-        self.frz_label.setToolTip(
-            _("Feedrate Z\n"
-              "The speed on Z plane."))
-
-        self.frz_entry = FCDoubleSpinner()
-        self.frz_entry.set_range(-999999.9999, 999999.9999)
-        self.frz_entry.set_precision(self.decimals)
-        self.frz_entry.setObjectName('gdb_frz')
-
-        self.grid0.addWidget(self.frz_label, 14, 0)
-        self.grid0.addWidget(self.frz_entry, 14, 1)
-
-        # Spindle Spped
-        self.spindle_label = QtWidgets.QLabel('%s:' % _("Spindle Speed"))
-        self.spindle_label.setToolTip(
-            _("Spindle Speed.\n"
-              "If it's left empty it will not be used.\n"
-              "The speed of the spindle in RPM."))
-
-        self.spindle_entry = FCDoubleSpinner()
-        self.spindle_entry.set_range(-999999.9999, 999999.9999)
-        self.spindle_entry.set_precision(self.decimals)
-        self.spindle_entry.setObjectName('gdb_spindle')
-
-        self.grid0.addWidget(self.spindle_label, 15, 0)
-        self.grid0.addWidget(self.spindle_entry, 15, 1)
-
-        # Dwell
-        self.dwell_label = QtWidgets.QLabel('%s:' % _("Dwell"))
-        self.dwell_label.setToolTip(
-            _("Dwell.\n"
-              "Check this if a delay is needed to allow\n"
-              "the spindle motor to reach it's set speed."))
-
-        self.dwell_cb = FCCheckBox()
-        self.dwell_cb.setObjectName('gdb_dwell')
-
-        self.grid0.addWidget(self.dwell_label, 16, 0)
-        self.grid0.addWidget(self.dwell_cb, 16, 1)
-
-        # Dwell Time
-        self.dwelltime_label = QtWidgets.QLabel('%s:' % _("Dwelltime"))
-        self.dwelltime_label.setToolTip(
-            _("Dwell Time.\n"
-              "A delay used to allow the motor spindle reach it's set speed."))
-
-        self.dwelltime_entry = FCDoubleSpinner()
-        self.dwelltime_entry.set_range(0.0000, 9999.9999)
-        self.dwelltime_entry.set_precision(self.decimals)
-        self.dwelltime_entry.setObjectName('gdb_dwelltime')
-
-        self.grid0.addWidget(self.dwelltime_label, 17, 0)
-        self.grid0.addWidget(self.dwelltime_entry, 17, 1)
-
-        # ###########################################################################
-        # ############### ADVANCED UI form ##########################################
-        # ###########################################################################
-
-        self.grid1 = QtWidgets.QGridLayout()
-        self.advanced_vlay.addLayout(self.grid1)
-        self.grid1.setColumnStretch(0, 0)
-        self.grid1.setColumnStretch(1, 1)
-        self.advanced_vlay.addStretch()
-
-        # Tool Type
-        self.type_label = QtWidgets.QLabel('%s:' % _("Tool Type"))
-        self.type_label.setToolTip(
-            _("Tool Type.\n"
-              "Can be:\n"
-              "Iso = isolation cut\n"
-              "Rough = rough cut, low feedrate, multiple passes\n"
-              "Finish = finishing cut, high feedrate"))
-
-        self.type_combo = FCComboBox()
-        self.type_combo.addItems(["Iso", "Rough", "Finish"])
-        self.type_combo.setObjectName('gdb_type')
-
-        self.grid1.addWidget(self.type_label, 0, 0)
-        self.grid1.addWidget(self.type_combo, 0, 1)
-
-        # Tool Offset
-        self.tooloffset_label = QtWidgets.QLabel('%s:' % _('Tool Offset'))
-        self.tooloffset_label.setToolTip(
-            _("Tool Offset.\n"
-              "Can be of a few types:\n"
-              "Path = zero offset\n"
-              "In = offset inside by half of tool diameter\n"
-              "Out = offset outside by half of tool diameter\n"
-              "Custom = custom offset using the Custom Offset value"))
-
-        self.tooloffset_combo = FCComboBox()
-        self.tooloffset_combo.addItems(["Path", "In", "Out", "Custom"])
-        self.tooloffset_combo.setObjectName('gdb_tool_offset')
-
-        self.grid1.addWidget(self.tooloffset_label, 2, 0)
-        self.grid1.addWidget(self.tooloffset_combo, 2, 1)
-
-        # Custom Offset
-        self.custom_offset_label = QtWidgets.QLabel('%s:' % _("Custom Offset"))
-        self.custom_offset_label.setToolTip(
-            _("Custom Offset.\n"
-              "A value to be used as offset from the current path."))
-
-        self.custom_offset_entry = FCDoubleSpinner()
-        self.custom_offset_entry.set_range(-9999.9999, 9999.9999)
-        self.custom_offset_entry.set_precision(self.decimals)
-        self.custom_offset_entry.setObjectName('gdb_custom_offset')
-
-        self.grid1.addWidget(self.custom_offset_label, 5, 0)
-        self.grid1.addWidget(self.custom_offset_entry, 5, 1)
-
-        # V-Dia
-        self.vdia_label = QtWidgets.QLabel('%s:' % _("V-Dia"))
-        self.vdia_label.setToolTip(
-            _("V-Dia.\n"
-              "Diameter of the tip for V-Shape Tools."))
-
-        self.vdia_entry = FCDoubleSpinner()
-        self.vdia_entry.set_range(0.0000, 9999.9999)
-        self.vdia_entry.set_precision(self.decimals)
-        self.vdia_entry.setObjectName('gdb_vdia')
-
-        self.grid1.addWidget(self.vdia_label, 7, 0)
-        self.grid1.addWidget(self.vdia_entry, 7, 1)
-
-        # V-Angle
-        self.vangle_label = QtWidgets.QLabel('%s:' % _("V-Angle"))
-        self.vangle_label.setToolTip(
-            _("V-Agle.\n"
-              "Angle at the tip for the V-Shape Tools."))
-
-        self.vangle_entry = FCDoubleSpinner()
-        self.vangle_entry.set_range(-360.0, 360.0)
-        self.vangle_entry.set_precision(self.decimals)
-        self.vangle_entry.setObjectName('gdb_vangle')
-
-        self.grid1.addWidget(self.vangle_label, 8, 0)
-        self.grid1.addWidget(self.vangle_entry, 8, 1)
-
-        # Feedrate Rapids
-        self.frapids_label = QtWidgets.QLabel('%s:' % _("FR Rapids"))
-        self.frapids_label.setToolTip(
-            _("FR Rapids. Feedrate Rapids\n"
-              "Speed used while moving as fast as possible.\n"
-              "This is used only by some devices that can't use\n"
-              "the G0 g-code command. Mostly 3D printers."))
-
-        self.frapids_entry = FCDoubleSpinner()
-        self.frapids_entry.set_range(0.0000, 9999.9999)
-        self.frapids_entry.set_precision(self.decimals)
-        self.frapids_entry.setObjectName('gdb_frapids')
-
-        self.grid1.addWidget(self.frapids_label, 10, 0)
-        self.grid1.addWidget(self.frapids_entry, 10, 1)
-
-        # Extra Cut
-        self.ecut_label = QtWidgets.QLabel('%s:' % _("ExtraCut"))
-        self.ecut_label.setToolTip(
-            _("Extra Cut.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation."))
-
-        self.ecut_cb = FCCheckBox()
-        self.ecut_cb.setObjectName('gdb_ecut')
-
-        self.grid1.addWidget(self.ecut_label, 12, 0)
-        self.grid1.addWidget(self.ecut_cb, 12, 1)
-
-        # Extra Cut Length
-        self.ecut_length_label = QtWidgets.QLabel('%s:' % _("E-Cut Length"))
-        self.ecut_length_label.setToolTip(
-            _("Extra Cut length.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation. This is the length of\n"
-              "the extra cut."))
-
-        self.ecut_length_entry = FCDoubleSpinner()
-        self.ecut_length_entry.set_range(0.0000, 9999.9999)
-        self.ecut_length_entry.set_precision(self.decimals)
-        self.ecut_length_entry.setObjectName('gdb_ecut_length')
-
-        self.grid1.addWidget(self.ecut_length_label, 13, 0)
-        self.grid1.addWidget(self.ecut_length_entry, 13, 1)
-
-        # ###########################################################################
-        # ############### NCC UI form ###############################################
-        # ###########################################################################
-
-        self.grid2 = QtWidgets.QGridLayout()
-        self.ncc_vlay.addLayout(self.grid2)
-        self.grid2.setColumnStretch(0, 0)
-        self.grid2.setColumnStretch(1, 1)
-        self.ncc_vlay.addStretch()
-
-        # Operation
-        op_label = QtWidgets.QLabel('%s:' % _('Operation'))
-        op_label.setToolTip(
-            _("The 'Operation' can be:\n"
-              "- Isolation -> will ensure that the non-copper clearing is always complete.\n"
-              "If it's not successful then the non-copper clearing will fail, too.\n"
-              "- Clear -> the regular non-copper clearing.")
-        )
-
-        self.op_radio = RadioSet([
-            {"label": _("Clear"), "value": "clear"},
-            {"label": _("Isolation"), "value": "iso"}
-        ], orientation='horizontal', stretch=False)
-        self.op_radio.setObjectName("gdb_n_operation")
-
-        self.grid2.addWidget(op_label, 13, 0)
-        self.grid2.addWidget(self.op_radio, 13, 1)
-
-        # 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"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-
-        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"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-        self.milling_type_radio.setObjectName("gdb_n_milling_type")
-
-        self.grid2.addWidget(self.milling_type_label, 14, 0)
-        self.grid2.addWidget(self.milling_type_radio, 14, 1)
-
-        # Overlap Entry
-        nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
-        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"
-              "Lower values = faster processing, faster execution on CNC.\n"
-              "Higher values = slow processing and slow execution on CNC\n"
-              "due of too many paths.")
-        )
-        self.ncc_overlap_entry = FCDoubleSpinner(suffix='%')
-        self.ncc_overlap_entry.set_precision(self.decimals)
-        self.ncc_overlap_entry.setWrapping(True)
-        self.ncc_overlap_entry.setRange(0.000, 99.9999)
-        self.ncc_overlap_entry.setSingleStep(0.1)
-        self.ncc_overlap_entry.setObjectName("gdb_n_overlap")
-
-        self.grid2.addWidget(nccoverlabel, 15, 0)
-        self.grid2.addWidget(self.ncc_overlap_entry, 15, 1)
-
-        # Margin
-        nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
-        nccmarginlabel.setToolTip(
-            _("Bounding box margin.")
-        )
-        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.setObjectName("gdb_n_margin")
-
-        self.grid2.addWidget(nccmarginlabel, 16, 0)
-        self.grid2.addWidget(self.ncc_margin_entry, 16, 1)
-
-        # Method
-        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
-        methodlabel.setToolTip(
-            _("Algorithm for copper clearing:\n"
-              "- Standard: Fixed step inwards.\n"
-              "- Seed-based: Outwards from seed.\n"
-              "- Line-based: Parallel lines.")
-        )
-
-        self.ncc_method_combo = FCComboBox()
-        self.ncc_method_combo.addItems(
-            [_("Standard"), _("Seed"), _("Lines")]
-        )
-        self.ncc_method_combo.setObjectName("gdb_n_method")
-
-        self.grid2.addWidget(methodlabel, 17, 0)
-        self.grid2.addWidget(self.ncc_method_combo, 17, 1)
-
-        # Connect lines
-        self.ncc_connect_cb = FCCheckBox('%s' % _("Connect"))
-        self.ncc_connect_cb.setObjectName("gdb_n_connect")
-
-        self.ncc_connect_cb.setToolTip(
-            _("Draw lines between resulting\n"
-              "segments to minimize tool lifts.")
-        )
-        self.grid2.addWidget(self.ncc_connect_cb, 18, 0)
-
-        # Contour
-        self.ncc_contour_cb = FCCheckBox('%s' % _("Contour"))
-        self.ncc_contour_cb.setObjectName("gdb_n_contour")
-
-        self.ncc_contour_cb.setToolTip(
-            _("Cut around the perimeter of the polygon\n"
-              "to trim rough edges.")
-        )
-        self.grid2.addWidget(self.ncc_contour_cb, 18, 1)
-
-        # ## NCC Offset choice
-        self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset"))
-        self.ncc_choice_offset_cb.setObjectName("gdb_n_offset")
-
-        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.")
-        )
-        self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0)
-
-        # ## NCC Offset Entry
-        self.ncc_offset_spinner = FCDoubleSpinner()
-        self.ncc_offset_spinner.set_range(0.00, 10.00)
-        self.ncc_offset_spinner.set_precision(4)
-        self.ncc_offset_spinner.setWrapping(True)
-        self.ncc_offset_spinner.setObjectName("gdb_n_offset_value")
-
-        units = self.app.defaults['units'].upper()
-        if units == 'MM':
-            self.ncc_offset_spinner.setSingleStep(0.1)
-        else:
-            self.ncc_offset_spinner.setSingleStep(0.01)
-
-        self.grid2.addWidget(self.ncc_offset_spinner, 19, 1)
-
-        # ###########################################################################
-        # ############### Paint UI form #############################################
-        # ###########################################################################
-
-        self.grid3 = QtWidgets.QGridLayout()
-        self.paint_vlay.addLayout(self.grid3)
-        self.grid3.setColumnStretch(0, 0)
-        self.grid3.setColumnStretch(1, 1)
-        self.paint_vlay.addStretch()
-
-        # Overlap
-        ovlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
-        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"
-              "Lower values = faster processing, faster execution on CNC.\n"
-              "Higher values = slow processing and slow execution on CNC\n"
-              "due of too many paths.")
-        )
-        self.paintoverlap_entry = FCDoubleSpinner(suffix='%')
-        self.paintoverlap_entry.set_precision(3)
-        self.paintoverlap_entry.setWrapping(True)
-        self.paintoverlap_entry.setRange(0.0000, 99.9999)
-        self.paintoverlap_entry.setSingleStep(0.1)
-        self.paintoverlap_entry.setObjectName('gdb_p_overlap')
-
-        self.grid3.addWidget(ovlabel, 1, 0)
-        self.grid3.addWidget(self.paintoverlap_entry, 1, 1)
-
-        # Margin
-        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
-        marginlabel.setToolTip(
-            _("Distance by which to avoid\n"
-              "the edges of the polygon to\n"
-              "be painted.")
-        )
-        self.paintmargin_entry = FCDoubleSpinner()
-        self.paintmargin_entry.set_precision(self.decimals)
-        self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
-        self.paintmargin_entry.setObjectName('gdb_p_margin')
-
-        self.grid3.addWidget(marginlabel, 2, 0)
-        self.grid3.addWidget(self.paintmargin_entry, 2, 1)
-
-        # Method
-        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
-        methodlabel.setToolTip(
-            _("Algorithm for painting:\n"
-              "- Standard: Fixed step inwards.\n"
-              "- Seed-based: Outwards from seed.\n"
-              "- Line-based: Parallel lines.\n"
-              "- Laser-lines: Active only for Gerber objects.\n"
-              "Will create lines that follow the traces.\n"
-              "- Combo: In case of failure a new method will be picked from the above\n"
-              "in the order specified.")
-        )
-
-        self.paintmethod_combo = FCComboBox()
-        self.paintmethod_combo.addItems(
-            [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
-        )
-        idx = self.paintmethod_combo.findText(_("Laser_lines"))
-        self.paintmethod_combo.model().item(idx).setEnabled(False)
-
-        self.paintmethod_combo.setObjectName('gdb_p_method')
-
-        self.grid3.addWidget(methodlabel, 7, 0)
-        self.grid3.addWidget(self.paintmethod_combo, 7, 1)
-
-        # Connect lines
-        self.pathconnect_cb = FCCheckBox('%s' % _("Connect"))
-        self.pathconnect_cb.setObjectName('gdb_p_connect')
-        self.pathconnect_cb.setToolTip(
-            _("Draw lines between resulting\n"
-              "segments to minimize tool lifts.")
-        )
-
-        self.paintcontour_cb = FCCheckBox('%s' % _("Contour"))
-        self.paintcontour_cb.setObjectName('gdb_p_contour')
-        self.paintcontour_cb.setToolTip(
-            _("Cut around the perimeter of the polygon\n"
-              "to trim rough edges.")
-        )
-
-        self.grid3.addWidget(self.pathconnect_cb, 10, 0)
-        self.grid3.addWidget(self.paintcontour_cb, 10, 1)
-
-        # ####################################################################
-        # ####################################################################
-        # GUI for the lower part of the window
-        # ####################################################################
-        # ####################################################################
-
-        new_vlay = QtWidgets.QVBoxLayout()
-        grid_layout.addLayout(new_vlay, 1, 0, 1, 2)
-
-        self.buttons_frame = QtWidgets.QFrame()
-        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
-        new_vlay.addWidget(self.buttons_frame)
-        self.buttons_box = QtWidgets.QHBoxLayout()
-        self.buttons_box.setContentsMargins(0, 0, 0, 0)
-        self.buttons_frame.setLayout(self.buttons_box)
-        self.buttons_frame.show()
-
-        add_entry_btn = FCButton(_("Add Tool in DB"))
-        add_entry_btn.setToolTip(
-            _("Add a new tool in the Tools Database.\n"
-              "It will be used in the Geometry UI.\n"
-              "You can edit it after it is added.")
-        )
-        self.buttons_box.addWidget(add_entry_btn)
-
-        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
-        # add_fct_entry_btn.setToolTip(
-        #     _("Add a new tool in the Tools Database.\n"
-        #       "It will be used in the Paint/NCC Tools UI.\n"
-        #       "You can edit it after it is added.")
-        # )
-        # self.buttons_box.addWidget(add_fct_entry_btn)
-
-        remove_entry_btn = FCButton(_("Delete Tool from DB"))
-        remove_entry_btn.setToolTip(
-            _("Remove a selection of tools in the Tools Database.")
-        )
-        self.buttons_box.addWidget(remove_entry_btn)
-
-        export_db_btn = FCButton(_("Export DB"))
-        export_db_btn.setToolTip(
-            _("Save the Tools Database to a custom text file.")
-        )
-        self.buttons_box.addWidget(export_db_btn)
-
-        import_db_btn = FCButton(_("Import DB"))
-        import_db_btn.setToolTip(
-            _("Load the Tools Database information's from a custom text file.")
-        )
-        self.buttons_box.addWidget(import_db_btn)
-
-        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
-        self.add_tool_from_db.setToolTip(
-            _("Add a new tool in the Tools Table of the\n"
-              "active Geometry object after selecting a tool\n"
-              "in the Tools Database.")
-        )
-        self.add_tool_from_db.hide()
-
-        self.cancel_tool_from_db = FCButton(_("Cancel"))
-        self.cancel_tool_from_db.hide()
-
-        hlay = QtWidgets.QHBoxLayout()
-        tree_layout.addLayout(hlay)
-        hlay.addWidget(self.add_tool_from_db)
-        hlay.addWidget(self.cancel_tool_from_db)
-        hlay.addStretch()
-
-        # ##############################################################################
-        # ##############################################################################
-        # ########## SETUP THE DICTIONARIES THAT HOLD THE WIDGETS #####################
-        # ##############################################################################
-        # ##############################################################################
-
-        self.form_fields = {
-            # Basic
-            "name":             self.name_entry,
-            "tooldia":          self.dia_entry,
-            "tool_type":        self.shape_combo,
-            "cutz":             self.cutz_entry,
-            "multidepth":       self.multidepth_cb,
-            "depthperpass":     self.multidepth_entry,
-            "travelz":          self.travelz_entry,
-            "feedrate":         self.frxy_entry,
-            "feedrate_z":       self.frz_entry,
-            "spindlespeed":     self.spindle_entry,
-            "dwell":            self.dwell_cb,
-            "dwelltime":        self.dwelltime_entry,
-
-            # Advanced
-            "type":             self.type_combo,
-            "offset":           self.tooloffset_combo,
-            "offset_value":     self.custom_offset_entry,
-            "vtipdia":          self.vdia_entry,
-            "vtipangle":        self.vangle_entry,
-            "feedrate_rapid":   self.frapids_entry,
-            "extracut":         self.ecut_cb,
-            "extracut_length":  self.ecut_length_entry,
-
-            # NCC
-            "tools_nccoperation":       self.op_radio,
-            "tools_nccmilling_type":    self.milling_type_radio,
-            "tools_nccoverlap":         self.ncc_overlap_entry,
-            "tools_nccmargin":          self.ncc_margin_entry,
-            "tools_nccmethod":          self.ncc_method_combo,
-            "tools_nccconnect":         self.ncc_connect_cb,
-            "tools_ncccontour":         self.ncc_contour_cb,
-            "tools_ncc_offset_choice":  self.ncc_choice_offset_cb,
-            "tools_ncc_offset_value":   self.ncc_offset_spinner,
-
-            # Paint
-            "tools_paintoverlap":       self.paintoverlap_entry,
-            "tools_paintmargin":        self.paintmargin_entry,
-            "tools_paintmethod":        self.paintmethod_combo,
-            "tools_pathconnect":        self.pathconnect_cb,
-            "tools_paintcontour":       self.paintcontour_cb,
-        }
-
-        self.name2option = {
-            # Basic
-            "gdb_name":             "name",
-            "gdb_dia":              "tooldia",
-            "gdb_shape":            "tool_type",
-            "gdb_cutz":             "cutz",
-            "gdb_multidepth":       "multidepth",
-            "gdb_multidepth_entry": "depthperpass",
-            "gdb_travel":           "travelz",
-            "gdb_frxy":             "feedrate",
-            "gdb_frz":              "feedrate_z",
-            "gdb_spindle":          "spindlespeed",
-            "gdb_dwell":            "dwell",
-            "gdb_dwelltime":        "dwelltime",
-
-            # Advanced
-            "gdb_type":             "type",
-            "gdb_tool_offset":      "offset",
-            "gdb_custom_offset":    "offset_value",
-            "gdb_vdia":             "vtipdia",
-            "gdb_vangle":           "vtipangle",
-            "gdb_frapids":          "feedrate_rapid",
-            "gdb_ecut":             "extracut",
-            "gdb_ecut_length":      "extracut_length",
-
-            # NCC
-            "gdb_n_operation":      "tools_nccoperation",
-            "gdb_n_overlap":        "tools_nccoverlap",
-            "gdb_n_margin":         "tools_nccmargin",
-            "gdb_n_method":         "tools_nccmethod",
-            "gdb_n_connect":        "tools_nccconnect",
-            "gdb_n_contour":        "tools_ncccontour",
-            "gdb_n_offset":         "tools_ncc_offset_choice",
-            "gdb_n_offset_value":   "tools_ncc_offset_value",
-            "gdb_n_milling_type":   "tools_nccmilling_type",
-
-            # Paint
-            'gdb_p_overlap':        "tools_paintoverlap",
-            'gdb_p_margin':         "tools_paintmargin",
-            'gdb_p_method':         "tools_paintmethod",
-            'gdb_p_connect':        "tools_pathconnect",
-            'gdb_p_contour':        "tools_paintcontour",
-        }
-
-        self.current_toolid = None
-
-        # variable to show if double clicking and item will trigger adding a tool from DB
-        self.ok_to_add = False
-
-        # ##############################################################################
-        # ######################## SIGNALS #############################################
-        # ##############################################################################
-
-        add_entry_btn.clicked.connect(self.on_tool_add)
-        remove_entry_btn.clicked.connect(self.on_tool_delete)
-        export_db_btn.clicked.connect(self.on_export_tools_db_file)
-        import_db_btn.clicked.connect(self.on_import_tools_db_file)
-        # closebtn.clicked.connect(self.accept)
-
-        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
-        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
-
-        # self.tree_widget.selectionModel().selectionChanged.connect(self.on_list_selection_change)
-        self.tree_widget.currentItemChanged.connect(self.on_list_selection_change)
-        self.tree_widget.itemChanged.connect(self.on_list_item_edited)
-        self.tree_widget.customContextMenuRequested.connect(self.on_menu_request)
-
-        self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked)
-
-        self.setup_db_ui()
-
-    def on_menu_request(self, pos):
-
-        menu = QtWidgets.QMenu()
-        add_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/plus16.png'), _("Add to DB"))
-        add_tool.triggered.connect(self.on_tool_add)
-
-        copy_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/copy16.png'), _("Copy from DB"))
-        copy_tool.triggered.connect(self.on_tool_copy)
-
-        delete_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete from DB"))
-        delete_tool.triggered.connect(self.on_tool_delete)
-
-        # tree_item = self.tree_widget.itemAt(pos)
-        menu.exec(self.tree_widget.viewport().mapToGlobal(pos))
-
-    def on_item_double_clicked(self, item, column):
-        if column == 0 and self.ok_to_add is True:
-            self.ok_to_add = False
-            self.on_tool_requested_from_app()
-
-    def on_list_selection_change(self, current, previous):
-        # for idx in current.indexes():
-        #     print(idx.data())
-        # print(current.text(0))
-        self.current_toolid = int(current.text(0))
-
-        self.storage_to_form(self.db_tool_dict[current.text(0)])
-
-    def on_list_item_edited(self, item, column):
-        if column == 0:
-            return
-
-        self.name_entry.set_value(item.text(1))
-
-    def storage_to_form(self, dict_storage):
-        for form_key in self.form_fields:
-            for storage_key in dict_storage:
-                if form_key == storage_key:
-                    try:
-                        self.form_fields[form_key].set_value(dict_storage[form_key])
-                    except Exception as e:
-                        print(str(e))
-                if storage_key == 'data':
-                    for data_key in dict_storage[storage_key]:
-                        if form_key == data_key:
-                            try:
-                                self.form_fields[form_key].set_value(dict_storage['data'][data_key])
-                            except Exception as e:
-                                print(str(e))
-
-    def form_to_storage(self, tool):
-        self.blockSignals(True)
-
-        widget_changed = self.sender()
-        wdg_objname = widget_changed.objectName()
-        option_changed = self.name2option[wdg_objname]
-
-        tooluid_item = int(tool)
-
-        for tooluid_key, tooluid_val in self.db_tool_dict.items():
-            if int(tooluid_key) == tooluid_item:
-                new_option_value = self.form_fields[option_changed].get_value()
-                if option_changed in tooluid_val:
-                    tooluid_val[option_changed] = new_option_value
-                if option_changed in tooluid_val['data']:
-                    tooluid_val['data'][option_changed] = new_option_value
-
-        self.blockSignals(False)
-
-    def setup_db_ui(self):
-        filename = self.app.data_path + '/geo_tools_db.FlatDB'
-
-        # 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."))
-            return
-
-        try:
-            self.db_tool_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."))
-            return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-
-        self.build_db_ui()
-
-    def build_db_ui(self):
-        self.ui_disconnect()
-        nr_crt = 0
-
-        parent = self.tree_widget
-        self.tree_widget.blockSignals(True)
-        self.tree_widget.clear()
-        self.tree_widget.blockSignals(False)
-
-        for toolid, dict_val in self.db_tool_dict.items():
-            row = nr_crt
-            nr_crt += 1
-
-            t_name = dict_val['name']
-            try:
-                # self.add_tool_table_line(row, name=t_name, tooldict=dict_val)
-                self.tree_widget.blockSignals(True)
-                try:
-                    self.tree_widget.addParentEditable(parent=parent, title=[str(row+1), t_name], editable=True)
-                except Exception as e:
-                    print('FlatCAMCoomn.ToolDB2.build_db_ui() -> ', str(e))
-                self.tree_widget.blockSignals(False)
-            except Exception as e:
-                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
-
-        if self.current_toolid is None or self.current_toolid < 1:
-            if self.db_tool_dict:
-                self.storage_to_form(self.db_tool_dict['1'])
-
-                # Enable GUI
-                self.basic_box.setEnabled(True)
-                self.advanced_box.setEnabled(True)
-                self.ncc_box.setEnabled(True)
-                self.paint_box.setEnabled(True)
-
-                self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
-                # self.tree_widget.setFocus()
-
-            else:
-                # Disable GUI
-                self.basic_box.setEnabled(False)
-                self.advanced_box.setEnabled(False)
-                self.ncc_box.setEnabled(False)
-                self.paint_box.setEnabled(False)
-        else:
-            self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
-
-        self.ui_connect()
-
-    def on_tool_add(self):
-        """
-        Add a tool in the DB Tool Table
-        :return: None
-        """
-
-        default_data = {}
-        default_data.update({
-            "plot":             True,
-            "cutz":             float(self.app.defaults["geometry_cutz"]),
-            "multidepth":       self.app.defaults["geometry_multidepth"],
-            "depthperpass":     float(self.app.defaults["geometry_depthperpass"]),
-            "vtipdia":          float(self.app.defaults["geometry_vtipdia"]),
-            "vtipangle":        float(self.app.defaults["geometry_vtipangle"]),
-            "travelz":          float(self.app.defaults["geometry_travelz"]),
-            "feedrate":         float(self.app.defaults["geometry_feedrate"]),
-            "feedrate_z":       float(self.app.defaults["geometry_feedrate_z"]),
-            "feedrate_rapid":   float(self.app.defaults["geometry_feedrate_rapid"]),
-            "spindlespeed":     self.app.defaults["geometry_spindlespeed"],
-            "dwell":            self.app.defaults["geometry_dwell"],
-            "dwelltime":        float(self.app.defaults["geometry_dwelltime"]),
-            "ppname_g":         self.app.defaults["geometry_ppname_g"],
-            "extracut":         self.app.defaults["geometry_extracut"],
-            "extracut_length":  float(self.app.defaults["geometry_extracut_length"]),
-            "toolchange":       self.app.defaults["geometry_toolchange"],
-            "toolchangexy":     self.app.defaults["geometry_toolchangexy"],
-            "toolchangez":      float(self.app.defaults["geometry_toolchangez"]),
-            "startz":           self.app.defaults["geometry_startz"],
-            "endz":             float(self.app.defaults["geometry_endz"]),
-
-            # NCC
-            "tools_nccoperation":       self.app.defaults["tools_nccoperation"],
-            "tools_nccmilling_type":    self.app.defaults["tools_nccmilling_type"],
-            "tools_nccoverlap":         float(self.app.defaults["tools_nccoverlap"]),
-            "tools_nccmargin":          float(self.app.defaults["tools_nccmargin"]),
-            "tools_nccmethod":          self.app.defaults["tools_nccmethod"],
-            "tools_nccconnect":         self.app.defaults["tools_nccconnect"],
-            "tools_ncccontour":         self.app.defaults["tools_ncccontour"],
-            "tools_ncc_offset_choice":  self.app.defaults["tools_ncc_offset_choice"],
-            "tools_ncc_offset_value":   float(self.app.defaults["tools_ncc_offset_value"]),
-
-            # Paint
-            "tools_paintoverlap":       float(self.app.defaults["tools_paintoverlap"]),
-            "tools_paintmargin":        float(self.app.defaults["tools_paintmargin"]),
-            "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
-            "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
-            "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
-        })
-
-        dict_elem = {}
-        dict_elem['name'] = 'new_tool'
-        if type(self.app.defaults["geometry_cnctooldia"]) == float:
-            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
-        else:
-            try:
-                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
-                tools_diameters = [eval(a) for a in tools_string if a != '']
-                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
-            except Exception as e:
-                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
-                return
-
-        dict_elem['offset'] = 'Path'
-        dict_elem['offset_value'] = 0.0
-        dict_elem['type'] = 'Rough'
-        dict_elem['tool_type'] = 'C1'
-        dict_elem['data'] = default_data
-
-        new_toolid = len(self.db_tool_dict) + 1
-        self.db_tool_dict[str(new_toolid)] = deepcopy(dict_elem)
-
-        # add the new entry to the Tools DB table
-        self.update_storage()
-        self.build_db_ui()
-        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
-
-    def on_tool_copy(self):
-        """
-        Copy a selection of Tools in the Tools DB table
-        :return:
-        """
-        new_tool_id = len(self.db_tool_dict)
-        for item in self.tree_widget.selectedItems():
-            old_tool_id = item.data(0, QtCore.Qt.DisplayRole)
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(old_tool_id) == int(toolid):
-                    new_tool_id += 1
-                    new_key = str(new_tool_id)
-
-                    self.db_tool_dict.update({
-                        new_key: deepcopy(dict_val)
-                    })
-
-        self.current_toolid = new_tool_id
-
-        self.update_storage()
-        self.build_db_ui()
-        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
-
-    def on_tool_delete(self):
-        """
-        Delete a selection of Tools in the Tools DB table
-        :return:
-        """
-        for item in self.tree_widget.selectedItems():
-            toolname_to_remove = item.data(0, QtCore.Qt.DisplayRole)
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(toolname_to_remove) == int(toolid):
-                    # remove from the storage
-                    self.db_tool_dict.pop(toolid, None)
-
-        self.current_toolid -= 1
-
-        self.update_storage()
-        self.build_db_ui()
-        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
-
-    def on_export_tools_db_file(self):
-        self.app.report_usage("on_export_tools_db_file")
-        self.app.log.debug("on_export_tools_db_file()")
-
-        date = str(datetime.today()).rpartition('.')[0]
-        date = ''.join(c for c in date if c not in ':-')
-        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),
-                                                             filter=filter__)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            try:
-                f = open(filename, 'w')
-                f.close()
-            except PermissionError:
-                self.app.inform.emit('[WARNING] %s' %
-                                     _("Permission denied, saving not possible.\n"
-                                       "Most likely another app is holding the file open and not accessible."))
-                return
-            except IOError:
-                self.app.log.debug('Creating a new Tools DB file ...')
-                f = open(filename, 'w')
-                f.close()
-            except Exception:
-                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."))
-                return
-
-            # Save update options
-            try:
-                # Save Tools DB in a file
-                try:
-                    with open(filename, "w") as f:
-                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                except Exception as e:
-                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
-                    self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
-
-    def on_import_tools_db_file(self):
-        self.app.report_usage("on_import_tools_db_file")
-        self.app.log.debug("on_import_tools_db_file()")
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-        else:
-            try:
-                with open(filename) as f:
-                    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."))
-                return
-
-            try:
-                self.db_tool_dict = json.loads(tools_in_db)
-            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."))
-                return
-
-            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-            self.build_db_ui()
-            self.update_storage()
-
-    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 + "/geo_tools_db.FlatDB"
-
-        # 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'))
-
-                # Save Tools DB in a file
-                try:
-                    f = open(filename, "w")
-                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                    f.close()
-                except Exception as e:
-                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-
-                if not silent:
-                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
-
-    def ui_connect(self):
-        # make sure that we don't make multiple connections to the widgets
-        self.ui_disconnect()
-
-        self.name_entry.editingFinished.connect(self.update_tree_name)
-
-        for key in self.form_fields:
-            wdg = self.form_fields[key]
-
-            # FCEntry
-            if isinstance(wdg, FCEntry):
-                wdg.textChanged.connect(self.update_storage)
-
-            # ComboBox
-            if isinstance(wdg, FCComboBox):
-                wdg.currentIndexChanged.connect(self.update_storage)
-
-            # CheckBox
-            if isinstance(wdg, FCCheckBox):
-                wdg.toggled.connect(self.update_storage)
-
-            # FCRadio
-            if isinstance(wdg, RadioSet):
-                wdg.activated_custom.connect(self.update_storage)
-
-            # SpinBox, DoubleSpinBox
-            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
-                wdg.valueChanged.connect(self.update_storage)
-
-    def ui_disconnect(self):
-        try:
-            self.name_entry.editingFinished.disconnect(self.update_tree_name)
-        except (TypeError, AttributeError):
-            pass
-
-        for key in self.form_fields:
-            wdg = self.form_fields[key]
-
-            # FCEntry
-            if isinstance(wdg, FCEntry):
-                try:
-                    wdg.textChanged.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # ComboBox
-            if isinstance(wdg, FCComboBox):
-                try:
-                    wdg.currentIndexChanged.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # CheckBox
-            if isinstance(wdg, FCCheckBox):
-                try:
-                    wdg.toggled.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # FCRadio
-            if isinstance(wdg, RadioSet):
-                try:
-                    wdg.activated_custom.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # SpinBox, DoubleSpinBox
-            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
-                try:
-                    wdg.valueChanged.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-    def update_tree_name(self):
-        val = self.name_entry.get_value()
-
-        item = self.tree_widget.currentItem()
-        # I'm setting the value for the second column (designated by 1) because first column holds the ID
-        # and second column holds the Name (this behavior is set in the build_ui method)
-        item.setData(1, QtCore.Qt.DisplayRole, val)
-
-    def update_storage(self):
-        """
-        Update the dictionary that is the storage of the tools 'database'
-        :return:
-        """
-        tool_id = str(self.current_toolid)
-
-        wdg = self.sender()
-        if wdg is None:
-            return
-
-        wdg_name = wdg.objectName()
-
-        try:
-            val = wdg.get_value()
-        except AttributeError:
-            return
-
-        if wdg_name == "gdb_name":
-            self.db_tool_dict[tool_id]['name'] = val
-        elif wdg_name == "gdb_dia":
-            self.db_tool_dict[tool_id]['tooldia'] = val
-        elif wdg_name == "gdb_tool_offset":
-            self.db_tool_dict[tool_id]['offset'] = val
-        elif wdg_name == "gdb_custom_offset":
-            self.db_tool_dict[tool_id]['offset_value'] = val
-        elif wdg_name == "gdb_type":
-            self.db_tool_dict[tool_id]['type'] = val
-        elif wdg_name == "gdb_shape":
-            self.db_tool_dict[tool_id]['tool_type'] = val
-        else:
-            if wdg_name == "gdb_cutz":
-                self.db_tool_dict[tool_id]['data']['cutz'] = val
-            elif wdg_name == "gdb_multidepth":
-                self.db_tool_dict[tool_id]['data']['multidepth'] = val
-            elif wdg_name == "gdb_multidepth_entry":
-                self.db_tool_dict[tool_id]['data']['depthperpass'] = val
-
-            elif wdg_name == "gdb_travel":
-                self.db_tool_dict[tool_id]['data']['travelz'] = val
-            elif wdg_name == "gdb_frxy":
-                self.db_tool_dict[tool_id]['data']['feedrate'] = val
-            elif wdg_name == "gdb_frz":
-                self.db_tool_dict[tool_id]['data']['feedrate_z'] = val
-            elif wdg_name == "gdb_spindle":
-                self.db_tool_dict[tool_id]['data']['spindlespeed'] = val
-            elif wdg_name == "gdb_dwell":
-                self.db_tool_dict[tool_id]['data']['dwell'] = val
-            elif wdg_name == "gdb_dwelltime":
-                self.db_tool_dict[tool_id]['data']['dwelltime'] = val
-
-            elif wdg_name == "gdb_vdia":
-                self.db_tool_dict[tool_id]['data']['vtipdia'] = val
-            elif wdg_name == "gdb_vangle":
-                self.db_tool_dict[tool_id]['data']['vtipangle'] = val
-            elif wdg_name == "gdb_frapids":
-                self.db_tool_dict[tool_id]['data']['feedrate_rapid'] = val
-            elif wdg_name == "gdb_ecut":
-                self.db_tool_dict[tool_id]['data']['extracut'] = val
-            elif wdg_name == "gdb_ecut_length":
-                self.db_tool_dict[tool_id]['data']['extracut_length'] = val
-
-            # NCC Tool
-            elif wdg_name == "gdb_n_operation":
-                self.db_tool_dict[tool_id]['data']['tools_nccoperation'] = val
-            elif wdg_name == "gdb_n_overlap":
-                self.db_tool_dict[tool_id]['data']['tools_nccoverlap'] = val
-            elif wdg_name == "gdb_n_margin":
-                self.db_tool_dict[tool_id]['data']['tools_nccmargin'] = val
-            elif wdg_name == "gdb_n_method":
-                self.db_tool_dict[tool_id]['data']['tools_nccmethod'] = val
-            elif wdg_name == "gdb_n_connect":
-                self.db_tool_dict[tool_id]['data']['tools_nccconnect'] = val
-            elif wdg_name == "gdb_n_contour":
-                self.db_tool_dict[tool_id]['data']['tools_ncccontour'] = val
-            elif wdg_name == "gdb_n_offset":
-                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_choice'] = val
-            elif wdg_name == "gdb_n_offset_value":
-                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_value'] = val
-            elif wdg_name == "gdb_n_milling_type":
-                self.db_tool_dict[tool_id]['data']['tools_nccmilling_type'] = val
-
-            # Paint Tool
-            elif wdg_name == "gdb_p_overlap":
-                self.db_tool_dict[tool_id]['data']['tools_paintoverlap'] = val
-            elif wdg_name == "gdb_p_margin":
-                self.db_tool_dict[tool_id]['data']['tools_paintmargin'] = val
-            elif wdg_name == "gdb_p_method":
-                self.db_tool_dict[tool_id]['data']['tools_paintmethod'] = val
-            elif wdg_name == "gdb_p_connect":
-                self.db_tool_dict[tool_id]['data']['tools_pathconnect'] = val
-            elif wdg_name == "gdb_p_contour":
-                self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
-
-        self.callback_app()
-
-    def on_tool_requested_from_app(self):
-        if not self.tree_widget.selectedItems():
-            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
-            return
-
-        for item in self.tree_widget.selectedItems():
-            tool_uid = item.data(0, QtCore.Qt.DisplayRole)
-
-            for key in self.db_tool_dict.keys():
-                if str(key) == str(tool_uid):
-                    selected_tool = self.db_tool_dict[key]
-                    self.on_tool_request(tool=selected_tool)
-
-    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"):
-                wdg = self.app.ui.plot_tab_area.widget(idx)
-                wdg.deleteLater()
-                self.app.ui.plot_tab_area.removeTab(idx)
-        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
-
-    def resize_new_tool_table_widget(self, min_size, max_size):
-        """
-        Resize the table widget responsible for adding new tool in the Tool Database
-
-        :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :return:
-        """
-        t_height = self.t_height
-        if max_size > min_size:
-            t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
-
-        self.new_tool_table_widget.setMaximumHeight(t_height)
-
-    def closeEvent(self, QCloseEvent):
-        super().closeEvent(QCloseEvent)
-
-
 def color_variant(hex_color, bright_factor=1):
     """
     Takes a color in HEX format #FF00FF and produces a lighter or darker variant

+ 2399 - 0
FlatCAMDB.py

@@ -0,0 +1,2399 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
+    FCTree, RadioSet, FCFileSaveDialog
+from camlib import to_dict
+
+import sys
+import json
+
+from copy import deepcopy
+from datetime import datetime
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class ToolsDB(QtWidgets.QWidget):
+
+    mark_tools_rows = QtCore.pyqtSignal()
+
+    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
+        super(ToolsDB, self).__init__(parent)
+
+        self.app = app
+        self.decimals = 4
+        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"]
+
+        '''
+        dict to hold all the tools in the Tools DB
+        format:
+        {
+            tool_id: {
+                'name': 'new_tool'
+                'tooldia': self.app.defaults["geometry_cnctooldia"]
+                'offset': 'Path'
+                'offset_value': 0.0
+                'type':  _('Rough'),
+                'tool_type': 'C1'
+                'data': dict()
+            }
+        }
+        '''
+        self.db_tool_dict = {}
+
+        # layouts
+        layout = QtWidgets.QVBoxLayout()
+        self.setLayout(layout)
+
+        table_hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(table_hlay)
+
+        self.table_widget = FCTable(drag_drop=True)
+        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        table_hlay.addWidget(self.table_widget)
+
+        # set the number of columns and the headers tool tips
+        self.configure_table()
+
+        # pal = QtGui.QPalette()
+        # pal.setColor(QtGui.QPalette.Background, Qt.white)
+
+        # New Bookmark
+        new_vlay = QtWidgets.QVBoxLayout()
+        layout.addLayout(new_vlay)
+
+        # new_tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Tool"))
+        # new_vlay.addWidget(new_tool_lbl, alignment=QtCore.Qt.AlignBottom)
+
+        self.buttons_frame = QtWidgets.QFrame()
+        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(self.buttons_frame)
+        self.buttons_box = QtWidgets.QHBoxLayout()
+        self.buttons_box.setContentsMargins(0, 0, 0, 0)
+        self.buttons_frame.setLayout(self.buttons_box)
+        self.buttons_frame.show()
+
+        add_entry_btn = FCButton(_("Add Geometry Tool in DB"))
+        add_entry_btn.setToolTip(
+            _("Add a new tool in the Tools Database.\n"
+              "It will be used in the Geometry UI.\n"
+              "You can edit it after it is added.")
+        )
+        self.buttons_box.addWidget(add_entry_btn)
+
+        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
+        # add_fct_entry_btn.setToolTip(
+        #     _("Add a new tool in the Tools Database.\n"
+        #       "It will be used in the Paint/NCC Tools UI.\n"
+        #       "You can edit it after it is added.")
+        # )
+        # self.buttons_box.addWidget(add_fct_entry_btn)
+
+        remove_entry_btn = FCButton(_("Delete Tool from DB"))
+        remove_entry_btn.setToolTip(
+            _("Remove a selection of tools in the Tools Database.")
+        )
+        self.buttons_box.addWidget(remove_entry_btn)
+
+        export_db_btn = FCButton(_("Export DB"))
+        export_db_btn.setToolTip(
+            _("Save the Tools Database to a custom text file.")
+        )
+        self.buttons_box.addWidget(export_db_btn)
+
+        import_db_btn = FCButton(_("Import DB"))
+        import_db_btn.setToolTip(
+            _("Load the Tools Database information's from a custom text file.")
+        )
+        self.buttons_box.addWidget(import_db_btn)
+
+        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
+        self.add_tool_from_db.setToolTip(
+            _("Add a new tool in the Tools Table of the\n"
+              "active Geometry object after selecting a tool\n"
+              "in the Tools Database.")
+        )
+        self.add_tool_from_db.hide()
+
+        self.cancel_tool_from_db = FCButton(_("Cancel"))
+        self.cancel_tool_from_db.hide()
+
+        hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(hlay)
+        hlay.addWidget(self.add_tool_from_db)
+        hlay.addWidget(self.cancel_tool_from_db)
+        hlay.addStretch()
+
+        # ##############################################################################
+        # ######################## SIGNALS #############################################
+        # ##############################################################################
+
+        add_entry_btn.clicked.connect(self.on_tool_add)
+        remove_entry_btn.clicked.connect(self.on_tool_delete)
+        export_db_btn.clicked.connect(self.on_export_tools_db_file)
+        import_db_btn.clicked.connect(self.on_import_tools_db_file)
+        # closebtn.clicked.connect(self.accept)
+
+        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
+        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
+
+        self.setup_db_ui()
+
+    def configure_table(self):
+        self.table_widget.setColumnCount(27)
+        # self.table_widget.setColumnWidth(0, 20)
+        self.table_widget.setHorizontalHeaderLabels(
+            [
+                '#',
+                _("Tool Name"),
+                _("Tool Dia"),
+                _("Tool Offset"),
+                _("Custom Offset"),
+                _("Tool Type"),
+                _("Tool Shape"),
+                _("Cut Z"),
+                _("MultiDepth"),
+                _("DPP"),
+                _("V-Dia"),
+                _("V-Angle"),
+                _("Travel Z"),
+                _("FR"),
+                _("FR Z"),
+                _("FR Rapids"),
+                _("Spindle Speed"),
+                _("Dwell"),
+                _("Dwelltime"),
+                _("Preprocessor"),
+                _("ExtraCut"),
+                _("E-Cut Length"),
+                _("Toolchange"),
+                _("Toolchange XY"),
+                _("Toolchange Z"),
+                _("Start Z"),
+                _("End Z"),
+            ]
+        )
+        self.table_widget.horizontalHeaderItem(0).setToolTip(
+            _("Tool Index."))
+        self.table_widget.horizontalHeaderItem(1).setToolTip(
+            _("Tool name.\n"
+              "This is not used in the app, it's function\n"
+              "is to serve as a note for the user."))
+        self.table_widget.horizontalHeaderItem(2).setToolTip(
+            _("Tool Diameter."))
+        self.table_widget.horizontalHeaderItem(3).setToolTip(
+            _("Tool Offset.\n"
+              "Can be of a few types:\n"
+              "Path = zero offset\n"
+              "In = offset inside by half of tool diameter\n"
+              "Out = offset outside by half of tool diameter\n"
+              "Custom = custom offset using the Custom Offset value"))
+        self.table_widget.horizontalHeaderItem(4).setToolTip(
+            _("Custom Offset.\n"
+              "A value to be used as offset from the current path."))
+        self.table_widget.horizontalHeaderItem(5).setToolTip(
+            _("Tool Type.\n"
+              "Can be:\n"
+              "Iso = isolation cut\n"
+              "Rough = rough cut, low feedrate, multiple passes\n"
+              "Finish = finishing cut, high feedrate"))
+        self.table_widget.horizontalHeaderItem(6).setToolTip(
+            _("Tool Shape. \n"
+              "Can be:\n"
+              "C1 ... C4 = circular tool with x flutes\n"
+              "B = ball tip milling tool\n"
+              "V = v-shape milling tool"))
+        self.table_widget.horizontalHeaderItem(7).setToolTip(
+            _("Cutting Depth.\n"
+              "The depth at which to cut into material."))
+        self.table_widget.horizontalHeaderItem(8).setToolTip(
+            _("Multi Depth.\n"
+              "Selecting this will allow cutting in multiple passes,\n"
+              "each pass adding a DPP parameter depth."))
+        self.table_widget.horizontalHeaderItem(9).setToolTip(
+            _("DPP. Depth per Pass.\n"
+              "The value used to cut into material on each pass."))
+        self.table_widget.horizontalHeaderItem(10).setToolTip(
+            _("V-Dia.\n"
+              "Diameter of the tip for V-Shape Tools."))
+        self.table_widget.horizontalHeaderItem(11).setToolTip(
+            _("V-Agle.\n"
+              "Angle at the tip for the V-Shape Tools."))
+        self.table_widget.horizontalHeaderItem(12).setToolTip(
+            _("Clearance Height.\n"
+              "Height at which the milling bit will travel between cuts,\n"
+              "above the surface of the material, avoiding all fixtures."))
+        self.table_widget.horizontalHeaderItem(13).setToolTip(
+            _("FR. Feedrate\n"
+              "The speed on XY plane used while cutting into material."))
+        self.table_widget.horizontalHeaderItem(14).setToolTip(
+            _("FR Z. Feedrate Z\n"
+              "The speed on Z plane."))
+        self.table_widget.horizontalHeaderItem(15).setToolTip(
+            _("FR Rapids. Feedrate Rapids\n"
+              "Speed used while moving as fast as possible.\n"
+              "This is used only by some devices that can't use\n"
+              "the G0 g-code command. Mostly 3D printers."))
+        self.table_widget.horizontalHeaderItem(16).setToolTip(
+            _("Spindle Speed.\n"
+              "If it's left empty it will not be used.\n"
+              "The speed of the spindle in RPM."))
+        self.table_widget.horizontalHeaderItem(17).setToolTip(
+            _("Dwell.\n"
+              "Check this if a delay is needed to allow\n"
+              "the spindle motor to reach it's set speed."))
+        self.table_widget.horizontalHeaderItem(18).setToolTip(
+            _("Dwell Time.\n"
+              "A delay used to allow the motor spindle reach it's set speed."))
+        self.table_widget.horizontalHeaderItem(19).setToolTip(
+            _("Preprocessor.\n"
+              "A selection of files that will alter the generated G-code\n"
+              "to fit for a number of use cases."))
+        self.table_widget.horizontalHeaderItem(20).setToolTip(
+            _("Extra Cut.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation."))
+        self.table_widget.horizontalHeaderItem(21).setToolTip(
+            _("Extra Cut length.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation. This is the length of\n"
+              "the extra cut."))
+        self.table_widget.horizontalHeaderItem(22).setToolTip(
+            _("Toolchange.\n"
+              "It will create a toolchange event.\n"
+              "The kind of toolchange is determined by\n"
+              "the preprocessor file."))
+        self.table_widget.horizontalHeaderItem(23).setToolTip(
+            _("Toolchange XY.\n"
+              "A set of coordinates in the format (x, y).\n"
+              "Will determine the cartesian position of the point\n"
+              "where the tool change event take place."))
+        self.table_widget.horizontalHeaderItem(24).setToolTip(
+            _("Toolchange Z.\n"
+              "The position on Z plane where the tool change event take place."))
+        self.table_widget.horizontalHeaderItem(25).setToolTip(
+            _("Start Z.\n"
+              "If it's left empty it will not be used.\n"
+              "A position on Z plane to move immediately after job start."))
+        self.table_widget.horizontalHeaderItem(26).setToolTip(
+            _("End Z.\n"
+              "A position on Z plane to move immediately after job stop."))
+
+    def setup_db_ui(self):
+        filename = self.app.data_path + '/geo_tools_db.FlatDB'
+
+        # 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."))
+            return
+
+        try:
+            self.db_tool_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."))
+            return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+
+        self.build_db_ui()
+
+        self.table_widget.setupContextMenu()
+        self.table_widget.addContextMenu(
+            _("Add to DB"), self.on_tool_add, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png"))
+        self.table_widget.addContextMenu(
+            _("Copy from DB"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png"))
+        self.table_widget.addContextMenu(
+            _("Delete from DB"), self.on_tool_delete, icon=QtGui.QIcon(self.app.resource_location + "/delete32.png"))
+
+    def build_db_ui(self):
+        self.ui_disconnect()
+        self.table_widget.setRowCount(len(self.db_tool_dict))
+
+        nr_crt = 0
+
+        for toolid, dict_val in self.db_tool_dict.items():
+            row = nr_crt
+            nr_crt += 1
+
+            t_name = dict_val['name']
+            try:
+                self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val)
+            except Exception as e:
+                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
+            vertical_header = self.table_widget.verticalHeader()
+            vertical_header.hide()
+
+            horizontal_header = self.table_widget.horizontalHeader()
+            horizontal_header.setMinimumSectionSize(10)
+            horizontal_header.setDefaultSectionSize(70)
+
+            self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
+            for x in range(27):
+                self.table_widget.resizeColumnToContents(x)
+
+            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+            # horizontal_header.setSectionResizeMode(13, QtWidgets.QHeaderView.Fixed)
+
+            horizontal_header.resizeSection(0, 20)
+            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
+            # horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+
+        self.ui_connect()
+
+    def add_tool_table_line(self, row, name, widget, tooldict):
+        data = tooldict['data']
+
+        nr_crt = row + 1
+        id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
+        # id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        flags = id_item.flags() & ~QtCore.Qt.ItemIsEditable
+        id_item.setFlags(flags)
+        widget.setItem(row, 0, id_item)  # Tool name/id
+
+        tool_name_item = QtWidgets.QTableWidgetItem(name)
+        widget.setItem(row, 1, tool_name_item)
+
+        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_value(float(tooldict['tooldia']))
+        widget.setCellWidget(row, 2, dia_item)
+
+        tool_offset_item = FCComboBox()
+        for item in self.offset_item_options:
+            tool_offset_item.addItem(item)
+        tool_offset_item.set_value(tooldict['offset'])
+        widget.setCellWidget(row, 3, tool_offset_item)
+
+        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_value(float(tooldict['offset_value']))
+        widget.setCellWidget(row, 4, c_offset_item)
+
+        tt_item = FCComboBox()
+        for item in self.type_item_options:
+            tt_item.addItem(item)
+        tt_item.set_value(tooldict['type'])
+        widget.setCellWidget(row, 5, tt_item)
+
+        tshape_item = FCComboBox()
+        for item in self.tool_type_item_options:
+            tshape_item.addItem(item)
+        tshape_item.set_value(tooldict['tool_type'])
+        widget.setCellWidget(row, 6, tshape_item)
+
+        cutz_item = FCDoubleSpinner()
+        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)
+        else:
+            cutz_item.set_range(-9999.9999, -0.0000)
+
+        cutz_item.set_value(float(data['cutz']))
+        widget.setCellWidget(row, 7, cutz_item)
+
+        multidepth_item = FCCheckBox()
+        multidepth_item.set_value(data['multidepth'])
+        widget.setCellWidget(row, 8, multidepth_item)
+
+        # to make the checkbox centered but it can no longer have it's value accessed - needs a fix using findchild()
+        # multidepth_item = QtWidgets.QWidget()
+        # cb = FCCheckBox()
+        # cb.set_value(data['multidepth'])
+        # qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item)
+        # qhboxlayout.addWidget(cb)
+        # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
+        # qhboxlayout.setContentsMargins(0, 0, 0, 0)
+        # widget.setCellWidget(row, 8, multidepth_item)
+
+        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_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_value(float(data['vtipdia']))
+        widget.setCellWidget(row, 10, vtip_dia_item)
+
+        vtip_angle_item = FCDoubleSpinner()
+        vtip_angle_item.set_precision(self.decimals)
+        vtip_angle_item.setSingleStep(0.1)
+        vtip_angle_item.set_range(-360.0, 360.0)
+        vtip_angle_item.set_value(float(data['vtipangle']))
+        widget.setCellWidget(row, 11, vtip_angle_item)
+
+        travelz_item = FCDoubleSpinner()
+        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)
+        else:
+            travelz_item.set_range(0.0000, 9999.9999)
+
+        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_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_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_value(float(data['feedrate_rapid']))
+        widget.setCellWidget(row, 15, frrapids_item)
+
+        spindlespeed_item = FCSpinner()
+        spindlespeed_item.set_range(0, 1000000)
+        spindlespeed_item.set_value(int(data['spindlespeed']))
+        spindlespeed_item.set_step(100)
+        widget.setCellWidget(row, 16, spindlespeed_item)
+
+        dwell_item = FCCheckBox()
+        dwell_item.set_value(data['dwell'])
+        widget.setCellWidget(row, 17, dwell_item)
+
+        dwelltime_item = FCDoubleSpinner()
+        dwelltime_item.set_precision(self.decimals)
+        dwelltime_item.set_range(0.0000, 9999.9999)
+        dwelltime_item.set_value(float(data['dwelltime']))
+        widget.setCellWidget(row, 18, dwelltime_item)
+
+        pp_item = FCComboBox()
+        for item in self.app.preprocessors:
+            pp_item.addItem(item)
+        pp_item.set_value(data['ppname_g'])
+        widget.setCellWidget(row, 19, pp_item)
+
+        ecut_item = FCCheckBox()
+        ecut_item.set_value(data['extracut'])
+        widget.setCellWidget(row, 20, ecut_item)
+
+        ecut_length_item = FCDoubleSpinner()
+        ecut_length_item.set_precision(self.decimals)
+        ecut_length_item.set_range(0.0000, 9999.9999)
+        ecut_length_item.set_value(data['extracut_length'])
+        widget.setCellWidget(row, 21, ecut_length_item)
+
+        toolchange_item = FCCheckBox()
+        toolchange_item.set_value(data['toolchange'])
+        widget.setCellWidget(row, 22, toolchange_item)
+
+        toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '')
+        widget.setItem(row, 23, toolchangexy_item)
+
+        toolchangez_item = FCDoubleSpinner()
+        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)
+        else:
+            toolchangez_item.set_range(0.0000, 9999.9999)
+
+        toolchangez_item.set_value(float(data['toolchangez']))
+        widget.setCellWidget(row, 24, toolchangez_item)
+
+        startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '')
+        widget.setItem(row, 25, startz_item)
+
+        endz_item = FCDoubleSpinner()
+        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)
+        else:
+            endz_item.set_range(0.0000, 9999.9999)
+
+        endz_item.set_value(float(data['endz']))
+        widget.setCellWidget(row, 26, endz_item)
+
+    def on_tool_add(self):
+        """
+        Add a tool in the DB Tool Table
+        :return: None
+        """
+
+        default_data = {}
+        default_data.update({
+            "cutz": float(self.app.defaults["geometry_cutz"]),
+            "multidepth": self.app.defaults["geometry_multidepth"],
+            "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
+            "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
+            "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
+            "travelz": float(self.app.defaults["geometry_travelz"]),
+            "feedrate": float(self.app.defaults["geometry_feedrate"]),
+            "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
+            "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
+            "spindlespeed": self.app.defaults["geometry_spindlespeed"],
+            "dwell": self.app.defaults["geometry_dwell"],
+            "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
+            "ppname_g": self.app.defaults["geometry_ppname_g"],
+            "extracut": self.app.defaults["geometry_extracut"],
+            "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
+            "toolchange": self.app.defaults["geometry_toolchange"],
+            "toolchangexy": self.app.defaults["geometry_toolchangexy"],
+            "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
+            "startz": self.app.defaults["geometry_startz"],
+            "endz": float(self.app.defaults["geometry_endz"])
+        })
+
+        dict_elem = {}
+        dict_elem['name'] = 'new_tool'
+        if type(self.app.defaults["geometry_cnctooldia"]) == float:
+            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
+        else:
+            try:
+                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
+                tools_diameters = [eval(a) for a in tools_string if a != '']
+                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
+            except Exception as e:
+                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
+                return
+
+        dict_elem['offset'] = 'Path'
+        dict_elem['offset_value'] = 0.0
+        dict_elem['type'] = 'Rough'
+        dict_elem['tool_type'] = 'C1'
+        dict_elem['data'] = default_data
+
+        new_toolid = len(self.db_tool_dict) + 1
+        self.db_tool_dict[new_toolid] = deepcopy(dict_elem)
+
+        # add the new entry to the Tools DB table
+        self.build_db_ui()
+        self.callback_on_edited()
+        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
+
+    def on_tool_copy(self):
+        """
+        Copy a selection of Tools in the Tools DB table
+        :return:
+        """
+        new_tool_id = self.table_widget.rowCount() + 1
+        for model_index in self.table_widget.selectionModel().selectedRows():
+            # index = QtCore.QPersistentModelIndex(model_index)
+            old_tool_id = self.table_widget.item(model_index.row(), 0).text()
+            new_tool_id += 1
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(old_tool_id) == int(toolid):
+                    self.db_tool_dict.update({
+                        new_tool_id: deepcopy(dict_val)
+                    })
+
+        self.build_db_ui()
+        self.callback_on_edited()
+        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
+
+    def on_tool_delete(self):
+        """
+        Delete a selection of Tools in the Tools DB table
+        :return:
+        """
+        for model_index in self.table_widget.selectionModel().selectedRows():
+            # index = QtCore.QPersistentModelIndex(model_index)
+            toolname_to_remove = self.table_widget.item(model_index.row(), 0).text()
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(toolname_to_remove) == int(toolid):
+                    # remove from the storage
+                    self.db_tool_dict.pop(toolid, None)
+
+        self.build_db_ui()
+        self.callback_on_edited()
+        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
+
+    def on_export_tools_db_file(self):
+        self.app.report_usage("on_export_tools_db_file")
+        self.app.log.debug("on_export_tools_db_file()")
+
+        date = str(datetime.today()).rpartition('.')[0]
+        date = ''.join(c for c in date if c not in ':-')
+        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),
+                                                           filter=filter__)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            try:
+                f = open(filename, 'w')
+                f.close()
+            except PermissionError:
+                self.app.inform.emit('[WARNING] %s' %
+                                     _("Permission denied, saving not possible.\n"
+                                       "Most likely another app is holding the file open and not accessible."))
+                return
+            except IOError:
+                self.app.log.debug('Creating a new Tools DB file ...')
+                f = open(filename, 'w')
+                f.close()
+            except Exception:
+                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."))
+                return
+
+            # Save update options
+            try:
+                # Save Tools DB in a file
+                try:
+                    with open(filename, "w") as f:
+                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                except Exception as e:
+                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
+
+    def on_import_tools_db_file(self):
+        self.app.report_usage("on_import_tools_db_file")
+        self.app.log.debug("on_import_tools_db_file()")
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+        else:
+            try:
+                with open(filename) as f:
+                    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."))
+                return
+
+            try:
+                self.db_tool_dict = json.loads(tools_in_db)
+            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."))
+                return
+
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+            self.build_db_ui()
+            self.callback_on_edited()
+
+    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 + "/geo_tools_db.FlatDB"
+
+        # 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'))
+
+                # Save Tools DB in a file
+                try:
+                    f = open(filename, "w")
+                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                    f.close()
+                except Exception as e:
+                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+
+                if not silent:
+                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
+
+    def ui_connect(self):
+        try:
+            try:
+                self.table_widget.itemChanged.disconnect(self.callback_on_edited)
+            except (TypeError, AttributeError):
+                pass
+            self.table_widget.itemChanged.connect(self.callback_on_edited)
+        except AttributeError:
+            pass
+
+        for row in range(self.table_widget.rowCount()):
+            for col in range(self.table_widget.columnCount()):
+                # ComboBox
+                try:
+                    try:
+                        self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
+                    except (TypeError, AttributeError):
+                        pass
+                    self.table_widget.cellWidget(row, col).currentIndexChanged.connect(self.callback_on_edited)
+                except AttributeError:
+                    pass
+
+                # CheckBox
+                try:
+                    try:
+                        self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
+                    except (TypeError, AttributeError):
+                        pass
+                    self.table_widget.cellWidget(row, col).toggled.connect(self.callback_on_edited)
+                except AttributeError:
+                    pass
+
+                # SpinBox, DoubleSpinBox
+                try:
+                    try:
+                        self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
+                    except (TypeError, AttributeError):
+                        pass
+                    self.table_widget.cellWidget(row, col).valueChanged.connect(self.callback_on_edited)
+                except AttributeError:
+                    pass
+
+    def ui_disconnect(self):
+        try:
+            self.table_widget.itemChanged.disconnect(self.callback_on_edited)
+        except (TypeError, AttributeError):
+            pass
+
+        for row in range(self.table_widget.rowCount()):
+            for col in range(self.table_widget.columnCount()):
+                # ComboBox
+                try:
+                    self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
+                except (TypeError, AttributeError):
+                    pass
+
+                # CheckBox
+                try:
+                    self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
+                except (TypeError, AttributeError):
+                    pass
+
+                # SpinBox, DoubleSpinBox
+                try:
+                    self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
+                except (TypeError, AttributeError):
+                    pass
+
+    def callback_on_edited(self):
+
+        # update the dictionary storage self.db_tool_dict
+        self.db_tool_dict.clear()
+        dict_elem = {}
+        default_data = {}
+
+        for row in range(self.table_widget.rowCount()):
+            new_toolid = row + 1
+            for col in range(self.table_widget.columnCount()):
+                column_header_text = self.table_widget.horizontalHeaderItem(col).text()
+                if column_header_text == _('Tool Name'):
+                    dict_elem['name'] = self.table_widget.item(row, col).text()
+                elif column_header_text == _('Tool Dia'):
+                    dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Tool Offset'):
+                    dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Custom Offset'):
+                    dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Tool Type'):
+                    dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Tool Shape'):
+                    dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value()
+                else:
+                    if column_header_text == _('Cut Z'):
+                        default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('MultiDepth'):
+                        default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('DPP'):
+                        default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('V-Dia'):
+                        default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('V-Angle'):
+                        default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Travel Z'):
+                        default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('FR'):
+                        default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('FR Z'):
+                        default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('FR Rapids'):
+                        default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Spindle Speed'):
+                        default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Dwell'):
+                        default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Dwelltime'):
+                        default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Preprocessor'):
+                        default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('ExtraCut'):
+                        default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _("E-Cut Length"):
+                        default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Toolchange'):
+                        default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Toolchange XY'):
+                        default_data['toolchangexy'] = self.table_widget.item(row, col).text()
+                    elif column_header_text == _('Toolchange Z'):
+                        default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Start Z'):
+                        default_data['startz'] = float(self.table_widget.item(row, col).text()) \
+                            if self.table_widget.item(row, col).text() != '' else None
+                    elif column_header_text == _('End Z'):
+                        default_data['endz'] = self.table_widget.cellWidget(row, col).get_value()
+
+            dict_elem['data'] = default_data
+            self.db_tool_dict.update(
+                {
+                    new_toolid: deepcopy(dict_elem)
+                }
+            )
+
+        self.callback_app()
+
+    def on_tool_requested_from_app(self):
+        if not self.table_widget.selectionModel().selectedRows():
+            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
+            return
+
+        model_index_list = self.table_widget.selectionModel().selectedRows()
+        for model_index in model_index_list:
+            selected_row = model_index.row()
+            tool_uid = selected_row + 1
+            for key in self.db_tool_dict.keys():
+                if str(key) == str(tool_uid):
+                    selected_tool = self.db_tool_dict[key]
+                    self.on_tool_request(tool=selected_tool)
+
+    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"):
+                wdg = self.app.ui.plot_tab_area.widget(idx)
+                wdg.deleteLater()
+                self.app.ui.plot_tab_area.removeTab(idx)
+        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
+
+    def resize_new_tool_table_widget(self, min_size, max_size):
+        """
+        Resize the table widget responsible for adding new tool in the Tool Database
+
+        :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+        :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+        :return:
+        """
+        t_height = self.t_height
+        if max_size > min_size:
+            t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
+
+        self.new_tool_table_widget.setMaximumHeight(t_height)
+
+    def closeEvent(self, QCloseEvent):
+        super().closeEvent(QCloseEvent)
+
+
+class ToolsDB2(QtWidgets.QWidget):
+
+    mark_tools_rows = QtCore.pyqtSignal()
+
+    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
+        super(ToolsDB2, self).__init__(parent)
+
+        self.app = app
+        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"]
+
+        '''
+        dict to hold all the tools in the Tools DB
+        format:
+        {
+            tool_id: {
+                'name': 'new_tool'
+                'tooldia': self.app.defaults["geometry_cnctooldia"]
+                'offset': 'Path'
+                'offset_value': 0.0
+                'type':  _('Rough'),
+                'tool_type': 'C1'
+                'data': dict()
+            }
+        }
+        '''
+        self.db_tool_dict = {}
+
+        # layouts
+        grid_layout = QtWidgets.QGridLayout()
+        grid_layout.setColumnStretch(0, 0)
+        grid_layout.setColumnStretch(1, 1)
+
+        self.setLayout(grid_layout)
+
+        tree_layout = QtWidgets.QVBoxLayout()
+        grid_layout.addLayout(tree_layout, 0, 0)
+
+        self.tree_widget = FCTree(columns=2, header_hidden=False, protected_column=[0])
+        self.tree_widget.setHeaderLabels(["ID", "Tool Name"])
+        self.tree_widget.setIndentation(0)
+        self.tree_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+        self.tree_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+
+        # set alternating colors
+        # self.tree_widget.setAlternatingRowColors(True)
+        # p = QtGui.QPalette()
+        # p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(226, 237, 253) )
+        # self.tree_widget.setPalette(p)
+
+        self.tree_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
+        tree_layout.addWidget(self.tree_widget)
+
+        param_hlay = QtWidgets.QHBoxLayout()
+        param_area = QtWidgets.QScrollArea()
+        param_widget = QtWidgets.QWidget()
+        param_widget.setLayout(param_hlay)
+
+        param_area.setWidget(param_widget)
+        param_area.setWidgetResizable(True)
+
+        grid_layout.addWidget(param_area, 0, 1)
+
+        # ###########################################################################
+        # ############## The UI form ################################################
+        # ###########################################################################
+        self.basic_box = QtWidgets.QGroupBox()
+        self.basic_box.setStyleSheet("""
+        QGroupBox
+        {
+            font-size: 16px;
+            font-weight: bold;
+        }
+        """)
+        self.basic_vlay = QtWidgets.QVBoxLayout()
+        self.basic_box.setTitle(_("Basic Geo Parameters"))
+        self.basic_box.setFixedWidth(250)
+
+        self.advanced_box = QtWidgets.QGroupBox()
+        self.advanced_box.setStyleSheet("""
+                QGroupBox
+                {
+                    font-size: 16px;
+                    font-weight: bold;
+                }
+                """)
+        self.advanced_vlay = QtWidgets.QVBoxLayout()
+        self.advanced_box.setTitle(_("Advanced Geo Parameters"))
+        self.advanced_box.setFixedWidth(250)
+
+        self.ncc_box = QtWidgets.QGroupBox()
+        self.ncc_box.setStyleSheet("""
+                        QGroupBox
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.ncc_vlay = QtWidgets.QVBoxLayout()
+        self.ncc_box.setTitle(_("NCC Parameters"))
+        self.ncc_box.setFixedWidth(250)
+
+        self.paint_box = QtWidgets.QGroupBox()
+        self.paint_box.setStyleSheet("""
+                        QGroupBox
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.paint_vlay = QtWidgets.QVBoxLayout()
+        self.paint_box.setTitle(_("Paint Parameters"))
+        self.paint_box.setFixedWidth(250)
+
+        self.basic_box.setLayout(self.basic_vlay)
+        self.advanced_box.setLayout(self.advanced_vlay)
+        self.ncc_box.setLayout(self.ncc_vlay)
+        self.paint_box.setLayout(self.paint_vlay)
+
+        geo_vlay = QtWidgets.QVBoxLayout()
+        geo_vlay.addWidget(self.basic_box)
+        geo_vlay.addWidget(self.advanced_box)
+        geo_vlay.addStretch()
+
+        tools_vlay = QtWidgets.QVBoxLayout()
+        tools_vlay.addWidget(self.ncc_box)
+        tools_vlay.addWidget(self.paint_box)
+        tools_vlay.addStretch()
+
+        param_hlay.addLayout(geo_vlay)
+        param_hlay.addLayout(tools_vlay)
+        param_hlay.addStretch()
+
+        # ###########################################################################
+        # ############### BASIC UI form #############################################
+        # ###########################################################################
+
+        self.grid0 = QtWidgets.QGridLayout()
+        self.basic_vlay.addLayout(self.grid0)
+        self.grid0.setColumnStretch(0, 0)
+        self.grid0.setColumnStretch(1, 1)
+        self.basic_vlay.addStretch()
+
+        # Tool Name
+        self.name_label = QtWidgets.QLabel('<span style="color:red;"><b>%s:</b></span>' % _('Tool Name'))
+        self.name_label.setToolTip(
+            _("Tool name.\n"
+              "This is not used in the app, it's function\n"
+              "is to serve as a note for the user."))
+
+        self.name_entry = FCEntry()
+        self.name_entry.setObjectName('gdb_name')
+
+        self.grid0.addWidget(self.name_label, 0, 0)
+        self.grid0.addWidget(self.name_entry, 0, 1)
+
+        # Tool Dia
+        self.dia_label = QtWidgets.QLabel('%s:' % _('Tool Dia'))
+        self.dia_label.setToolTip(
+            _("Tool Diameter."))
+
+        self.dia_entry = FCDoubleSpinner()
+        self.dia_entry.set_range(-9999.9999, 9999.9999)
+        self.dia_entry.set_precision(self.decimals)
+        self.dia_entry.setObjectName('gdb_dia')
+
+        self.grid0.addWidget(self.dia_label, 1, 0)
+        self.grid0.addWidget(self.dia_entry, 1, 1)
+
+        # Tool Shape
+        self.shape_label = QtWidgets.QLabel('%s:' % _('Tool Shape'))
+        self.shape_label.setToolTip(
+            _("Tool Shape. \n"
+              "Can be:\n"
+              "C1 ... C4 = circular tool with x flutes\n"
+              "B = ball tip milling tool\n"
+              "V = v-shape milling tool"))
+
+        self.shape_combo = FCComboBox()
+        self.shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"])
+        self.shape_combo.setObjectName('gdb_shape')
+
+        self.grid0.addWidget(self.shape_label, 2, 0)
+        self.grid0.addWidget(self.shape_combo, 2, 1)
+
+        # Cut Z
+        self.cutz_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
+        self.cutz_label.setToolTip(
+            _("Cutting Depth.\n"
+              "The depth at which to cut into material."))
+
+        self.cutz_entry = FCDoubleSpinner()
+        self.cutz_entry.set_range(-9999.9999, 9999.9999)
+        self.cutz_entry.set_precision(self.decimals)
+        self.cutz_entry.setObjectName('gdb_cutz')
+
+        self.grid0.addWidget(self.cutz_label, 4, 0)
+        self.grid0.addWidget(self.cutz_entry, 4, 1)
+
+        # Multi Depth
+        self.multidepth_label = QtWidgets.QLabel('%s:' % _("MultiDepth"))
+        self.multidepth_label.setToolTip(
+            _("Multi Depth.\n"
+              "Selecting this will allow cutting in multiple passes,\n"
+              "each pass adding a DPP parameter depth."))
+
+        self.multidepth_cb = FCCheckBox()
+        self.multidepth_cb.setObjectName('gdb_multidepth')
+
+        self.grid0.addWidget(self.multidepth_label, 5, 0)
+        self.grid0.addWidget(self.multidepth_cb, 5, 1)
+
+        # Depth Per Pass
+        self.dpp_label = QtWidgets.QLabel('%s:' % _("DPP"))
+        self.dpp_label.setToolTip(
+            _("DPP. Depth per Pass.\n"
+              "The value used to cut into material on each pass."))
+
+        self.multidepth_entry = FCDoubleSpinner()
+        self.multidepth_entry.set_range(-9999.9999, 9999.9999)
+        self.multidepth_entry.set_precision(self.decimals)
+        self.multidepth_entry.setObjectName('gdb_multidepth_entry')
+
+        self.grid0.addWidget(self.dpp_label, 7, 0)
+        self.grid0.addWidget(self.multidepth_entry, 7, 1)
+
+        # Travel Z
+        self.travelz_label = QtWidgets.QLabel('%s:' % _("Travel Z"))
+        self.travelz_label.setToolTip(
+            _("Clearance Height.\n"
+              "Height at which the milling bit will travel between cuts,\n"
+              "above the surface of the material, avoiding all fixtures."))
+
+        self.travelz_entry = FCDoubleSpinner()
+        self.travelz_entry.set_range(-9999.9999, 9999.9999)
+        self.travelz_entry.set_precision(self.decimals)
+        self.travelz_entry.setObjectName('gdb_travel')
+
+        self.grid0.addWidget(self.travelz_label, 9, 0)
+        self.grid0.addWidget(self.travelz_entry, 9, 1)
+
+        # Feedrate X-Y
+        self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
+        self.frxy_label.setToolTip(
+            _("Feedrate X-Y. Feedrate\n"
+              "The speed on XY plane used while cutting into material."))
+
+        self.frxy_entry = FCDoubleSpinner()
+        self.frxy_entry.set_range(-999999.9999, 999999.9999)
+        self.frxy_entry.set_precision(self.decimals)
+        self.frxy_entry.setObjectName('gdb_frxy')
+
+        self.grid0.addWidget(self.frxy_label, 12, 0)
+        self.grid0.addWidget(self.frxy_entry, 12, 1)
+
+        # Feedrate Z
+        self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
+        self.frz_label.setToolTip(
+            _("Feedrate Z\n"
+              "The speed on Z plane."))
+
+        self.frz_entry = FCDoubleSpinner()
+        self.frz_entry.set_range(-999999.9999, 999999.9999)
+        self.frz_entry.set_precision(self.decimals)
+        self.frz_entry.setObjectName('gdb_frz')
+
+        self.grid0.addWidget(self.frz_label, 14, 0)
+        self.grid0.addWidget(self.frz_entry, 14, 1)
+
+        # Spindle Spped
+        self.spindle_label = QtWidgets.QLabel('%s:' % _("Spindle Speed"))
+        self.spindle_label.setToolTip(
+            _("Spindle Speed.\n"
+              "If it's left empty it will not be used.\n"
+              "The speed of the spindle in RPM."))
+
+        self.spindle_entry = FCDoubleSpinner()
+        self.spindle_entry.set_range(-999999.9999, 999999.9999)
+        self.spindle_entry.set_precision(self.decimals)
+        self.spindle_entry.setObjectName('gdb_spindle')
+
+        self.grid0.addWidget(self.spindle_label, 15, 0)
+        self.grid0.addWidget(self.spindle_entry, 15, 1)
+
+        # Dwell
+        self.dwell_label = QtWidgets.QLabel('%s:' % _("Dwell"))
+        self.dwell_label.setToolTip(
+            _("Dwell.\n"
+              "Check this if a delay is needed to allow\n"
+              "the spindle motor to reach it's set speed."))
+
+        self.dwell_cb = FCCheckBox()
+        self.dwell_cb.setObjectName('gdb_dwell')
+
+        self.grid0.addWidget(self.dwell_label, 16, 0)
+        self.grid0.addWidget(self.dwell_cb, 16, 1)
+
+        # Dwell Time
+        self.dwelltime_label = QtWidgets.QLabel('%s:' % _("Dwelltime"))
+        self.dwelltime_label.setToolTip(
+            _("Dwell Time.\n"
+              "A delay used to allow the motor spindle reach it's set speed."))
+
+        self.dwelltime_entry = FCDoubleSpinner()
+        self.dwelltime_entry.set_range(0.0000, 9999.9999)
+        self.dwelltime_entry.set_precision(self.decimals)
+        self.dwelltime_entry.setObjectName('gdb_dwelltime')
+
+        self.grid0.addWidget(self.dwelltime_label, 17, 0)
+        self.grid0.addWidget(self.dwelltime_entry, 17, 1)
+
+        # ###########################################################################
+        # ############### ADVANCED UI form ##########################################
+        # ###########################################################################
+
+        self.grid1 = QtWidgets.QGridLayout()
+        self.advanced_vlay.addLayout(self.grid1)
+        self.grid1.setColumnStretch(0, 0)
+        self.grid1.setColumnStretch(1, 1)
+        self.advanced_vlay.addStretch()
+
+        # Tool Type
+        self.type_label = QtWidgets.QLabel('%s:' % _("Tool Type"))
+        self.type_label.setToolTip(
+            _("Tool Type.\n"
+              "Can be:\n"
+              "Iso = isolation cut\n"
+              "Rough = rough cut, low feedrate, multiple passes\n"
+              "Finish = finishing cut, high feedrate"))
+
+        self.type_combo = FCComboBox()
+        self.type_combo.addItems(["Iso", "Rough", "Finish"])
+        self.type_combo.setObjectName('gdb_type')
+
+        self.grid1.addWidget(self.type_label, 0, 0)
+        self.grid1.addWidget(self.type_combo, 0, 1)
+
+        # Tool Offset
+        self.tooloffset_label = QtWidgets.QLabel('%s:' % _('Tool Offset'))
+        self.tooloffset_label.setToolTip(
+            _("Tool Offset.\n"
+              "Can be of a few types:\n"
+              "Path = zero offset\n"
+              "In = offset inside by half of tool diameter\n"
+              "Out = offset outside by half of tool diameter\n"
+              "Custom = custom offset using the Custom Offset value"))
+
+        self.tooloffset_combo = FCComboBox()
+        self.tooloffset_combo.addItems(["Path", "In", "Out", "Custom"])
+        self.tooloffset_combo.setObjectName('gdb_tool_offset')
+
+        self.grid1.addWidget(self.tooloffset_label, 2, 0)
+        self.grid1.addWidget(self.tooloffset_combo, 2, 1)
+
+        # Custom Offset
+        self.custom_offset_label = QtWidgets.QLabel('%s:' % _("Custom Offset"))
+        self.custom_offset_label.setToolTip(
+            _("Custom Offset.\n"
+              "A value to be used as offset from the current path."))
+
+        self.custom_offset_entry = FCDoubleSpinner()
+        self.custom_offset_entry.set_range(-9999.9999, 9999.9999)
+        self.custom_offset_entry.set_precision(self.decimals)
+        self.custom_offset_entry.setObjectName('gdb_custom_offset')
+
+        self.grid1.addWidget(self.custom_offset_label, 5, 0)
+        self.grid1.addWidget(self.custom_offset_entry, 5, 1)
+
+        # V-Dia
+        self.vdia_label = QtWidgets.QLabel('%s:' % _("V-Dia"))
+        self.vdia_label.setToolTip(
+            _("V-Dia.\n"
+              "Diameter of the tip for V-Shape Tools."))
+
+        self.vdia_entry = FCDoubleSpinner()
+        self.vdia_entry.set_range(0.0000, 9999.9999)
+        self.vdia_entry.set_precision(self.decimals)
+        self.vdia_entry.setObjectName('gdb_vdia')
+
+        self.grid1.addWidget(self.vdia_label, 7, 0)
+        self.grid1.addWidget(self.vdia_entry, 7, 1)
+
+        # V-Angle
+        self.vangle_label = QtWidgets.QLabel('%s:' % _("V-Angle"))
+        self.vangle_label.setToolTip(
+            _("V-Agle.\n"
+              "Angle at the tip for the V-Shape Tools."))
+
+        self.vangle_entry = FCDoubleSpinner()
+        self.vangle_entry.set_range(-360.0, 360.0)
+        self.vangle_entry.set_precision(self.decimals)
+        self.vangle_entry.setObjectName('gdb_vangle')
+
+        self.grid1.addWidget(self.vangle_label, 8, 0)
+        self.grid1.addWidget(self.vangle_entry, 8, 1)
+
+        # Feedrate Rapids
+        self.frapids_label = QtWidgets.QLabel('%s:' % _("FR Rapids"))
+        self.frapids_label.setToolTip(
+            _("FR Rapids. Feedrate Rapids\n"
+              "Speed used while moving as fast as possible.\n"
+              "This is used only by some devices that can't use\n"
+              "the G0 g-code command. Mostly 3D printers."))
+
+        self.frapids_entry = FCDoubleSpinner()
+        self.frapids_entry.set_range(0.0000, 9999.9999)
+        self.frapids_entry.set_precision(self.decimals)
+        self.frapids_entry.setObjectName('gdb_frapids')
+
+        self.grid1.addWidget(self.frapids_label, 10, 0)
+        self.grid1.addWidget(self.frapids_entry, 10, 1)
+
+        # Extra Cut
+        self.ecut_label = QtWidgets.QLabel('%s:' % _("ExtraCut"))
+        self.ecut_label.setToolTip(
+            _("Extra Cut.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation."))
+
+        self.ecut_cb = FCCheckBox()
+        self.ecut_cb.setObjectName('gdb_ecut')
+
+        self.grid1.addWidget(self.ecut_label, 12, 0)
+        self.grid1.addWidget(self.ecut_cb, 12, 1)
+
+        # Extra Cut Length
+        self.ecut_length_label = QtWidgets.QLabel('%s:' % _("E-Cut Length"))
+        self.ecut_length_label.setToolTip(
+            _("Extra Cut length.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation. This is the length of\n"
+              "the extra cut."))
+
+        self.ecut_length_entry = FCDoubleSpinner()
+        self.ecut_length_entry.set_range(0.0000, 9999.9999)
+        self.ecut_length_entry.set_precision(self.decimals)
+        self.ecut_length_entry.setObjectName('gdb_ecut_length')
+
+        self.grid1.addWidget(self.ecut_length_label, 13, 0)
+        self.grid1.addWidget(self.ecut_length_entry, 13, 1)
+
+        # ###########################################################################
+        # ############### NCC UI form ###############################################
+        # ###########################################################################
+
+        self.grid2 = QtWidgets.QGridLayout()
+        self.ncc_vlay.addLayout(self.grid2)
+        self.grid2.setColumnStretch(0, 0)
+        self.grid2.setColumnStretch(1, 1)
+        self.ncc_vlay.addStretch()
+
+        # Operation
+        op_label = QtWidgets.QLabel('%s:' % _('Operation'))
+        op_label.setToolTip(
+            _("The 'Operation' can be:\n"
+              "- Isolation -> will ensure that the non-copper clearing is always complete.\n"
+              "If it's not successful then the non-copper clearing will fail, too.\n"
+              "- Clear -> the regular non-copper clearing.")
+        )
+
+        self.op_radio = RadioSet([
+            {"label": _("Clear"), "value": "clear"},
+            {"label": _("Isolation"), "value": "iso"}
+        ], orientation='horizontal', stretch=False)
+        self.op_radio.setObjectName("gdb_n_operation")
+
+        self.grid2.addWidget(op_label, 13, 0)
+        self.grid2.addWidget(self.op_radio, 13, 1)
+
+        # 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"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+
+        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"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+        self.milling_type_radio.setObjectName("gdb_n_milling_type")
+
+        self.grid2.addWidget(self.milling_type_label, 14, 0)
+        self.grid2.addWidget(self.milling_type_radio, 14, 1)
+
+        # Overlap Entry
+        nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
+        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"
+              "Lower values = faster processing, faster execution on CNC.\n"
+              "Higher values = slow processing and slow execution on CNC\n"
+              "due of too many paths.")
+        )
+        self.ncc_overlap_entry = FCDoubleSpinner(suffix='%')
+        self.ncc_overlap_entry.set_precision(self.decimals)
+        self.ncc_overlap_entry.setWrapping(True)
+        self.ncc_overlap_entry.setRange(0.000, 99.9999)
+        self.ncc_overlap_entry.setSingleStep(0.1)
+        self.ncc_overlap_entry.setObjectName("gdb_n_overlap")
+
+        self.grid2.addWidget(nccoverlabel, 15, 0)
+        self.grid2.addWidget(self.ncc_overlap_entry, 15, 1)
+
+        # Margin
+        nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
+        nccmarginlabel.setToolTip(
+            _("Bounding box margin.")
+        )
+        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.setObjectName("gdb_n_margin")
+
+        self.grid2.addWidget(nccmarginlabel, 16, 0)
+        self.grid2.addWidget(self.ncc_margin_entry, 16, 1)
+
+        # Method
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
+        methodlabel.setToolTip(
+            _("Algorithm for copper clearing:\n"
+              "- Standard: Fixed step inwards.\n"
+              "- Seed-based: Outwards from seed.\n"
+              "- Line-based: Parallel lines.")
+        )
+
+        self.ncc_method_combo = FCComboBox()
+        self.ncc_method_combo.addItems(
+            [_("Standard"), _("Seed"), _("Lines")]
+        )
+        self.ncc_method_combo.setObjectName("gdb_n_method")
+
+        self.grid2.addWidget(methodlabel, 17, 0)
+        self.grid2.addWidget(self.ncc_method_combo, 17, 1)
+
+        # Connect lines
+        self.ncc_connect_cb = FCCheckBox('%s' % _("Connect"))
+        self.ncc_connect_cb.setObjectName("gdb_n_connect")
+
+        self.ncc_connect_cb.setToolTip(
+            _("Draw lines between resulting\n"
+              "segments to minimize tool lifts.")
+        )
+        self.grid2.addWidget(self.ncc_connect_cb, 18, 0)
+
+        # Contour
+        self.ncc_contour_cb = FCCheckBox('%s' % _("Contour"))
+        self.ncc_contour_cb.setObjectName("gdb_n_contour")
+
+        self.ncc_contour_cb.setToolTip(
+            _("Cut around the perimeter of the polygon\n"
+              "to trim rough edges.")
+        )
+        self.grid2.addWidget(self.ncc_contour_cb, 18, 1)
+
+        # ## NCC Offset choice
+        self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset"))
+        self.ncc_choice_offset_cb.setObjectName("gdb_n_offset")
+
+        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.")
+        )
+        self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0)
+
+        # ## NCC Offset Entry
+        self.ncc_offset_spinner = FCDoubleSpinner()
+        self.ncc_offset_spinner.set_range(0.00, 10.00)
+        self.ncc_offset_spinner.set_precision(4)
+        self.ncc_offset_spinner.setWrapping(True)
+        self.ncc_offset_spinner.setObjectName("gdb_n_offset_value")
+
+        units = self.app.defaults['units'].upper()
+        if units == 'MM':
+            self.ncc_offset_spinner.setSingleStep(0.1)
+        else:
+            self.ncc_offset_spinner.setSingleStep(0.01)
+
+        self.grid2.addWidget(self.ncc_offset_spinner, 19, 1)
+
+        # ###########################################################################
+        # ############### Paint UI form #############################################
+        # ###########################################################################
+
+        self.grid3 = QtWidgets.QGridLayout()
+        self.paint_vlay.addLayout(self.grid3)
+        self.grid3.setColumnStretch(0, 0)
+        self.grid3.setColumnStretch(1, 1)
+        self.paint_vlay.addStretch()
+
+        # Overlap
+        ovlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
+        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"
+              "Lower values = faster processing, faster execution on CNC.\n"
+              "Higher values = slow processing and slow execution on CNC\n"
+              "due of too many paths.")
+        )
+        self.paintoverlap_entry = FCDoubleSpinner(suffix='%')
+        self.paintoverlap_entry.set_precision(3)
+        self.paintoverlap_entry.setWrapping(True)
+        self.paintoverlap_entry.setRange(0.0000, 99.9999)
+        self.paintoverlap_entry.setSingleStep(0.1)
+        self.paintoverlap_entry.setObjectName('gdb_p_overlap')
+
+        self.grid3.addWidget(ovlabel, 1, 0)
+        self.grid3.addWidget(self.paintoverlap_entry, 1, 1)
+
+        # Margin
+        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
+        marginlabel.setToolTip(
+            _("Distance by which to avoid\n"
+              "the edges of the polygon to\n"
+              "be painted.")
+        )
+        self.paintmargin_entry = FCDoubleSpinner()
+        self.paintmargin_entry.set_precision(self.decimals)
+        self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
+        self.paintmargin_entry.setObjectName('gdb_p_margin')
+
+        self.grid3.addWidget(marginlabel, 2, 0)
+        self.grid3.addWidget(self.paintmargin_entry, 2, 1)
+
+        # Method
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
+        methodlabel.setToolTip(
+            _("Algorithm for painting:\n"
+              "- Standard: Fixed step inwards.\n"
+              "- Seed-based: Outwards from seed.\n"
+              "- Line-based: Parallel lines.\n"
+              "- Laser-lines: Active only for Gerber objects.\n"
+              "Will create lines that follow the traces.\n"
+              "- Combo: In case of failure a new method will be picked from the above\n"
+              "in the order specified.")
+        )
+
+        self.paintmethod_combo = FCComboBox()
+        self.paintmethod_combo.addItems(
+            [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
+        )
+        idx = self.paintmethod_combo.findText(_("Laser_lines"))
+        self.paintmethod_combo.model().item(idx).setEnabled(False)
+
+        self.paintmethod_combo.setObjectName('gdb_p_method')
+
+        self.grid3.addWidget(methodlabel, 7, 0)
+        self.grid3.addWidget(self.paintmethod_combo, 7, 1)
+
+        # Connect lines
+        self.pathconnect_cb = FCCheckBox('%s' % _("Connect"))
+        self.pathconnect_cb.setObjectName('gdb_p_connect')
+        self.pathconnect_cb.setToolTip(
+            _("Draw lines between resulting\n"
+              "segments to minimize tool lifts.")
+        )
+
+        self.paintcontour_cb = FCCheckBox('%s' % _("Contour"))
+        self.paintcontour_cb.setObjectName('gdb_p_contour')
+        self.paintcontour_cb.setToolTip(
+            _("Cut around the perimeter of the polygon\n"
+              "to trim rough edges.")
+        )
+
+        self.grid3.addWidget(self.pathconnect_cb, 10, 0)
+        self.grid3.addWidget(self.paintcontour_cb, 10, 1)
+
+        # ####################################################################
+        # ####################################################################
+        # GUI for the lower part of the window
+        # ####################################################################
+        # ####################################################################
+
+        new_vlay = QtWidgets.QVBoxLayout()
+        grid_layout.addLayout(new_vlay, 1, 0, 1, 2)
+
+        self.buttons_frame = QtWidgets.QFrame()
+        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
+        new_vlay.addWidget(self.buttons_frame)
+        self.buttons_box = QtWidgets.QHBoxLayout()
+        self.buttons_box.setContentsMargins(0, 0, 0, 0)
+        self.buttons_frame.setLayout(self.buttons_box)
+        self.buttons_frame.show()
+
+        add_entry_btn = FCButton(_("Add Tool in DB"))
+        add_entry_btn.setToolTip(
+            _("Add a new tool in the Tools Database.\n"
+              "It will be used in the Geometry UI.\n"
+              "You can edit it after it is added.")
+        )
+        self.buttons_box.addWidget(add_entry_btn)
+
+        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
+        # add_fct_entry_btn.setToolTip(
+        #     _("Add a new tool in the Tools Database.\n"
+        #       "It will be used in the Paint/NCC Tools UI.\n"
+        #       "You can edit it after it is added.")
+        # )
+        # self.buttons_box.addWidget(add_fct_entry_btn)
+
+        remove_entry_btn = FCButton(_("Delete Tool from DB"))
+        remove_entry_btn.setToolTip(
+            _("Remove a selection of tools in the Tools Database.")
+        )
+        self.buttons_box.addWidget(remove_entry_btn)
+
+        export_db_btn = FCButton(_("Export DB"))
+        export_db_btn.setToolTip(
+            _("Save the Tools Database to a custom text file.")
+        )
+        self.buttons_box.addWidget(export_db_btn)
+
+        import_db_btn = FCButton(_("Import DB"))
+        import_db_btn.setToolTip(
+            _("Load the Tools Database information's from a custom text file.")
+        )
+        self.buttons_box.addWidget(import_db_btn)
+
+        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
+        self.add_tool_from_db.setToolTip(
+            _("Add a new tool in the Tools Table of the\n"
+              "active Geometry object after selecting a tool\n"
+              "in the Tools Database.")
+        )
+        self.add_tool_from_db.hide()
+
+        self.cancel_tool_from_db = FCButton(_("Cancel"))
+        self.cancel_tool_from_db.hide()
+
+        hlay = QtWidgets.QHBoxLayout()
+        tree_layout.addLayout(hlay)
+        hlay.addWidget(self.add_tool_from_db)
+        hlay.addWidget(self.cancel_tool_from_db)
+        hlay.addStretch()
+
+        # ##############################################################################
+        # ##############################################################################
+        # ########## SETUP THE DICTIONARIES THAT HOLD THE WIDGETS #####################
+        # ##############################################################################
+        # ##############################################################################
+
+        self.form_fields = {
+            # Basic
+            "name":             self.name_entry,
+            "tooldia":          self.dia_entry,
+            "tool_type":        self.shape_combo,
+            "cutz":             self.cutz_entry,
+            "multidepth":       self.multidepth_cb,
+            "depthperpass":     self.multidepth_entry,
+            "travelz":          self.travelz_entry,
+            "feedrate":         self.frxy_entry,
+            "feedrate_z":       self.frz_entry,
+            "spindlespeed":     self.spindle_entry,
+            "dwell":            self.dwell_cb,
+            "dwelltime":        self.dwelltime_entry,
+
+            # Advanced
+            "type":             self.type_combo,
+            "offset":           self.tooloffset_combo,
+            "offset_value":     self.custom_offset_entry,
+            "vtipdia":          self.vdia_entry,
+            "vtipangle":        self.vangle_entry,
+            "feedrate_rapid":   self.frapids_entry,
+            "extracut":         self.ecut_cb,
+            "extracut_length":  self.ecut_length_entry,
+
+            # NCC
+            "tools_nccoperation":       self.op_radio,
+            "tools_nccmilling_type":    self.milling_type_radio,
+            "tools_nccoverlap":         self.ncc_overlap_entry,
+            "tools_nccmargin":          self.ncc_margin_entry,
+            "tools_nccmethod":          self.ncc_method_combo,
+            "tools_nccconnect":         self.ncc_connect_cb,
+            "tools_ncccontour":         self.ncc_contour_cb,
+            "tools_ncc_offset_choice":  self.ncc_choice_offset_cb,
+            "tools_ncc_offset_value":   self.ncc_offset_spinner,
+
+            # Paint
+            "tools_paintoverlap":       self.paintoverlap_entry,
+            "tools_paintmargin":        self.paintmargin_entry,
+            "tools_paintmethod":        self.paintmethod_combo,
+            "tools_pathconnect":        self.pathconnect_cb,
+            "tools_paintcontour":       self.paintcontour_cb,
+        }
+
+        self.name2option = {
+            # Basic
+            "gdb_name":             "name",
+            "gdb_dia":              "tooldia",
+            "gdb_shape":            "tool_type",
+            "gdb_cutz":             "cutz",
+            "gdb_multidepth":       "multidepth",
+            "gdb_multidepth_entry": "depthperpass",
+            "gdb_travel":           "travelz",
+            "gdb_frxy":             "feedrate",
+            "gdb_frz":              "feedrate_z",
+            "gdb_spindle":          "spindlespeed",
+            "gdb_dwell":            "dwell",
+            "gdb_dwelltime":        "dwelltime",
+
+            # Advanced
+            "gdb_type":             "type",
+            "gdb_tool_offset":      "offset",
+            "gdb_custom_offset":    "offset_value",
+            "gdb_vdia":             "vtipdia",
+            "gdb_vangle":           "vtipangle",
+            "gdb_frapids":          "feedrate_rapid",
+            "gdb_ecut":             "extracut",
+            "gdb_ecut_length":      "extracut_length",
+
+            # NCC
+            "gdb_n_operation":      "tools_nccoperation",
+            "gdb_n_overlap":        "tools_nccoverlap",
+            "gdb_n_margin":         "tools_nccmargin",
+            "gdb_n_method":         "tools_nccmethod",
+            "gdb_n_connect":        "tools_nccconnect",
+            "gdb_n_contour":        "tools_ncccontour",
+            "gdb_n_offset":         "tools_ncc_offset_choice",
+            "gdb_n_offset_value":   "tools_ncc_offset_value",
+            "gdb_n_milling_type":   "tools_nccmilling_type",
+
+            # Paint
+            'gdb_p_overlap':        "tools_paintoverlap",
+            'gdb_p_margin':         "tools_paintmargin",
+            'gdb_p_method':         "tools_paintmethod",
+            'gdb_p_connect':        "tools_pathconnect",
+            'gdb_p_contour':        "tools_paintcontour",
+        }
+
+        self.current_toolid = None
+
+        # variable to show if double clicking and item will trigger adding a tool from DB
+        self.ok_to_add = False
+
+        # ##############################################################################
+        # ######################## SIGNALS #############################################
+        # ##############################################################################
+
+        add_entry_btn.clicked.connect(self.on_tool_add)
+        remove_entry_btn.clicked.connect(self.on_tool_delete)
+        export_db_btn.clicked.connect(self.on_export_tools_db_file)
+        import_db_btn.clicked.connect(self.on_import_tools_db_file)
+        # closebtn.clicked.connect(self.accept)
+
+        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
+        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
+
+        # self.tree_widget.selectionModel().selectionChanged.connect(self.on_list_selection_change)
+        self.tree_widget.currentItemChanged.connect(self.on_list_selection_change)
+        self.tree_widget.itemChanged.connect(self.on_list_item_edited)
+        self.tree_widget.customContextMenuRequested.connect(self.on_menu_request)
+
+        self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked)
+
+        self.setup_db_ui()
+
+    def on_menu_request(self, pos):
+
+        menu = QtWidgets.QMenu()
+        add_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/plus16.png'), _("Add to DB"))
+        add_tool.triggered.connect(self.on_tool_add)
+
+        copy_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/copy16.png'), _("Copy from DB"))
+        copy_tool.triggered.connect(self.on_tool_copy)
+
+        delete_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete from DB"))
+        delete_tool.triggered.connect(self.on_tool_delete)
+
+        # tree_item = self.tree_widget.itemAt(pos)
+        menu.exec(self.tree_widget.viewport().mapToGlobal(pos))
+
+    def on_item_double_clicked(self, item, column):
+        if column == 0 and self.ok_to_add is True:
+            self.ok_to_add = False
+            self.on_tool_requested_from_app()
+
+    def on_list_selection_change(self, current, previous):
+        # for idx in current.indexes():
+        #     print(idx.data())
+        # print(current.text(0))
+        self.current_toolid = int(current.text(0))
+
+        self.storage_to_form(self.db_tool_dict[current.text(0)])
+
+    def on_list_item_edited(self, item, column):
+        if column == 0:
+            return
+
+        self.name_entry.set_value(item.text(1))
+
+    def storage_to_form(self, dict_storage):
+        for form_key in self.form_fields:
+            for storage_key in dict_storage:
+                if form_key == storage_key:
+                    try:
+                        self.form_fields[form_key].set_value(dict_storage[form_key])
+                    except Exception as e:
+                        print(str(e))
+                if storage_key == 'data':
+                    for data_key in dict_storage[storage_key]:
+                        if form_key == data_key:
+                            try:
+                                self.form_fields[form_key].set_value(dict_storage['data'][data_key])
+                            except Exception as e:
+                                print(str(e))
+
+    def form_to_storage(self, tool):
+        self.blockSignals(True)
+
+        widget_changed = self.sender()
+        wdg_objname = widget_changed.objectName()
+        option_changed = self.name2option[wdg_objname]
+
+        tooluid_item = int(tool)
+
+        for tooluid_key, tooluid_val in self.db_tool_dict.items():
+            if int(tooluid_key) == tooluid_item:
+                new_option_value = self.form_fields[option_changed].get_value()
+                if option_changed in tooluid_val:
+                    tooluid_val[option_changed] = new_option_value
+                if option_changed in tooluid_val['data']:
+                    tooluid_val['data'][option_changed] = new_option_value
+
+        self.blockSignals(False)
+
+    def setup_db_ui(self):
+        filename = self.app.data_path + '/geo_tools_db.FlatDB'
+
+        # 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."))
+            return
+
+        try:
+            self.db_tool_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."))
+            return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+
+        self.build_db_ui()
+
+    def build_db_ui(self):
+        self.ui_disconnect()
+        nr_crt = 0
+
+        parent = self.tree_widget
+        self.tree_widget.blockSignals(True)
+        self.tree_widget.clear()
+        self.tree_widget.blockSignals(False)
+
+        for toolid, dict_val in self.db_tool_dict.items():
+            row = nr_crt
+            nr_crt += 1
+
+            t_name = dict_val['name']
+            try:
+                # self.add_tool_table_line(row, name=t_name, tooldict=dict_val)
+                self.tree_widget.blockSignals(True)
+                try:
+                    self.tree_widget.addParentEditable(parent=parent, title=[str(row+1), t_name], editable=True)
+                except Exception as e:
+                    print('FlatCAMCoomn.ToolDB2.build_db_ui() -> ', str(e))
+                self.tree_widget.blockSignals(False)
+            except Exception as e:
+                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
+
+        if self.current_toolid is None or self.current_toolid < 1:
+            if self.db_tool_dict:
+                self.storage_to_form(self.db_tool_dict['1'])
+
+                # Enable GUI
+                self.basic_box.setEnabled(True)
+                self.advanced_box.setEnabled(True)
+                self.ncc_box.setEnabled(True)
+                self.paint_box.setEnabled(True)
+
+                self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
+                # self.tree_widget.setFocus()
+
+            else:
+                # Disable GUI
+                self.basic_box.setEnabled(False)
+                self.advanced_box.setEnabled(False)
+                self.ncc_box.setEnabled(False)
+                self.paint_box.setEnabled(False)
+        else:
+            self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
+
+        self.ui_connect()
+
+    def on_tool_add(self):
+        """
+        Add a tool in the DB Tool Table
+        :return: None
+        """
+
+        default_data = {}
+        default_data.update({
+            "plot":             True,
+            "cutz":             float(self.app.defaults["geometry_cutz"]),
+            "multidepth":       self.app.defaults["geometry_multidepth"],
+            "depthperpass":     float(self.app.defaults["geometry_depthperpass"]),
+            "vtipdia":          float(self.app.defaults["geometry_vtipdia"]),
+            "vtipangle":        float(self.app.defaults["geometry_vtipangle"]),
+            "travelz":          float(self.app.defaults["geometry_travelz"]),
+            "feedrate":         float(self.app.defaults["geometry_feedrate"]),
+            "feedrate_z":       float(self.app.defaults["geometry_feedrate_z"]),
+            "feedrate_rapid":   float(self.app.defaults["geometry_feedrate_rapid"]),
+            "spindlespeed":     self.app.defaults["geometry_spindlespeed"],
+            "dwell":            self.app.defaults["geometry_dwell"],
+            "dwelltime":        float(self.app.defaults["geometry_dwelltime"]),
+            "ppname_g":         self.app.defaults["geometry_ppname_g"],
+            "extracut":         self.app.defaults["geometry_extracut"],
+            "extracut_length":  float(self.app.defaults["geometry_extracut_length"]),
+            "toolchange":       self.app.defaults["geometry_toolchange"],
+            "toolchangexy":     self.app.defaults["geometry_toolchangexy"],
+            "toolchangez":      float(self.app.defaults["geometry_toolchangez"]),
+            "startz":           self.app.defaults["geometry_startz"],
+            "endz":             float(self.app.defaults["geometry_endz"]),
+
+            # NCC
+            "tools_nccoperation":       self.app.defaults["tools_nccoperation"],
+            "tools_nccmilling_type":    self.app.defaults["tools_nccmilling_type"],
+            "tools_nccoverlap":         float(self.app.defaults["tools_nccoverlap"]),
+            "tools_nccmargin":          float(self.app.defaults["tools_nccmargin"]),
+            "tools_nccmethod":          self.app.defaults["tools_nccmethod"],
+            "tools_nccconnect":         self.app.defaults["tools_nccconnect"],
+            "tools_ncccontour":         self.app.defaults["tools_ncccontour"],
+            "tools_ncc_offset_choice":  self.app.defaults["tools_ncc_offset_choice"],
+            "tools_ncc_offset_value":   float(self.app.defaults["tools_ncc_offset_value"]),
+
+            # Paint
+            "tools_paintoverlap":       float(self.app.defaults["tools_paintoverlap"]),
+            "tools_paintmargin":        float(self.app.defaults["tools_paintmargin"]),
+            "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
+            "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
+            "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
+        })
+
+        dict_elem = {}
+        dict_elem['name'] = 'new_tool'
+        if type(self.app.defaults["geometry_cnctooldia"]) == float:
+            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
+        else:
+            try:
+                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
+                tools_diameters = [eval(a) for a in tools_string if a != '']
+                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
+            except Exception as e:
+                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
+                return
+
+        dict_elem['offset'] = 'Path'
+        dict_elem['offset_value'] = 0.0
+        dict_elem['type'] = 'Rough'
+        dict_elem['tool_type'] = 'C1'
+        dict_elem['data'] = default_data
+
+        new_toolid = len(self.db_tool_dict) + 1
+        self.db_tool_dict[str(new_toolid)] = deepcopy(dict_elem)
+
+        # add the new entry to the Tools DB table
+        self.update_storage()
+        self.build_db_ui()
+        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
+
+    def on_tool_copy(self):
+        """
+        Copy a selection of Tools in the Tools DB table
+        :return:
+        """
+        new_tool_id = len(self.db_tool_dict)
+        for item in self.tree_widget.selectedItems():
+            old_tool_id = item.data(0, QtCore.Qt.DisplayRole)
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(old_tool_id) == int(toolid):
+                    new_tool_id += 1
+                    new_key = str(new_tool_id)
+
+                    self.db_tool_dict.update({
+                        new_key: deepcopy(dict_val)
+                    })
+
+        self.current_toolid = new_tool_id
+
+        self.update_storage()
+        self.build_db_ui()
+        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
+
+    def on_tool_delete(self):
+        """
+        Delete a selection of Tools in the Tools DB table
+        :return:
+        """
+        for item in self.tree_widget.selectedItems():
+            toolname_to_remove = item.data(0, QtCore.Qt.DisplayRole)
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(toolname_to_remove) == int(toolid):
+                    # remove from the storage
+                    self.db_tool_dict.pop(toolid, None)
+
+        self.current_toolid -= 1
+
+        self.update_storage()
+        self.build_db_ui()
+        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
+
+    def on_export_tools_db_file(self):
+        self.app.report_usage("on_export_tools_db_file")
+        self.app.log.debug("on_export_tools_db_file()")
+
+        date = str(datetime.today()).rpartition('.')[0]
+        date = ''.join(c for c in date if c not in ':-')
+        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),
+                                                           filter=filter__)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            try:
+                f = open(filename, 'w')
+                f.close()
+            except PermissionError:
+                self.app.inform.emit('[WARNING] %s' %
+                                     _("Permission denied, saving not possible.\n"
+                                       "Most likely another app is holding the file open and not accessible."))
+                return
+            except IOError:
+                self.app.log.debug('Creating a new Tools DB file ...')
+                f = open(filename, 'w')
+                f.close()
+            except Exception:
+                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."))
+                return
+
+            # Save update options
+            try:
+                # Save Tools DB in a file
+                try:
+                    with open(filename, "w") as f:
+                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                except Exception as e:
+                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
+                    self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
+
+    def on_import_tools_db_file(self):
+        self.app.report_usage("on_import_tools_db_file")
+        self.app.log.debug("on_import_tools_db_file()")
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+        else:
+            try:
+                with open(filename) as f:
+                    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."))
+                return
+
+            try:
+                self.db_tool_dict = json.loads(tools_in_db)
+            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."))
+                return
+
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+            self.build_db_ui()
+            self.update_storage()
+
+    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 + "/geo_tools_db.FlatDB"
+
+        # 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'))
+
+                # Save Tools DB in a file
+                try:
+                    f = open(filename, "w")
+                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                    f.close()
+                except Exception as e:
+                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+
+                if not silent:
+                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
+
+    def ui_connect(self):
+        # make sure that we don't make multiple connections to the widgets
+        self.ui_disconnect()
+
+        self.name_entry.editingFinished.connect(self.update_tree_name)
+
+        for key in self.form_fields:
+            wdg = self.form_fields[key]
+
+            # FCEntry
+            if isinstance(wdg, FCEntry):
+                wdg.textChanged.connect(self.update_storage)
+
+            # ComboBox
+            if isinstance(wdg, FCComboBox):
+                wdg.currentIndexChanged.connect(self.update_storage)
+
+            # CheckBox
+            if isinstance(wdg, FCCheckBox):
+                wdg.toggled.connect(self.update_storage)
+
+            # FCRadio
+            if isinstance(wdg, RadioSet):
+                wdg.activated_custom.connect(self.update_storage)
+
+            # SpinBox, DoubleSpinBox
+            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
+                wdg.valueChanged.connect(self.update_storage)
+
+    def ui_disconnect(self):
+        try:
+            self.name_entry.editingFinished.disconnect(self.update_tree_name)
+        except (TypeError, AttributeError):
+            pass
+
+        for key in self.form_fields:
+            wdg = self.form_fields[key]
+
+            # FCEntry
+            if isinstance(wdg, FCEntry):
+                try:
+                    wdg.textChanged.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # ComboBox
+            if isinstance(wdg, FCComboBox):
+                try:
+                    wdg.currentIndexChanged.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # CheckBox
+            if isinstance(wdg, FCCheckBox):
+                try:
+                    wdg.toggled.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # FCRadio
+            if isinstance(wdg, RadioSet):
+                try:
+                    wdg.activated_custom.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # SpinBox, DoubleSpinBox
+            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
+                try:
+                    wdg.valueChanged.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+    def update_tree_name(self):
+        val = self.name_entry.get_value()
+
+        item = self.tree_widget.currentItem()
+        # I'm setting the value for the second column (designated by 1) because first column holds the ID
+        # and second column holds the Name (this behavior is set in the build_ui method)
+        item.setData(1, QtCore.Qt.DisplayRole, val)
+
+    def update_storage(self):
+        """
+        Update the dictionary that is the storage of the tools 'database'
+        :return:
+        """
+        tool_id = str(self.current_toolid)
+
+        try:
+            wdg = self.sender()
+
+            assert isinstance(wdg, QtWidgets.QWidget), "Expected a QWidget got %s" % type(wdg)
+
+            if wdg is None:
+                return
+
+            wdg_name = wdg.objectName()
+            val = wdg.get_value()
+        except AttributeError:
+            return
+
+        if wdg_name == "gdb_name":
+            self.db_tool_dict[tool_id]['name'] = val
+        elif wdg_name == "gdb_dia":
+            self.db_tool_dict[tool_id]['tooldia'] = val
+        elif wdg_name == "gdb_tool_offset":
+            self.db_tool_dict[tool_id]['offset'] = val
+        elif wdg_name == "gdb_custom_offset":
+            self.db_tool_dict[tool_id]['offset_value'] = val
+        elif wdg_name == "gdb_type":
+            self.db_tool_dict[tool_id]['type'] = val
+        elif wdg_name == "gdb_shape":
+            self.db_tool_dict[tool_id]['tool_type'] = val
+        else:
+            if wdg_name == "gdb_cutz":
+                self.db_tool_dict[tool_id]['data']['cutz'] = val
+            elif wdg_name == "gdb_multidepth":
+                self.db_tool_dict[tool_id]['data']['multidepth'] = val
+            elif wdg_name == "gdb_multidepth_entry":
+                self.db_tool_dict[tool_id]['data']['depthperpass'] = val
+
+            elif wdg_name == "gdb_travel":
+                self.db_tool_dict[tool_id]['data']['travelz'] = val
+            elif wdg_name == "gdb_frxy":
+                self.db_tool_dict[tool_id]['data']['feedrate'] = val
+            elif wdg_name == "gdb_frz":
+                self.db_tool_dict[tool_id]['data']['feedrate_z'] = val
+            elif wdg_name == "gdb_spindle":
+                self.db_tool_dict[tool_id]['data']['spindlespeed'] = val
+            elif wdg_name == "gdb_dwell":
+                self.db_tool_dict[tool_id]['data']['dwell'] = val
+            elif wdg_name == "gdb_dwelltime":
+                self.db_tool_dict[tool_id]['data']['dwelltime'] = val
+
+            elif wdg_name == "gdb_vdia":
+                self.db_tool_dict[tool_id]['data']['vtipdia'] = val
+            elif wdg_name == "gdb_vangle":
+                self.db_tool_dict[tool_id]['data']['vtipangle'] = val
+            elif wdg_name == "gdb_frapids":
+                self.db_tool_dict[tool_id]['data']['feedrate_rapid'] = val
+            elif wdg_name == "gdb_ecut":
+                self.db_tool_dict[tool_id]['data']['extracut'] = val
+            elif wdg_name == "gdb_ecut_length":
+                self.db_tool_dict[tool_id]['data']['extracut_length'] = val
+
+            # NCC Tool
+            elif wdg_name == "gdb_n_operation":
+                self.db_tool_dict[tool_id]['data']['tools_nccoperation'] = val
+            elif wdg_name == "gdb_n_overlap":
+                self.db_tool_dict[tool_id]['data']['tools_nccoverlap'] = val
+            elif wdg_name == "gdb_n_margin":
+                self.db_tool_dict[tool_id]['data']['tools_nccmargin'] = val
+            elif wdg_name == "gdb_n_method":
+                self.db_tool_dict[tool_id]['data']['tools_nccmethod'] = val
+            elif wdg_name == "gdb_n_connect":
+                self.db_tool_dict[tool_id]['data']['tools_nccconnect'] = val
+            elif wdg_name == "gdb_n_contour":
+                self.db_tool_dict[tool_id]['data']['tools_ncccontour'] = val
+            elif wdg_name == "gdb_n_offset":
+                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_choice'] = val
+            elif wdg_name == "gdb_n_offset_value":
+                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_value'] = val
+            elif wdg_name == "gdb_n_milling_type":
+                self.db_tool_dict[tool_id]['data']['tools_nccmilling_type'] = val
+
+            # Paint Tool
+            elif wdg_name == "gdb_p_overlap":
+                self.db_tool_dict[tool_id]['data']['tools_paintoverlap'] = val
+            elif wdg_name == "gdb_p_margin":
+                self.db_tool_dict[tool_id]['data']['tools_paintmargin'] = val
+            elif wdg_name == "gdb_p_method":
+                self.db_tool_dict[tool_id]['data']['tools_paintmethod'] = val
+            elif wdg_name == "gdb_p_connect":
+                self.db_tool_dict[tool_id]['data']['tools_pathconnect'] = val
+            elif wdg_name == "gdb_p_contour":
+                self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
+
+        self.callback_app()
+
+    def on_tool_requested_from_app(self):
+        if not self.tree_widget.selectedItems():
+            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
+            return
+
+        for item in self.tree_widget.selectedItems():
+            tool_uid = item.data(0, QtCore.Qt.DisplayRole)
+
+            for key in self.db_tool_dict.keys():
+                if str(key) == str(tool_uid):
+                    selected_tool = self.db_tool_dict[key]
+                    self.on_tool_request(tool=selected_tool)
+
+    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"):
+                wdg = self.app.ui.plot_tab_area.widget(idx)
+                wdg.deleteLater()
+                self.app.ui.plot_tab_area.removeTab(idx)
+        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
+
+    # def resize_new_tool_table_widget(self, min_size, max_size):
+    #     """
+    #     Resize the table widget responsible for adding new tool in the Tool Database
+    #
+    #     :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+    #     :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+    #     :return:
+    #     """
+    #     t_height = self.t_height
+    #     if max_size > min_size:
+    #         t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
+    #
+    #     self.new_tool_table_widget.setMaximumHeight(t_height)
+
+    def closeEvent(self, QCloseEvent):
+        super().closeEvent(QCloseEvent)

+ 0 - 8244
FlatCAMObj.py

@@ -1,8244 +0,0 @@
-# ##########################################################
-# FlatCAM: 2D Post-processing for Manufacturing            #
-# http://flatcam.org                                       #
-# Author: Juan Pablo Caram (c)                             #
-# Date: 2/5/2014                                           #
-# MIT Licence                                              #
-# ##########################################################
-
-# ##########################################################
-# File modified by: Marius Stanciu                         #
-# ##########################################################
-
-
-from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
-from shapely.ops import cascaded_union
-import shapely.affinity as affinity
-
-from copy import deepcopy
-# from copy import copy
-
-from io import StringIO
-import traceback
-import inspect  # TODO: For debugging only.
-from datetime import datetime
-
-from flatcamEditors.FlatCAMTextEditor import TextEditor
-from flatcamGUI.ObjectUI import *
-from flatcamGUI.GUIElements import FCFileSaveDialog
-from FlatCAMCommon import LoudDict
-from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
-from flatcamParsers.ParseExcellon import Excellon
-from flatcamParsers.ParseGerber import Gerber
-from camlib import Geometry, CNCjob
-import FlatCAMApp
-
-# from flatcamGUI.VisPyVisuals import ShapeCollection
-
-import tkinter as tk
-import os
-import sys
-import itertools
-import ezdxf
-
-import math
-import numpy as np
-
-import gettext
-import FlatCAMTranslation as fcTranslate
-import builtins
-
-fcTranslate.apply_language('strings')
-if '_' not in builtins.__dict__:
-    _ = gettext.gettext
-
-
-# Interrupts plotting process if FlatCAMObj has been deleted
-class ObjectDeleted(Exception):
-    pass
-
-
-class ValidationError(Exception):
-    def __init__(self, message, errors):
-        super().__init__(message)
-
-        self.errors = errors
-
-
-# #######################################
-# #            FlatCAMObj              ##
-# #######################################
-
-
-class FlatCAMObj(QtCore.QObject):
-    """
-    Base type of objects handled in FlatCAM. These become interactive
-    in the GUI, can be plotted, and their options can be modified
-    by the user in their respective forms.
-    """
-
-    # Instance of the application to which these are related.
-    # The app should set this value.
-    app = None
-
-    # signal to plot a single object
-    plot_single_object = QtCore.pyqtSignal()
-
-    def __init__(self, name):
-        """
-        Constructor.
-
-        :param name: Name of the object given by the user.
-        :return: FlatCAMObj
-        """
-
-        QtCore.QObject.__init__(self)
-
-        # View
-        self.ui = None
-
-        self.options = LoudDict(name=name)
-        self.options.set_change_callback(self.on_options_change)
-
-        self.form_fields = {}
-
-        # store here the default data for Geometry Data
-        self.default_data = {}
-
-        # 2D mode
-        # Axes must exist and be attached to canvas.
-        self.axes = None
-        self.kind = None  # Override with proper name
-
-        if self.app.is_legacy is False:
-            self.shapes = self.app.plotcanvas.new_shape_group()
-            # self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, pool=self.app.pool, layers=2)
-        else:
-            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name)
-
-        self.mark_shapes = {}
-
-        self.item = None  # Link with project view item
-
-        self.muted_ui = False
-        self.deleted = False
-
-        try:
-            self._drawing_tolerance = float(self.app.defaults["global_tolerance"]) if \
-                self.app.defaults["global_tolerance"] else 0.01
-        except ValueError:
-            self._drawing_tolerance = 0.01
-
-        self.isHovering = False
-        self.notHovering = True
-
-        # Flag to show if a selection shape is drawn
-        self.selection_shape_drawn = False
-
-        # self.units = 'IN'
-        self.units = self.app.defaults['units']
-
-        self.plot_single_object.connect(self.single_object_plot)
-
-    def __del__(self):
-        pass
-
-    def __str__(self):
-        return "<FlatCAMObj({:12s}): {:20s}>".format(self.kind, self.options["name"])
-
-    def from_dict(self, d):
-        """
-        This supersedes ``from_dict`` in derived classes. Derived classes
-        must inherit from FlatCAMObj first, then from derivatives of Geometry.
-
-        ``self.options`` is only updated, not overwritten. This ensures that
-        options set by the app do not vanish when reading the objects
-        from a project file.
-
-        :param d: Dictionary with attributes to set.
-        :return: None
-        """
-
-        for attr in self.ser_attrs:
-
-            if attr == 'options':
-                self.options.update(d[attr])
-            else:
-                try:
-                    setattr(self, attr, d[attr])
-                except KeyError:
-                    log.debug("FlatCAMObj.from_dict() --> KeyError: %s. "
-                              "Means that we are loading an old project that don't"
-                              "have all attributes in the latest FlatCAM." % str(attr))
-                    pass
-
-    def on_options_change(self, key):
-        # Update form on programmatically options change
-        self.set_form_item(key)
-
-        # Set object visibility
-        if key == 'plot':
-            self.visible = self.options['plot']
-
-        self.optionChanged.emit(key)
-
-    def set_ui(self, ui):
-        self.ui = ui
-
-        self.form_fields = {"name": self.ui.name_entry}
-
-        assert isinstance(self.ui, ObjectUI)
-        self.ui.name_entry.returnPressed.connect(self.on_name_activate)
-
-        try:
-            # it will raise an exception for those FlatCAM objects that do not build UI with the common elements
-            self.ui.offset_button.clicked.connect(self.on_offset_button_click)
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.scale_button.clicked.connect(self.on_scale_button_click)
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click)
-        except (TypeError, AttributeError):
-            pass
-
-        # Creates problems on focusOut
-        try:
-            self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
-        except (TypeError, AttributeError):
-            pass
-
-        # self.ui.skew_button.clicked.connect(self.on_skew_button_click)
-
-    def build_ui(self):
-        """
-        Sets up the UI/form for this object. Show the UI
-        in the App.
-
-        :return: None
-        :rtype: None
-        """
-
-        self.muted_ui = True
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()")
-
-        try:
-            # HACK: disconnect the scale entry signal since on focus out event will trigger an undesired scale()
-            # it seems that the takewidget() does generate a focus out event for the QDoubleSpinbox ...
-            # and reconnect after the takeWidget() is done
-            # self.ui.scale_entry.returnPressed.disconnect(self.on_scale_button_click)
-            self.app.ui.selected_scroll_area.takeWidget()
-            # self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
-        except Exception as e:
-            self.app.log.debug("FlatCAMObj.build_ui() --> Nothing to remove: %s" % str(e))
-
-        self.app.ui.selected_scroll_area.setWidget(self.ui)
-        # self.ui.setMinimumWidth(100)
-        # self.ui.setMaximumWidth(self.app.ui.selected_tab.sizeHint().width())
-
-        self.muted_ui = False
-
-    def on_name_activate(self, silent=None):
-        old_name = copy(self.options["name"])
-        new_name = self.ui.name_entry.get_value()
-
-        if new_name != old_name:
-            # update the SHELL auto-completer model data
-            try:
-                self.app.myKeywords.remove(old_name)
-                self.app.myKeywords.append(new_name)
-                self.app.shell._edit.set_model_data(self.app.myKeywords)
-                self.app.ui.code_editor.set_model_data(self.app.myKeywords)
-            except Exception:
-                log.debug("on_name_activate() --> Could not remove the old object name from auto-completer model list")
-
-            self.options["name"] = self.ui.name_entry.get_value()
-            self.default_data["name"] = self.ui.name_entry.get_value()
-            self.app.collection.update_view()
-            if silent:
-                self.app.inform.emit('[success] %s: %s %s: %s' % (
-                    _("Name changed from"), str(old_name), _("to"), str(new_name)
-                )
-                                     )
-
-    def on_offset_button_click(self):
-        self.app.report_usage("obj_on_offset_button")
-
-        self.read_form()
-        vector_val = self.ui.offsetvector_entry.get_value()
-
-        def worker_task():
-            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")):
-                self.plot()
-            self.app.object_changed.emit(self)
-
-        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
-
-    def on_scale_button_click(self):
-        self.read_form()
-        try:
-            factor = float(eval(self.ui.scale_entry.get_value()))
-        except Exception as e:
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
-            log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e))
-            return
-
-        if type(factor) != float:
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
-
-        # if factor is 1.0 do nothing, there is no point in scaling with a factor of 1.0
-        if factor == 1.0:
-            self.app.inform.emit('[success] %s' % _("Scale done."))
-            return
-
-        log.debug("FlatCAMObj.on_scale_button_click()")
-
-        def worker_task():
-            with self.app.proc_container.new(_("Scaling...")):
-                self.scale(factor)
-                self.app.inform.emit('[success] %s' % _("Scale done."))
-
-            self.app.proc_container.update_view_text('')
-            with self.app.proc_container.new('%s...' % _("Plotting")):
-                self.plot()
-            self.app.object_changed.emit(self)
-
-        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
-
-    def on_skew_button_click(self):
-        self.app.report_usage("obj_on_skew_button")
-        self.read_form()
-        x_angle = self.ui.xangle_entry.get_value()
-        y_angle = self.ui.yangle_entry.get_value()
-
-        def worker_task():
-            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")):
-                self.plot()
-            self.app.object_changed.emit(self)
-
-        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
-
-    def to_form(self):
-        """
-        Copies options to the UI form.
-
-        :return: None
-        """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.to_form()")
-        for option in self.options:
-            try:
-                self.set_form_item(option)
-            except Exception:
-                self.app.log.warning("Unexpected error:", sys.exc_info())
-
-    def read_form(self):
-        """
-        Reads form into ``self.options``.
-
-        :return: None
-        :rtype: None
-        """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
-        for option in self.options:
-            try:
-                self.read_form_item(option)
-            except Exception:
-                self.app.log.warning("Unexpected error:", sys.exc_info())
-
-    def set_form_item(self, option):
-        """
-        Copies the specified option to the UI form.
-
-        :param option: Name of the option (Key in ``self.options``).
-        :type option: str
-        :return: None
-        """
-
-        try:
-            self.form_fields[option].set_value(self.options[option])
-        except KeyError:
-            # self.app.log.warn("Tried to set an option or field that does not exist: %s" % option)
-            pass
-
-    def read_form_item(self, option):
-        """
-        Reads the specified option from the UI form into ``self.options``.
-
-        :param option: Name of the option.
-        :type option: str
-        :return: None
-        """
-        try:
-            self.options[option] = self.form_fields[option].get_value()
-        except KeyError:
-            pass
-            # self.app.log.warning("Failed to read option from field: %s" % option)
-
-    def plot(self, kind=None):
-        """
-        Plot this object (Extend this method to implement the actual plotting).
-        Call this in descendants before doing the plotting.
-
-        :param kind:    Used by only some of the FlatCAM objects
-        :return:        Whether to continue plotting or not depending on the "plot" option. Boolean
-        """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()")
-
-        if self.deleted:
-            return False
-
-        self.clear()
-        return True
-
-    def single_object_plot(self):
-        def plot_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
-                self.plot()
-            self.app.object_changed.emit(self)
-
-        self.app.worker_task.emit({'fcn': plot_task, 'params': []})
-
-    def serialize(self):
-        """
-        Returns a representation of the object as a dictionary so
-        it can be later exported as JSON. Override this method.
-
-        :return: Dictionary representing the object
-        :rtype: dict
-        """
-        return
-
-    def deserialize(self, obj_dict):
-        """
-        Re-builds an object from its serialized version.
-
-        :param obj_dict: Dictionary representing a FlatCAMObj
-        :type obj_dict: dict
-        :return: None
-        """
-        return
-
-    def add_shape(self, **kwargs):
-        if self.deleted:
-            raise ObjectDeleted()
-        else:
-            key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs)
-        return key
-
-    def add_mark_shape(self, apid, **kwargs):
-        if self.deleted:
-            raise ObjectDeleted()
-        else:
-            key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, layer=0, **kwargs)
-        return key
-
-    def update_filters(self, last_ext, filter_string):
-        """
-        Will modify the filter string that is used when saving a file (a list of file extensions) to have the last
-        used file extension as the first one in the special string
-
-        :param last_ext:        The file extension that was last used to save a file
-        :param filter_string:   A key in self.app.defaults that holds a string with the filter from QFileDialog
-        used when saving a file
-        :return:                None
-        """
-
-        filters = copy(self.app.defaults[filter_string])
-        filter_list = filters.split(';;')
-        filter_list_enum_1 = enumerate(filter_list)
-
-        # search for the last element in the filters which should always be "All Files (*.*)"
-        last_elem = ''
-        for elem in list(filter_list_enum_1):
-            if '(*.*)' in elem[1]:
-                last_elem = filter_list.pop(elem[0])
-
-        filter_list_enum = enumerate(filter_list)
-        for elem in list(filter_list_enum):
-            if '.' + last_ext in elem[1]:
-                used_ext = filter_list.pop(elem[0])
-
-                # sort the extensions back
-                filter_list.sort(key=lambda x: x.rpartition('.')[2])
-
-                # add as a first element the last used extension
-                filter_list.insert(0, used_ext)
-                # add back the element that should always be the last (All Files)
-                filter_list.append(last_elem)
-
-                self.app.defaults[filter_string] = ';;'.join(filter_list)
-                return
-
-    @staticmethod
-    def poly2rings(poly):
-        return [poly.exterior] + [interior for interior in poly.interiors]
-
-    @property
-    def visible(self):
-        return self.shapes.visible
-
-    @visible.setter
-    def visible(self, value, threaded=True):
-        log.debug("FlatCAMObj.visible()")
-
-        def worker_task(app_obj):
-            self.shapes.visible = value
-
-            if self.app.is_legacy is False:
-                # Not all object types has annotations
-                try:
-                    self.annotation.visible = value
-                except Exception:
-                    pass
-
-        if threaded is False:
-            worker_task(app_obj=self.app)
-        else:
-            self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
-
-    @property
-    def drawing_tolerance(self):
-        self.units = self.app.defaults['units'].upper()
-        tol = self._drawing_tolerance if self.units == 'MM' or not self.units else self._drawing_tolerance / 25.4
-        return tol
-
-    @drawing_tolerance.setter
-    def drawing_tolerance(self, value):
-        self.units = self.app.defaults['units'].upper()
-        self._drawing_tolerance = value if self.units == 'MM' or not self.units else value / 25.4
-
-    def clear(self, update=False):
-        self.shapes.clear(update)
-
-        # Not all object types has annotations
-        try:
-            self.annotation.clear(update)
-        except AttributeError:
-            pass
-
-    def delete(self):
-        # Free resources
-        del self.ui
-        del self.options
-
-        # Set flag
-        self.deleted = True
-
-
-class FlatCAMGerber(FlatCAMObj, Gerber):
-    """
-    Represents Gerber code.
-    """
-    optionChanged = QtCore.pyqtSignal(str)
-    replotApertures = QtCore.pyqtSignal()
-
-    ui_type = GerberObjectUI
-
-    def merge(self, grb_list, grb_final):
-        """
-        Merges the geometry of objects in geo_list into
-        the geometry of geo_final.
-
-        :param grb_list: List of FlatCAMGerber Objects to join.
-        :param grb_final: Destination FlatCAMGeometry object.
-        :return: None
-        """
-
-        if grb_final.solid_geometry is None:
-            grb_final.solid_geometry = []
-            grb_final.follow_geometry = []
-
-        if not grb_final.apertures:
-            grb_final.apertures = {}
-
-        if type(grb_final.solid_geometry) is not list:
-            grb_final.solid_geometry = [grb_final.solid_geometry]
-            grb_final.follow_geometry = [grb_final.follow_geometry]
-
-        for grb in grb_list:
-
-            # Expand lists
-            if type(grb) is list:
-                FlatCAMGerber.merge(grb, grb_final)
-            else:   # If not list, just append
-                for option in grb.options:
-                    if option != 'name':
-                        try:
-                            grb_final.options[option] = grb.options[option]
-                        except KeyError:
-                            log.warning("Failed to copy option.", option)
-
-                try:
-                    for geos in grb.solid_geometry:
-                        grb_final.solid_geometry.append(geos)
-                        grb_final.follow_geometry.append(geos)
-                except TypeError:
-                    grb_final.solid_geometry.append(grb.solid_geometry)
-                    grb_final.follow_geometry.append(grb.solid_geometry)
-
-                for ap in grb.apertures:
-                    if ap not in grb_final.apertures:
-                        grb_final.apertures[ap] = grb.apertures[ap]
-                    else:
-                        # create a list of integers out of the grb.apertures keys and find the max of that value
-                        # then, the aperture duplicate is assigned an id value incremented with 1,
-                        # and finally made string because the apertures dict keys are strings
-                        max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1)
-                        grb_final.apertures[max_ap] = {}
-                        grb_final.apertures[max_ap]['geometry'] = []
-
-                        for k, v in grb.apertures[ap].items():
-                            grb_final.apertures[max_ap][k] = deepcopy(v)
-
-        grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
-        grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
-
-    def __init__(self, name):
-        self.decimals = self.app.decimals
-
-        self.circle_steps = int(self.app.defaults["gerber_circle_steps"])
-
-        Gerber.__init__(self, steps_per_circle=self.circle_steps)
-        FlatCAMObj.__init__(self, name)
-
-        self.kind = "gerber"
-
-        # The 'name' is already in self.options from FlatCAMObj
-        # Automatically updates the UI
-        self.options.update({
-            "plot": True,
-            "multicolored": False,
-            "solid": False,
-            "tool_type": 'circular',
-            "vtipdia": 0.1,
-            "vtipangle": 30,
-            "vcutz": -0.05,
-            "isotooldia": 0.016,
-            "isopasses": 1,
-            "isooverlap": 15,
-            "milling_type": "cl",
-            "combine_passes": True,
-            "noncoppermargin": 0.0,
-            "noncopperrounded": False,
-            "bboxmargin": 0.0,
-            "bboxrounded": False,
-            "aperture_display": False,
-            "follow": False,
-            "iso_scope": 'all',
-            "iso_type": 'full'
-        })
-
-        # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
-        self.iso_type = 2
-
-        self.multigeo = False
-
-        self.follow = False
-
-        self.apertures_row = 0
-
-        # store the source file here
-        self.source_file = ""
-
-        # list of rows with apertures plotted
-        self.marked_rows = []
-
-        # Mouse events
-        self.mr = None
-        self.mm = None
-        self.mp = None
-
-        # dict to store the polygons selected for isolation; key is the shape added to be plotted and value is the poly
-        self.poly_dict = {}
-
-        # store the status of grid snapping
-        self.grid_status_memory = None
-
-        self.units_found = self.app.defaults['units']
-
-        self.fill_color = self.app.defaults['gerber_plot_fill']
-        self.outline_color = self.app.defaults['gerber_plot_line']
-        self.alpha_level = 'bf'
-
-        # keep track if the UI is built so we don't have to build it every time
-        self.ui_build = False
-
-        # build only once the aperture storage (takes time)
-        self.build_aperture_storage = False
-
-        # Attributes to be included in serialization
-        # Always append to it because it carries contents
-        # from predecessors.
-        self.ser_attrs += ['options', 'kind', 'fill_color', 'outline_color', 'alpha_level']
-
-    def set_ui(self, ui):
-        """
-        Maps options with GUI inputs.
-        Connects GUI events to methods.
-
-        :param ui: GUI object.
-        :type ui: GerberObjectUI
-        :return: None
-        """
-        FlatCAMObj.set_ui(self, ui)
-        FlatCAMApp.App.log.debug("FlatCAMGerber.set_ui()")
-
-        self.units = self.app.defaults['units'].upper()
-
-        self.replotApertures.connect(self.on_mark_cb_click_table)
-
-        self.form_fields.update({
-            "plot": self.ui.plot_cb,
-            "multicolored": self.ui.multicolored_cb,
-            "solid": self.ui.solid_cb,
-            "tool_type": self.ui.tool_type_radio,
-            "vtipdia": self.ui.tipdia_spinner,
-            "vtipangle": self.ui.tipangle_spinner,
-            "vcutz": self.ui.cutz_spinner,
-            "isotooldia": self.ui.iso_tool_dia_entry,
-            "isopasses": self.ui.iso_width_entry,
-            "isooverlap": self.ui.iso_overlap_entry,
-            "milling_type": self.ui.milling_type_radio,
-            "combine_passes": self.ui.combine_passes_cb,
-            "noncoppermargin": self.ui.noncopper_margin_entry,
-            "noncopperrounded": self.ui.noncopper_rounded_cb,
-            "bboxmargin": self.ui.bbmargin_entry,
-            "bboxrounded": self.ui.bbrounded_cb,
-            "aperture_display": self.ui.aperture_table_visibility_cb,
-            "follow": self.ui.follow_cb,
-            "iso_scope": self.ui.iso_scope_radio,
-            "iso_type": self.ui.iso_type_radio
-        })
-
-        # Fill form fields only on object create
-        self.to_form()
-
-        assert isinstance(self.ui, GerberObjectUI)
-        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
-        self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
-        self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
-        self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
-        self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
-        self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run)
-        self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
-        self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
-        self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
-        self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
-
-        # set the model for the Area Exception comboboxes
-        self.ui.obj_combo.setModel(self.app.collection)
-        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.ui.obj_combo.is_last = True
-        self.ui.obj_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Geometry"): "Geometry"
-        }[self.ui.type_obj_combo.get_value()]
-        self.on_type_obj_index_changed()
-
-        self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
-
-        self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change)
-        # establish visibility for the GUI elements found in the slot function
-        self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type'])
-
-        # Show/Hide Advanced Options
-        if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
-            self.options['tool_type'] = 'circular'
-
-            self.ui.tool_type_label.hide()
-            self.ui.tool_type_radio.hide()
-
-            # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
-            self.ui.tool_type_radio.set_value('circular')
-
-            self.ui.tipdialabel.hide()
-            self.ui.tipdia_spinner.hide()
-            self.ui.tipanglelabel.hide()
-            self.ui.tipangle_spinner.hide()
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_spinner.hide()
-
-            self.ui.apertures_table_label.hide()
-            self.ui.aperture_table_visibility_cb.hide()
-            self.ui.milling_type_label.hide()
-            self.ui.milling_type_radio.hide()
-            self.ui.iso_type_label.hide()
-            self.ui.iso_type_radio.hide()
-
-            self.ui.follow_cb.hide()
-            self.ui.except_cb.setChecked(False)
-            self.ui.except_cb.hide()
-        else:
-            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
-            self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia)
-            self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia)
-            self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia)
-
-        if self.app.defaults["gerber_buffering"] == 'no':
-            self.ui.create_buffer_button.show()
-            try:
-                self.ui.create_buffer_button.clicked.disconnect(self.on_generate_buffer)
-            except TypeError:
-                pass
-            self.ui.create_buffer_button.clicked.connect(self.on_generate_buffer)
-        else:
-            self.ui.create_buffer_button.hide()
-
-        # set initial state of the aperture table and associated widgets
-        self.on_aperture_table_visibility_change()
-
-        self.build_ui()
-        self.units_found = self.app.defaults['units']
-
-    def on_calculate_tooldia(self):
-        try:
-            tdia = float(self.ui.tipdia_spinner.get_value())
-        except Exception:
-            return
-        try:
-            dang = float(self.ui.tipangle_spinner.get_value())
-        except Exception:
-            return
-        try:
-            cutz = float(self.ui.cutz_spinner.get_value())
-        except Exception:
-            return
-
-        cutz *= -1
-        if cutz < 0:
-            cutz *= -1
-
-        half_tip_angle = dang / 2
-
-        tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle)))
-        self.ui.iso_tool_dia_entry.set_value(tool_diameter)
-
-    def on_type_obj_index_changed(self):
-        val = self.ui.type_obj_combo.get_value()
-        obj_type = {"Gerber": 0, "Geometry": 2}[val]
-        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.ui.obj_combo.setCurrentIndex(0)
-        self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val]
-
-    def on_tool_type_change(self, state):
-        if state == 'circular':
-            self.ui.tipdialabel.hide()
-            self.ui.tipdia_spinner.hide()
-            self.ui.tipanglelabel.hide()
-            self.ui.tipangle_spinner.hide()
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_spinner.hide()
-            self.ui.iso_tool_dia_entry.setDisabled(False)
-            # update the value in the self.iso_tool_dia_entry once this is selected
-            self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia'])
-        else:
-            self.ui.tipdialabel.show()
-            self.ui.tipdia_spinner.show()
-            self.ui.tipanglelabel.show()
-            self.ui.tipangle_spinner.show()
-            self.ui.cutzlabel.show()
-            self.ui.cutz_spinner.show()
-            self.ui.iso_tool_dia_entry.setDisabled(True)
-            # update the value in the self.iso_tool_dia_entry once this is selected
-            self.on_calculate_tooldia()
-
-    def build_ui(self):
-        FlatCAMObj.build_ui(self)
-
-        if self.ui.aperture_table_visibility_cb.get_value() and self.ui_build is False:
-            self.ui_build = True
-
-            try:
-                # if connected, disconnect the signal from the slot on item_changed as it creates issues
-                self.ui.apertures_table.itemChanged.disconnect()
-            except (TypeError, AttributeError):
-                pass
-
-            self.apertures_row = 0
-            aper_no = self.apertures_row + 1
-            sort = []
-            for k, v in list(self.apertures.items()):
-                sort.append(int(k))
-            sorted_apertures = sorted(sort)
-
-            n = len(sorted_apertures)
-            self.ui.apertures_table.setRowCount(n)
-
-            for ap_code in sorted_apertures:
-                ap_code = str(ap_code)
-
-                ap_id_item = QtWidgets.QTableWidgetItem('%d' % int(self.apertures_row + 1))
-                ap_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-                self.ui.apertures_table.setItem(self.apertures_row, 0, ap_id_item)  # Tool name/id
-
-                ap_code_item = QtWidgets.QTableWidgetItem(ap_code)
-                ap_code_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-                ap_type_item = QtWidgets.QTableWidgetItem(str(self.apertures[ap_code]['type']))
-                ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-                if str(self.apertures[ap_code]['type']) == 'R' or str(self.apertures[ap_code]['type']) == 'O':
-                    ap_dim_item = QtWidgets.QTableWidgetItem(
-                        '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['width'],
-                                        self.decimals, self.apertures[ap_code]['height']
-                                        )
-                    )
-                    ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
-                elif str(self.apertures[ap_code]['type']) == 'P':
-                    ap_dim_item = QtWidgets.QTableWidgetItem(
-                        '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['diam'],
-                                        self.decimals, self.apertures[ap_code]['nVertices'])
-                    )
-                    ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
-                else:
-                    ap_dim_item = QtWidgets.QTableWidgetItem('')
-                    ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-                try:
-                    if self.apertures[ap_code]['size'] is not None:
-                        ap_size_item = QtWidgets.QTableWidgetItem(
-                            '%.*f' % (self.decimals, float(self.apertures[ap_code]['size'])))
-                    else:
-                        ap_size_item = QtWidgets.QTableWidgetItem('')
-                except KeyError:
-                    ap_size_item = QtWidgets.QTableWidgetItem('')
-                ap_size_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-                mark_item = FCCheckBox()
-                mark_item.setLayoutDirection(QtCore.Qt.RightToLeft)
-                # if self.ui.aperture_table_visibility_cb.isChecked():
-                #     mark_item.setChecked(True)
-
-                self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item)  # Aperture Code
-                self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item)  # Aperture Type
-                self.ui.apertures_table.setItem(self.apertures_row, 3, ap_size_item)   # Aperture Dimensions
-                self.ui.apertures_table.setItem(self.apertures_row, 4, ap_dim_item)   # Aperture Dimensions
-
-                empty_plot_item = QtWidgets.QTableWidgetItem('')
-                empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-                self.ui.apertures_table.setItem(self.apertures_row, 5, empty_plot_item)
-                self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item)
-
-                self.apertures_row += 1
-
-            self.ui.apertures_table.selectColumn(0)
-            self.ui.apertures_table.resizeColumnsToContents()
-            self.ui.apertures_table.resizeRowsToContents()
-
-            vertical_header = self.ui.apertures_table.verticalHeader()
-            # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
-            vertical_header.hide()
-            self.ui.apertures_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-            horizontal_header = self.ui.apertures_table.horizontalHeader()
-            horizontal_header.setMinimumSectionSize(10)
-            horizontal_header.setDefaultSectionSize(70)
-            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-            horizontal_header.resizeSection(0, 27)
-            horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
-            horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
-            horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
-            horizontal_header.setSectionResizeMode(4,  QtWidgets.QHeaderView.Stretch)
-            horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed)
-            horizontal_header.resizeSection(5, 17)
-            self.ui.apertures_table.setColumnWidth(5, 17)
-
-            self.ui.apertures_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-            self.ui.apertures_table.setSortingEnabled(False)
-            self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight())
-            self.ui.apertures_table.setMaximumHeight(self.ui.apertures_table.getHeight())
-
-            # update the 'mark' checkboxes state according with what is stored in the self.marked_rows list
-            if self.marked_rows:
-                for row in range(self.ui.apertures_table.rowCount()):
-                    try:
-                        self.ui.apertures_table.cellWidget(row, 5).set_value(self.marked_rows[row])
-                    except IndexError:
-                        pass
-
-            self.ui_connect()
-
-    def ui_connect(self):
-        for row in range(self.ui.apertures_table.rowCount()):
-            try:
-                self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect(self.on_mark_cb_click_table)
-            except (TypeError, AttributeError):
-                pass
-            self.ui.apertures_table.cellWidget(row, 5).clicked.connect(self.on_mark_cb_click_table)
-
-        try:
-            self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click)
-        except (TypeError, AttributeError):
-            pass
-        self.ui.mark_all_cb.clicked.connect(self.on_mark_all_click)
-
-    def ui_disconnect(self):
-        for row in range(self.ui.apertures_table.rowCount()):
-            try:
-                self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect()
-            except (TypeError, AttributeError):
-                pass
-
-        try:
-            self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click)
-        except (TypeError, AttributeError):
-            pass
-
-    def on_generate_buffer(self):
-        self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry"))
-
-        def buffer_task():
-            with self.app.proc_container.new('%s...' % _("Buffering")):
-                if isinstance(self.solid_geometry, list):
-                    self.solid_geometry = MultiPolygon(self.solid_geometry)
-
-                self.solid_geometry = self.solid_geometry.buffer(0.0000001)
-                self.solid_geometry = self.solid_geometry.buffer(-0.0000001)
-                self.app.inform.emit('[success] %s.' % _("Done"))
-                self.plot_single_object.emit()
-
-        self.app.worker_task.emit({'fcn': buffer_task, 'params': []})
-
-    def on_generatenoncopper_button_click(self, *args):
-        self.app.report_usage("gerber_on_generatenoncopper_button")
-
-        self.read_form()
-        name = self.options["name"] + "_noncopper"
-
-        def geo_init(geo_obj, app_obj):
-            assert isinstance(geo_obj, FlatCAMGeometry)
-            if isinstance(self.solid_geometry, list):
-                try:
-                    self.solid_geometry = MultiPolygon(self.solid_geometry)
-                except Exception:
-                    self.solid_geometry = cascaded_union(self.solid_geometry)
-
-            bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
-            if not self.options["noncopperrounded"]:
-                bounding_box = bounding_box.envelope
-            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."))
-                return "fail"
-            geo_obj.solid_geometry = non_copper
-
-        self.app.new_object("geometry", name, geo_init)
-
-    def on_generatebb_button_click(self, *args):
-        self.app.report_usage("gerber_on_generatebb_button")
-        self.read_form()
-        name = self.options["name"] + "_bbox"
-
-        def geo_init(geo_obj, app_obj):
-            assert isinstance(geo_obj, FlatCAMGeometry)
-
-            if isinstance(self.solid_geometry, list):
-                try:
-                    self.solid_geometry = MultiPolygon(self.solid_geometry)
-                except Exception:
-                    self.solid_geometry = cascaded_union(self.solid_geometry)
-
-            # Bounding box with rounded corners
-            bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"]))
-            if not self.options["bboxrounded"]:  # Remove rounded corners
-                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."))
-                return "fail"
-            geo_obj.solid_geometry = bounding_box
-
-        self.app.new_object("geometry", name, geo_init)
-
-    def on_iso_button_click(self, *args):
-
-        obj = self.app.collection.get_active()
-
-        self.iso_type = 2
-        if self.ui.iso_type_radio.get_value() == 'ext':
-            self.iso_type = 0
-        if self.ui.iso_type_radio.get_value() == 'int':
-            self.iso_type = 1
-
-        def worker_task(iso_obj, app_obj):
-            with self.app.proc_container.new(_("Isolating...")):
-                if self.ui.follow_cb.get_value() is True:
-                    iso_obj.follow_geo()
-                    # in the end toggle the visibility of the origin object so we can see the generated Geometry
-                    iso_obj.ui.plot_cb.toggle()
-                else:
-                    app_obj.report_usage("gerber_on_iso_button")
-                    self.read_form()
-
-                    iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
-                    self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
-
-        self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
-
-    def follow_geo(self, outname=None):
-        """
-        Creates a geometry object "following" the gerber paths.
-
-        :return: None
-        """
-
-        # default_name = self.options["name"] + "_follow"
-        # follow_name = outname or default_name
-
-        if outname is None:
-            follow_name = self.options["name"] + "_follow"
-        else:
-            follow_name = outname
-
-        def follow_init(follow_obj, app):
-            # Propagate options
-            follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-            follow_obj.solid_geometry = self.follow_geometry
-
-        # TODO: Do something if this is None. Offer changing name?
-        try:
-            self.app.new_object("geometry", follow_name, follow_init)
-        except Exception as e:
-            return "Operation failed: %s" % str(e)
-
-    def isolate_handler(self, iso_type, iso_scope):
-
-        if iso_scope == 'all':
-            self.isolate(iso_type=iso_type)
-        else:
-            # 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
-                self.app.ui.grid_snap_btn.trigger()
-            else:
-                self.grid_status_memory = False
-
-            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
-
-            if self.app.is_legacy is False:
-                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.mr)
-
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
-
-    def on_mouse_click_release(self, event):
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            right_button = 2
-            self.app.event_is_dragging = self.app.event_is_dragging
-        else:
-            event_pos = (event.xdata, event.ydata)
-            right_button = 3
-            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
-
-        try:
-            x = float(event_pos[0])
-            y = float(event_pos[1])
-        except TypeError:
-            return
-
-        event_pos = (x, y)
-        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
-        if self.app.grid_status():
-            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-        else:
-            curr_pos = (curr_pos[0], curr_pos[1])
-
-        if event.button == 1:
-            clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
-
-            if self.app.selection_type is not None:
-                self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
-                self.app.selection_type = None
-            elif clicked_poly:
-                if clicked_poly not in self.poly_dict.values():
-                    shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
-                                                        color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                        face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                        visible=True)
-                    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."))
-                    )
-                else:
-                    try:
-                        for k, v in list(self.poly_dict.items()):
-                            if v == clicked_poly:
-                                self.app.tool_shapes.remove(k)
-                                self.poly_dict.pop(k)
-                                break
-                    except TypeError:
-                        return
-                    self.app.inform.emit(
-                        '%s. %s' % (_("Removed polygon"),
-                                    _("Click to add/remove next polygon or right click to start isolation."))
-                    )
-
-                self.app.tool_shapes.redraw()
-            else:
-                self.app.inform.emit(_("No polygon detected under click position."))
-        elif event.button == right_button and self.app.event_is_dragging is False:
-            # restore the Grid snapping if it was active before
-            if self.grid_status_memory is True:
-                self.app.ui.grid_snap_btn.trigger()
-
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.mr)
-
-            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                  self.app.on_mouse_click_release_over_plot)
-
-            self.app.tool_shapes.clear(update=True)
-
-            if self.poly_dict:
-                poly_list = deepcopy(list(self.poly_dict.values()))
-                self.isolate(iso_type=self.iso_type, geometry=poly_list)
-                self.poly_dict.clear()
-            else:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
-
-    def selection_area_handler(self, start_pos, end_pos, sel_type):
-        """
-        :param start_pos: mouse position when the selection LMB click was done
-        :param end_pos: mouse position when the left mouse button is released
-        :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
-        :return:
-        """
-        poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
-
-        # delete previous selection shape
-        self.app.delete_selection_shape()
-
-        added_poly_count = 0
-        try:
-            for geo in self.solid_geometry:
-                if geo not in self.poly_dict.values():
-                    if sel_type is True:
-                        if geo.within(poly_selection):
-                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                                shape=geo,
-                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                                face_color=self.app.defaults[
-                                                                               'global_sel_draw_color'] + 'AF',
-                                                                visible=True)
-                            self.poly_dict[shape_id] = geo
-                            added_poly_count += 1
-                    else:
-                        if poly_selection.intersects(geo):
-                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                                shape=geo,
-                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                                face_color=self.app.defaults[
-                                                                               'global_sel_draw_color'] + 'AF',
-                                                                visible=True)
-                            self.poly_dict[shape_id] = geo
-                            added_poly_count += 1
-        except TypeError:
-            if self.solid_geometry not in self.poly_dict.values():
-                if sel_type is True:
-                    if self.solid_geometry.within(poly_selection):
-                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                            shape=self.solid_geometry,
-                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                            face_color=self.app.defaults[
-                                                                           'global_sel_draw_color'] + 'AF',
-                                                            visible=True)
-                        self.poly_dict[shape_id] = self.solid_geometry
-                        added_poly_count += 1
-                else:
-                    if poly_selection.intersects(self.solid_geometry):
-                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                            shape=self.solid_geometry,
-                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                            face_color=self.app.defaults[
-                                                                           'global_sel_draw_color'] + 'AF',
-                                                            visible=True)
-                        self.poly_dict[shape_id] = self.solid_geometry
-                        added_poly_count += 1
-
-        if added_poly_count > 0:
-            self.app.tool_shapes.redraw()
-            self.app.inform.emit(
-                '%s: %d. %s' % (_("Added polygon"),
-                                int(added_poly_count),
-                                _("Click to add next polygon or right click to start isolation."))
-            )
-        else:
-            self.app.inform.emit(_("No polygon in selection."))
-
-    def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
-                milling_type=None, follow=None, plot=True):
-        """
-        Creates an isolation routing geometry object in the project.
-
-        :param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
-        :param geometry: specific geometry to isolate
-        :param dia: Tool diameter
-        :param passes: Number of tool widths to cut
-        :param overlap: Overlap between passes in fraction of tool diameter
-        :param outname: Base name of the output object
-        :param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
-        :param milling_type: type of milling: conventional or climbing
-        :param follow: Boolean: if to generate a 'follow' geometry
-        :param plot: Boolean: if to plot the resulting geometry object
-        :return: None
-        """
-
-        if geometry is None:
-            work_geo = self.follow_geometry if follow is True else self.solid_geometry
-        else:
-            work_geo = geometry
-
-        if dia is None:
-            dia = float(self.options["isotooldia"])
-
-        if passes is None:
-            passes = int(self.options["isopasses"])
-
-        if overlap is None:
-            overlap = float(self.options["isooverlap"])
-
-        overlap /= 100.0
-
-        combine = self.options["combine_passes"] if combine is None else bool(combine)
-
-        if milling_type is None:
-            milling_type = self.options["milling_type"]
-
-        if iso_type is None:
-            iso_t = 2
-        else:
-            iso_t = iso_type
-
-        base_name = self.options["name"]
-
-        if combine:
-            if outname is None:
-                if self.iso_type == 0:
-                    iso_name = base_name + "_ext_iso"
-                elif self.iso_type == 1:
-                    iso_name = base_name + "_int_iso"
-                else:
-                    iso_name = base_name + "_iso"
-            else:
-                iso_name = outname
-
-            def iso_init(geo_obj, app_obj):
-                # Propagate options
-                geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-                geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
-
-                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
-                if self.ui.tool_type_radio.get_value() == 'v':
-                    new_cutz = self.ui.cutz_spinner.get_value()
-                    new_vtipdia = self.ui.tipdia_spinner.get_value()
-                    new_vtipangle = self.ui.tipangle_spinner.get_value()
-                    tool_type = 'V'
-                else:
-                    new_cutz = self.app.defaults['geometry_cutz']
-                    new_vtipdia = self.app.defaults['geometry_vtipdia']
-                    new_vtipangle = self.app.defaults['geometry_vtipangle']
-                    tool_type = 'C1'
-
-                # store here the default data for Geometry Data
-                default_data = {}
-                default_data.update({
-                    "name": iso_name,
-                    "plot": self.app.defaults['geometry_plot'],
-                    "cutz": new_cutz,
-                    "vtipdia": new_vtipdia,
-                    "vtipangle": new_vtipangle,
-                    "travelz": self.app.defaults['geometry_travelz'],
-                    "feedrate": self.app.defaults['geometry_feedrate'],
-                    "feedrate_z": self.app.defaults['geometry_feedrate_z'],
-                    "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
-                    "dwell": self.app.defaults['geometry_dwell'],
-                    "dwelltime": self.app.defaults['geometry_dwelltime'],
-                    "multidepth": self.app.defaults['geometry_multidepth'],
-                    "ppname_g": self.app.defaults['geometry_ppname_g'],
-                    "depthperpass": self.app.defaults['geometry_depthperpass'],
-                    "extracut": self.app.defaults['geometry_extracut'],
-                    "extracut_length": self.app.defaults['geometry_extracut_length'],
-                    "toolchange": self.app.defaults['geometry_toolchange'],
-                    "toolchangez": self.app.defaults['geometry_toolchangez'],
-                    "endz": self.app.defaults['geometry_endz'],
-                    "spindlespeed": self.app.defaults['geometry_spindlespeed'],
-                    "toolchangexy": self.app.defaults['geometry_toolchangexy'],
-                    "startz": self.app.defaults['geometry_startz']
-                })
-
-                geo_obj.tools = {}
-                geo_obj.tools['1'] = {}
-                geo_obj.tools.update({
-                    '1': {
-                        'tooldia': float(self.options["isotooldia"]),
-                        'offset': 'Path',
-                        'offset_value': 0.0,
-                        'type': _('Rough'),
-                        'tool_type': tool_type,
-                        'data': default_data,
-                        'solid_geometry': geo_obj.solid_geometry
-                    }
-                })
-
-                for nr_pass in range(passes):
-                    iso_offset = dia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * dia)
-
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = 1 if milling_type == 'cl' else 0
-                    geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                  follow=follow, nr_passes=nr_pass)
-
-                    if geom == 'fail':
-                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                        return 'fail'
-                    geo_obj.solid_geometry.append(geom)
-
-                    # update the geometry in the tools
-                    geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
-
-                # detect if solid_geometry is empty and this require list flattening which is "heavy"
-                # or just looking in the lists (they are one level depth) and if any is not empty
-                # proceed with object creation, if there are empty and the number of them is the length
-                # of the list then we have an empty solid_geometry which should raise a Custom Exception
-                empty_cnt = 0
-                if not isinstance(geo_obj.solid_geometry, list) and \
-                        not isinstance(geo_obj.solid_geometry, MultiPolygon):
-                    geo_obj.solid_geometry = [geo_obj.solid_geometry]
-
-                for g in geo_obj.solid_geometry:
-                    if g:
-                        break
-                    else:
-                        empty_cnt += 1
-
-                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"]))
-
-                # even if combine is checked, one pass is still single-geo
-                geo_obj.multigeo = True if passes > 1 else False
-
-                # ############################################################
-                # ########## AREA SUBTRACTION ################################
-                # ############################################################
-                if self.ui.except_cb.get_value():
-                    self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                    geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
-
-            # TODO: Do something if this is None. Offer changing name?
-            self.app.new_object("geometry", iso_name, iso_init, plot=plot)
-        else:
-            for i in range(passes):
-
-                offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
-                if passes > 1:
-                    if outname is None:
-                        if self.iso_type == 0:
-                            iso_name = base_name + "_ext_iso" + str(i + 1)
-                        elif self.iso_type == 1:
-                            iso_name = base_name + "_int_iso" + str(i + 1)
-                        else:
-                            iso_name = base_name + "_iso" + str(i + 1)
-                    else:
-                        iso_name = outname
-                else:
-                    if outname is None:
-                        if self.iso_type == 0:
-                            iso_name = base_name + "_ext_iso"
-                        elif self.iso_type == 1:
-                            iso_name = base_name + "_int_iso"
-                        else:
-                            iso_name = base_name + "_iso"
-                    else:
-                        iso_name = outname
-
-                def iso_init(geo_obj, app_obj):
-                    # Propagate options
-                    geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-                    if self.ui.tool_type_radio.get_value() == 'v':
-                        geo_obj.tool_type = 'V'
-                    else:
-                        geo_obj.tool_type = 'C1'
-
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = 1 if milling_type == 'cl' else 0
-                    geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                  follow=follow,
-                                                  nr_passes=i)
-
-                    if geom == 'fail':
-                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                        return 'fail'
-
-                    geo_obj.solid_geometry = geom
-
-                    # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
-                    # even if the resulting geometry is not multigeo we add the tools dict which will hold the data
-                    # required to be transfered to the Geometry object
-                    if self.ui.tool_type_radio.get_value() == 'v':
-                        new_cutz = self.ui.cutz_spinner.get_value()
-                        new_vtipdia = self.ui.tipdia_spinner.get_value()
-                        new_vtipangle = self.ui.tipangle_spinner.get_value()
-                        tool_type = 'V'
-                    else:
-                        new_cutz = self.app.defaults['geometry_cutz']
-                        new_vtipdia = self.app.defaults['geometry_vtipdia']
-                        new_vtipangle = self.app.defaults['geometry_vtipangle']
-                        tool_type = 'C1'
-
-                    # store here the default data for Geometry Data
-                    default_data = {}
-                    default_data.update({
-                        "name": iso_name,
-                        "plot": self.app.defaults['geometry_plot'],
-                        "cutz": new_cutz,
-                        "vtipdia": new_vtipdia,
-                        "vtipangle": new_vtipangle,
-                        "travelz": self.app.defaults['geometry_travelz'],
-                        "feedrate": self.app.defaults['geometry_feedrate'],
-                        "feedrate_z": self.app.defaults['geometry_feedrate_z'],
-                        "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
-                        "dwell": self.app.defaults['geometry_dwell'],
-                        "dwelltime": self.app.defaults['geometry_dwelltime'],
-                        "multidepth": self.app.defaults['geometry_multidepth'],
-                        "ppname_g": self.app.defaults['geometry_ppname_g'],
-                        "depthperpass": self.app.defaults['geometry_depthperpass'],
-                        "extracut": self.app.defaults['geometry_extracut'],
-                        "extracut_length": self.app.defaults['geometry_extracut_length'],
-                        "toolchange": self.app.defaults['geometry_toolchange'],
-                        "toolchangez": self.app.defaults['geometry_toolchangez'],
-                        "endz": self.app.defaults['geometry_endz'],
-                        "spindlespeed": self.app.defaults['geometry_spindlespeed'],
-                        "toolchangexy": self.app.defaults['geometry_toolchangexy'],
-                        "startz": self.app.defaults['geometry_startz']
-                    })
-
-                    geo_obj.tools = {}
-                    geo_obj.tools['1'] = {}
-                    geo_obj.tools.update({
-                        '1': {
-                            'tooldia': float(self.options["isotooldia"]),
-                            'offset': 'Path',
-                            'offset_value': 0.0,
-                            'type': _('Rough'),
-                            'tool_type': tool_type,
-                            'data': default_data,
-                            'solid_geometry': geo_obj.solid_geometry
-                        }
-                    })
-
-                    # detect if solid_geometry is empty and this require list flattening which is "heavy"
-                    # or just looking in the lists (they are one level depth) and if any is not empty
-                    # proceed with object creation, if there are empty and the number of them is the length
-                    # of the list then we have an empty solid_geometry which should raise a Custom Exception
-                    empty_cnt = 0
-                    if not isinstance(geo_obj.solid_geometry, list):
-                        geo_obj.solid_geometry = [geo_obj.solid_geometry]
-
-                    for g in geo_obj.solid_geometry:
-                        if g:
-                            break
-                        else:
-                            empty_cnt += 1
-
-                    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"]))
-                    geo_obj.multigeo = False
-
-                    # ############################################################
-                    # ########## AREA SUBTRACTION ################################
-                    # ############################################################
-                    if self.ui.except_cb.get_value():
-                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                        geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
-
-                # TODO: Do something if this is None. Offer changing name?
-                self.app.new_object("geometry", iso_name, iso_init, plot=plot)
-
-    def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
-        # isolation_geometry produces an envelope that is going on the left of the geometry
-        # (the copper features). To leave the least amount of burrs on the features
-        # the tool needs to travel on the right side of the features (this is called conventional milling)
-        # the first pass is the one cutting all of the features, so it needs to be reversed
-        # the other passes overlap preceding ones and cut the left over copper. It is better for them
-        # to cut on the right side of the left over copper i.e on the left side of the features.
-
-        if follow:
-            geom = self.isolation_geometry(offset, geometry=geometry, follow=follow)
-        else:
-            try:
-                geom = self.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type, passes=nr_passes)
-            except Exception as e:
-                log.debug('FlatCAMGerber.isolate().generate_envelope() --> %s' % str(e))
-                return 'fail'
-
-        if invert:
-            try:
-                pl = []
-                for p in geom:
-                    if p is not None:
-                        if isinstance(p, Polygon):
-                            pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
-                        elif isinstance(p, LinearRing):
-                            pl.append(Polygon(p.coords[::-1]))
-                geom = MultiPolygon(pl)
-            except TypeError:
-                if isinstance(geom, Polygon) and geom is not None:
-                    geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
-                elif isinstance(geom, LinearRing) and geom is not None:
-                    geom = Polygon(geom.coords[::-1])
-                else:
-                    log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" %
-                              type(geom))
-            except Exception as e:
-                log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> %s" % str(e))
-                return 'fail'
-        return geom
-
-    def area_subtraction(self, geo, subtractor_geo=None):
-        """
-        Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
-
-        :param geo: target geometry from which to subtract
-        :param subtractor_geo: geometry that acts as subtractor
-        :return:
-        """
-        new_geometry = []
-        target_geo = geo
-
-        if subtractor_geo:
-            sub_union = cascaded_union(subtractor_geo)
-        else:
-            name = self.ui.obj_combo.currentText()
-            subtractor_obj = self.app.collection.get_by_name(name)
-            sub_union = cascaded_union(subtractor_obj.solid_geometry)
-
-        try:
-            for geo_elem in target_geo:
-                if isinstance(geo_elem, Polygon):
-                    for ring in self.poly2rings(geo_elem):
-                        new_geo = ring.difference(sub_union)
-                        if new_geo and not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-                elif isinstance(geo_elem, MultiPolygon):
-                    for poly in geo_elem:
-                        for ring in self.poly2rings(poly):
-                            new_geo = ring.difference(sub_union)
-                            if new_geo and not new_geo.is_empty:
-                                new_geometry.append(new_geo)
-                elif isinstance(geo_elem, LineString):
-                    new_geo = geo_elem.difference(sub_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-                elif isinstance(geo_elem, MultiLineString):
-                    for line_elem in geo_elem:
-                        new_geo = line_elem.difference(sub_union)
-                        if new_geo and not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-        except TypeError:
-            if isinstance(target_geo, Polygon):
-                for ring in self.poly2rings(target_geo):
-                    new_geo = ring.difference(sub_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-            elif isinstance(target_geo, LineString):
-                new_geo = target_geo.difference(sub_union)
-                if new_geo and not new_geo.is_empty:
-                    new_geometry.append(new_geo)
-            elif isinstance(target_geo, MultiLineString):
-                for line_elem in target_geo:
-                    new_geo = line_elem.difference(sub_union)
-                    if new_geo and not new_geo.is_empty:
-                        new_geometry.append(new_geo)
-        return new_geometry
-
-    def on_plot_cb_click(self, *args):
-        if self.muted_ui:
-            return
-        self.read_form_item('plot')
-        self.plot()
-
-    def on_solid_cb_click(self, *args):
-        if self.muted_ui:
-            return
-        self.read_form_item('solid')
-        self.plot()
-
-    def on_multicolored_cb_click(self, *args):
-        if self.muted_ui:
-            return
-        self.read_form_item('multicolored')
-        self.plot()
-
-    def on_follow_cb_click(self):
-        if self.muted_ui:
-            return
-        self.plot()
-
-    def on_aperture_table_visibility_change(self):
-        if self.ui.aperture_table_visibility_cb.isChecked():
-            # add the shapes storage for marking apertures
-            if self.build_aperture_storage is False:
-                self.build_aperture_storage = True
-
-                if self.app.is_legacy is False:
-                    for ap_code in self.apertures:
-                        self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=1)
-                else:
-                    for ap_code in self.apertures:
-                        self.mark_shapes[ap_code] = ShapeCollectionLegacy(obj=self, app=self.app,
-                                                                          name=self.options['name'] + str(ap_code))
-
-            self.ui.apertures_table.setVisible(True)
-            for ap in self.mark_shapes:
-                self.mark_shapes[ap].enabled = True
-
-            self.ui.mark_all_cb.setVisible(True)
-            self.ui.mark_all_cb.setChecked(False)
-            self.build_ui()
-        else:
-            self.ui.apertures_table.setVisible(False)
-
-            self.ui.mark_all_cb.setVisible(False)
-
-            # on hide disable all mark plots
-            try:
-                for row in range(self.ui.apertures_table.rowCount()):
-                    self.ui.apertures_table.cellWidget(row, 5).set_value(False)
-                self.clear_plot_apertures()
-
-                # for ap in list(self.mark_shapes.keys()):
-                #     # self.mark_shapes[ap].enabled = False
-                #     del self.mark_shapes[ap]
-            except Exception as e:
-                log.debug(" FlatCAMGerber.on_aperture_visibility_changed() --> %s" % str(e))
-
-    def convert_units(self, units):
-        """
-        Converts the units of the object by scaling dimensions in all geometry
-        and options.
-
-        :param units: Units to which to convert the object: "IN" or "MM".
-        :type units: str
-        :return: None
-        :rtype: None
-        """
-
-        # units conversion to get a conversion should be done only once even if we found multiple
-        # units declaration inside a Gerber file (it can happen to find also the obsolete declaration)
-        if self.conversion_done is True:
-            log.debug("Gerber units conversion cancelled. Already done.")
-            return
-
-        log.debug("FlatCAMObj.FlatCAMGerber.convert_units()")
-
-        factor = Gerber.convert_units(self, units)
-
-        # self.options['isotooldia'] = float(self.options['isotooldia']) * factor
-        # self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor
-
-    def plot(self, kind=None, **kwargs):
-        """
-
-        :param kind:    Not used, for compatibility with the plot method for other objects
-        :param kwargs:  Color and face_color, visible
-        :return:
-        """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot()")
-
-        # Does all the required setup and returns False
-        # if the 'ptint' option is set to False.
-        if not FlatCAMObj.plot(self):
-            return
-
-        if 'color' in kwargs:
-            color = kwargs['color']
-        else:
-            color = self.outline_color
-
-        if 'face_color' in kwargs:
-            face_color = kwargs['face_color']
-        else:
-            face_color = self.fill_color
-
-        if 'visible' not in kwargs:
-            visible = self.options['plot']
-        else:
-            visible = kwargs['visible']
-
-        # if the Follow Geometry checkbox is checked then plot only the follow geometry
-        if self.ui.follow_cb.get_value():
-            geometry = self.follow_geometry
-        else:
-            geometry = self.solid_geometry
-
-        # Make sure geometry is iterable.
-        try:
-            __ = iter(geometry)
-        except TypeError:
-            geometry = [geometry]
-
-        if self.app.is_legacy is False:
-            def random_color():
-                r_color = np.random.rand(4)
-                r_color[3] = 1
-                return r_color
-        else:
-            def random_color():
-                while True:
-                    r_color = np.random.rand(4)
-                    r_color[3] = 1
-
-                    new_color = '#'
-                    for idx in range(len(r_color)):
-                        new_color += '%x' % int(r_color[idx] * 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
-                    if len(new_color) == 9:
-                        break
-                return new_color
-
-        try:
-            if self.options["solid"]:
-                for g in geometry:
-                    if type(g) == Polygon or type(g) == LineString:
-                        self.add_shape(shape=g, color=color,
-                                       face_color=random_color() if self.options['multicolored']
-                                       else face_color, visible=visible)
-                    elif type(g) == Point:
-                        pass
-                    else:
-                        try:
-                            for el in g:
-                                self.add_shape(shape=el, color=color,
-                                               face_color=random_color() if self.options['multicolored']
-                                               else face_color, visible=visible)
-                        except TypeError:
-                            self.add_shape(shape=g, color=color,
-                                           face_color=random_color() if self.options['multicolored']
-                                           else face_color, visible=visible)
-            else:
-                for g in geometry:
-                    if type(g) == Polygon or type(g) == LineString:
-                        self.add_shape(shape=g, color=random_color() if self.options['multicolored'] else 'black',
-                                       visible=visible)
-                    elif type(g) == Point:
-                        pass
-                    else:
-                        for el in g:
-                            self.add_shape(shape=el, color=random_color() if self.options['multicolored'] else 'black',
-                                           visible=visible)
-            self.shapes.redraw(
-                # update_colors=(self.fill_color, self.outline_color),
-                # indexes=self.app.plotcanvas.shape_collection.data.keys()
-            )
-        except (ObjectDeleted, AttributeError):
-            self.shapes.clear(update=True)
-        except Exception as e:
-            log.debug("FlatCAMGerber.plot() --> %s" % str(e))
-
-    # experimental plot() when the solid_geometry is stored in the self.apertures
-    def plot_aperture(self, run_thread=True, **kwargs):
-        """
-
-        :param run_thread: if True run the aperture plot as a thread in a worker
-        :param kwargs: color and face_color
-        :return:
-        """
-
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot_aperture()")
-
-        # Does all the required setup and returns False
-        # if the 'ptint' option is set to False.
-        # if not FlatCAMObj.plot(self):
-        #     return
-
-        # for marking apertures, line color and fill color are the same
-        if 'color' in kwargs:
-            color = kwargs['color']
-        else:
-            color = self.app.defaults['gerber_plot_fill']
-
-        if 'marked_aperture' not in kwargs:
-            return
-        else:
-            aperture_to_plot_mark = kwargs['marked_aperture']
-            if aperture_to_plot_mark is None:
-                return
-
-        if 'visible' not in kwargs:
-            visibility = True
-        else:
-            visibility = kwargs['visible']
-
-        with self.app.proc_container.new(_("Plotting Apertures")):
-
-            def job_thread(app_obj):
-                try:
-                    if aperture_to_plot_mark in self.apertures:
-                        for elem in self.apertures[aperture_to_plot_mark]['geometry']:
-                            if 'solid' in elem:
-                                geo = elem['solid']
-                                if type(geo) == Polygon or type(geo) == LineString:
-                                    self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color,
-                                                        face_color=color, visible=visibility)
-                                else:
-                                    for el in geo:
-                                        self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color,
-                                                            face_color=color, visible=visibility)
-
-                    self.mark_shapes[aperture_to_plot_mark].redraw()
-
-                except (ObjectDeleted, AttributeError):
-                    self.clear_plot_apertures()
-                except Exception as e:
-                    log.debug("FlatCAMGerber.plot_aperture() --> %s" % str(e))
-
-            if run_thread:
-                self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
-            else:
-                job_thread(self)
-
-    def clear_plot_apertures(self, aperture='all'):
-        """
-
-        :param aperture: string; aperture for which to clear the mark shapes
-        :return:
-        """
-
-        if self.mark_shapes:
-            if aperture == 'all':
-                for apid in list(self.apertures.keys()):
-                    try:
-                        if self.app.is_legacy is True:
-                            self.mark_shapes[apid].clear(update=False)
-                        else:
-                            self.mark_shapes[apid].clear(update=True)
-                    except Exception as e:
-                        log.debug("FlatCAMGerber.clear_plot_apertures() 'all' --> %s" % str(e))
-            else:
-                try:
-                    if self.app.is_legacy is True:
-                        self.mark_shapes[aperture].clear(update=False)
-                    else:
-                        self.mark_shapes[aperture].clear(update=True)
-                except Exception as e:
-                    log.debug("FlatCAMGerber.clear_plot_apertures() 'aperture' --> %s" % str(e))
-
-    def clear_mark_all(self):
-        self.ui.mark_all_cb.set_value(False)
-        self.marked_rows[:] = []
-
-    def on_mark_cb_click_table(self):
-        """
-        Will mark aperture geometries on canvas or delete the markings depending on the checkbox state
-        :return:
-        """
-
-        self.ui_disconnect()
-        cw = self.sender()
-        try:
-            cw_index = self.ui.apertures_table.indexAt(cw.pos())
-            cw_row = cw_index.row()
-        except AttributeError:
-            cw_row = 0
-        except TypeError:
-            return
-
-        self.marked_rows[:] = []
-
-        try:
-            aperture = self.ui.apertures_table.item(cw_row, 1).text()
-        except AttributeError:
-            return
-
-        if self.ui.apertures_table.cellWidget(cw_row, 5).isChecked():
-            self.marked_rows.append(True)
-            # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
-            self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                               marked_aperture=aperture, visible=True, run_thread=True)
-            # self.mark_shapes[aperture].redraw()
-        else:
-            self.marked_rows.append(False)
-            self.clear_plot_apertures(aperture=aperture)
-
-        # make sure that the Mark All is disabled if one of the row mark's are disabled and
-        # if all the row mark's are enabled also enable the Mark All checkbox
-        cb_cnt = 0
-        total_row = self.ui.apertures_table.rowCount()
-        for row in range(total_row):
-            if self.ui.apertures_table.cellWidget(row, 5).isChecked():
-                cb_cnt += 1
-            else:
-                cb_cnt -= 1
-        if cb_cnt < total_row:
-            self.ui.mark_all_cb.setChecked(False)
-        else:
-            self.ui.mark_all_cb.setChecked(True)
-        self.ui_connect()
-
-    def on_mark_all_click(self):
-        self.ui_disconnect()
-        mark_all = self.ui.mark_all_cb.isChecked()
-        for row in range(self.ui.apertures_table.rowCount()):
-            # update the mark_rows list
-            if mark_all:
-                self.marked_rows.append(True)
-            else:
-                self.marked_rows[:] = []
-
-            mark_cb = self.ui.apertures_table.cellWidget(row, 5)
-            mark_cb.setChecked(mark_all)
-
-        if mark_all:
-            for aperture in self.apertures:
-                # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
-                self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                   marked_aperture=aperture, visible=True)
-            # HACK: enable/disable the grid for a better look
-            self.app.ui.grid_snap_btn.trigger()
-            self.app.ui.grid_snap_btn.trigger()
-        else:
-            self.clear_plot_apertures()
-            self.marked_rows[:] = []
-
-        self.ui_connect()
-
-    def export_gerber(self, whole, fract, g_zeros='L', factor=1):
-        """
-        Creates a Gerber file content to be exported to a file.
-
-        :param whole: how many digits in the whole part of coordinates
-        :param fract: how many decimals in coordinates
-        :param g_zeros: type of the zero suppression used: LZ or TZ; string
-        :param factor: factor to be applied onto the Gerber coordinates
-        :return: Gerber_code
-        """
-        log.debug("FlatCAMGerber.export_gerber() --> Generating the Gerber code from the selected Gerber file")
-
-        def tz_format(x, y, fac):
-            x_c = x * fac
-            y_c = y * fac
-
-            x_form = "{:.{dec}f}".format(x_c, dec=fract)
-            y_form = "{:.{dec}f}".format(y_c, dec=fract)
-
-            # extract whole part and decimal part
-            x_form = x_form.partition('.')
-            y_form = y_form.partition('.')
-
-            # left padd the 'whole' part with zeros
-            x_whole = x_form[0].rjust(whole, '0')
-            y_whole = y_form[0].rjust(whole, '0')
-
-            # restore the coordinate padded in the left with 0 and added the decimal part
-            # without the decinal dot
-            x_form = x_whole + x_form[2]
-            y_form = y_whole + y_form[2]
-            return x_form, y_form
-
-        def lz_format(x, y, fac):
-            x_c = x * fac
-            y_c = y * fac
-
-            x_form = "{:.{dec}f}".format(x_c, dec=fract).replace('.', '')
-            y_form = "{:.{dec}f}".format(y_c, dec=fract).replace('.', '')
-
-            # pad with rear zeros
-            x_form.ljust(length, '0')
-            y_form.ljust(length, '0')
-
-            return x_form, y_form
-
-        # Gerber code is stored here
-        gerber_code = ''
-
-        # apertures processing
-        try:
-            length = whole + fract
-            if '0' in self.apertures:
-                if 'geometry' in self.apertures['0']:
-                    for geo_elem in self.apertures['0']['geometry']:
-                        if 'solid' in geo_elem:
-                            geo = geo_elem['solid']
-                            if not geo.is_empty:
-                                gerber_code += 'G36*\n'
-                                geo_coords = list(geo.exterior.coords)
-                                # first command is a move with pen-up D02 at the beginning of the geo
-                                if g_zeros == 'T':
-                                    x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                                else:
-                                    x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                                for coord in geo_coords[1:]:
-                                    if g_zeros == 'T':
-                                        x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                       yform=y_formatted)
-                                    else:
-                                        x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                       yform=y_formatted)
-                                gerber_code += 'D02*\n'
-                                gerber_code += 'G37*\n'
-
-                                clear_list = list(geo.interiors)
-                                if clear_list:
-                                    gerber_code += '%LPC*%\n'
-                                    for clear_geo in clear_list:
-                                        gerber_code += 'G36*\n'
-                                        geo_coords = list(clear_geo.coords)
-
-                                        # first command is a move with pen-up D02 at the beginning of the geo
-                                        if g_zeros == 'T':
-                                            x_formatted, y_formatted = tz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                        else:
-                                            x_formatted, y_formatted = lz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-
-                                        prev_coord = geo_coords[0]
-                                        for coord in geo_coords[1:]:
-                                            if coord != prev_coord:
-                                                if g_zeros == 'T':
-                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-                                                else:
-                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-                                            prev_coord = coord
-
-                                        gerber_code += 'D02*\n'
-                                        gerber_code += 'G37*\n'
-                                    gerber_code += '%LPD*%\n'
-                        if 'clear' in geo_elem:
-                            geo = geo_elem['clear']
-                            if not geo.is_empty:
-                                gerber_code += '%LPC*%\n'
-                                gerber_code += 'G36*\n'
-                                geo_coords = list(geo.exterior.coords)
-                                # first command is a move with pen-up D02 at the beginning of the geo
-                                if g_zeros == 'T':
-                                    x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                                else:
-                                    x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-
-                                prev_coord = geo_coords[0]
-                                for coord in geo_coords[1:]:
-                                    if coord != prev_coord:
-                                        if g_zeros == 'T':
-                                            x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                            gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                        else:
-                                            x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                            gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                    prev_coord = coord
-
-                                gerber_code += 'D02*\n'
-                                gerber_code += 'G37*\n'
-                                gerber_code += '%LPD*%\n'
-        except Exception as e:
-            log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() '0' aperture --> %s" % str(e))
-
-        for apid in self.apertures:
-            if apid == '0':
-                continue
-            else:
-                gerber_code += 'D%s*\n' % str(apid)
-                if 'geometry' in self.apertures[apid]:
-                    for geo_elem in self.apertures[apid]['geometry']:
-                        try:
-                            if 'follow' in geo_elem:
-                                geo = geo_elem['follow']
-                                if not geo.is_empty:
-                                    if isinstance(geo, Point):
-                                        if g_zeros == 'T':
-                                            x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
-                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                        else:
-                                            x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
-                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                    else:
-                                        geo_coords = list(geo.coords)
-                                        # first command is a move with pen-up D02 at the beginning of the geo
-                                        if g_zeros == 'T':
-                                            x_formatted, y_formatted = tz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                        else:
-                                            x_formatted, y_formatted = lz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-
-                                        prev_coord = geo_coords[0]
-                                        for coord in geo_coords[1:]:
-                                            if coord != prev_coord:
-                                                if g_zeros == 'T':
-                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-                                                else:
-                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-                                            prev_coord = coord
-
-                                        # gerber_code += "D02*\n"
-                        except Exception as e:
-                            log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() 'follow' --> %s" % str(e))
-
-                        try:
-                            if 'clear' in geo_elem:
-                                gerber_code += '%LPC*%\n'
-
-                                geo = geo_elem['clear']
-                                if not geo.is_empty:
-                                    if isinstance(geo, Point):
-                                        if g_zeros == 'T':
-                                            x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
-                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                        else:
-                                            x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
-                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                    elif isinstance(geo, Polygon):
-                                        geo_coords = list(geo.exterior.coords)
-                                        # first command is a move with pen-up D02 at the beginning of the geo
-                                        if g_zeros == 'T':
-                                            x_formatted, y_formatted = tz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                        else:
-                                            x_formatted, y_formatted = lz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-
-                                        prev_coord = geo_coords[0]
-                                        for coord in geo_coords[1:]:
-                                            if coord != prev_coord:
-                                                if g_zeros == 'T':
-                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-                                                else:
-                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-
-                                            prev_coord = coord
-
-                                        for geo_int in geo.interiors:
-                                            geo_coords = list(geo_int.coords)
-                                            # first command is a move with pen-up D02 at the beginning of the geo
-                                            if g_zeros == 'T':
-                                                x_formatted, y_formatted = tz_format(
-                                                    geo_coords[0][0], geo_coords[0][1], factor)
-                                                gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                               yform=y_formatted)
-                                            else:
-                                                x_formatted, y_formatted = lz_format(
-                                                    geo_coords[0][0], geo_coords[0][1], factor)
-                                                gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                               yform=y_formatted)
-
-                                            prev_coord = geo_coords[0]
-                                            for coord in geo_coords[1:]:
-                                                if coord != prev_coord:
-                                                    if g_zeros == 'T':
-                                                        x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                                        gerber_code += "X{xform}Y{yform}D01*\n".format(
-                                                            xform=x_formatted,
-                                                            yform=y_formatted)
-                                                    else:
-                                                        x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                                        gerber_code += "X{xform}Y{yform}D01*\n".format(
-                                                            xform=x_formatted,
-                                                            yform=y_formatted)
-
-                                                prev_coord = coord
-                                    else:
-                                        geo_coords = list(geo.coords)
-                                        # first command is a move with pen-up D02 at the beginning of the geo
-                                        if g_zeros == 'T':
-                                            x_formatted, y_formatted = tz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-                                        else:
-                                            x_formatted, y_formatted = lz_format(
-                                                geo_coords[0][0], geo_coords[0][1], factor)
-                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                           yform=y_formatted)
-
-                                        prev_coord = geo_coords[0]
-                                        for coord in geo_coords[1:]:
-                                            if coord != prev_coord:
-                                                if g_zeros == 'T':
-                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-                                                else:
-                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                                   yform=y_formatted)
-
-                                            prev_coord = coord
-                                        # gerber_code += "D02*\n"
-                                    gerber_code += '%LPD*%\n'
-                        except Exception as e:
-                            log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() 'clear' --> %s" % str(e))
-
-        if not self.apertures:
-            log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> Gerber Object is empty: no apertures.")
-            return 'fail'
-
-        return gerber_code
-
-    def mirror(self, axis, point):
-        Gerber.mirror(self, axis=axis, point=point)
-        self.replotApertures.emit()
-
-    def offset(self, vect):
-        Gerber.offset(self, vect=vect)
-        self.replotApertures.emit()
-
-    def rotate(self, angle, point):
-        Gerber.rotate(self, angle=angle, point=point)
-        self.replotApertures.emit()
-
-    def scale(self, xfactor, yfactor=None, point=None):
-        Gerber.scale(self, xfactor=xfactor, yfactor=yfactor, point=point)
-        self.replotApertures.emit()
-
-    def skew(self, angle_x, angle_y, point):
-        Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
-        self.replotApertures.emit()
-
-    def buffer(self, distance, join, factor=None):
-        Gerber.buffer(self, distance=distance, join=join, factor=factor)
-        self.replotApertures.emit()
-
-    def serialize(self):
-        return {
-            "options": self.options,
-            "kind": self.kind
-        }
-
-
-class FlatCAMExcellon(FlatCAMObj, Excellon):
-    """
-    Represents Excellon/Drill code.
-    """
-
-    ui_type = ExcellonObjectUI
-    optionChanged = QtCore.pyqtSignal(str)
-
-    def __init__(self, name):
-        self.decimals = self.app.decimals
-
-        self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
-
-        Excellon.__init__(self, geo_steps_per_circle=self.circle_steps)
-        FlatCAMObj.__init__(self, name)
-
-        self.kind = "excellon"
-
-        self.options.update({
-            "plot": True,
-            "solid": False,
-
-            "operation": "drill",
-            "milling_type": "drills",
-
-            "milling_dia": 0.04,
-
-            "cutz": -0.1,
-            "multidepth": False,
-            "depthperpass": 0.7,
-            "travelz": 0.1,
-            "feedrate": self.app.defaults["geometry_feedrate"],
-            "feedrate_z": 5.0,
-            "feedrate_rapid": 5.0,
-            "tooldia": 0.1,
-            "slot_tooldia": 0.1,
-            "toolchange": False,
-            "toolchangez": 1.0,
-            "toolchangexy": "0.0, 0.0",
-            "extracut": self.app.defaults["geometry_extracut"],
-            "extracut_length": self.app.defaults["geometry_extracut_length"],
-            "endz": 2.0,
-            "endxy": '',
-
-            "startz": None,
-            "offset": 0.0,
-            "spindlespeed": 0,
-            "dwell": True,
-            "dwelltime": 1000,
-            "ppname_e": 'default',
-            "ppname_g": self.app.defaults["geometry_ppname_g"],
-            "z_pdepth": -0.02,
-            "feedrate_probe": 3.0,
-            "optimization_type": "B",
-        })
-
-        # TODO: Document this.
-        self.tool_cbs = {}
-
-        # dict that holds the object names and the option name
-        # the key is the object name (defines in ObjectUI) for each UI element that is a parameter
-        # particular for a tool and the value is the actual name of the option that the UI element is changing
-        self.name2option = {}
-
-        # default set of data to be added to each tool in self.tools as self.tools[tool]['data'] = self.default_data
-        self.default_data = {}
-
-        # fill in self.default_data values from self.options
-        for opt_key, opt_val in self.app.options.items():
-            if opt_key.find('excellon_') == 0:
-                self.default_data[opt_key] = deepcopy(opt_val)
-        for opt_key, opt_val in self.app.options.items():
-            if opt_key.find('geometry_') == 0:
-                self.default_data[opt_key] = deepcopy(opt_val)
-
-        # variable to store the total amount of drills per job
-        self.tot_drill_cnt = 0
-        self.tool_row = 0
-
-        # variable to store the total amount of slots per job
-        self.tot_slot_cnt = 0
-        self.tool_row_slots = 0
-
-        # variable to store the distance travelled
-        self.travel_distance = 0.0
-
-        # store the source file here
-        self.source_file = ""
-
-        self.multigeo = False
-        self.units_found = self.app.defaults['units']
-
-        self.fill_color = self.app.defaults['excellon_plot_fill']
-        self.outline_color = self.app.defaults['excellon_plot_line']
-        self.alpha_level = 'bf'
-
-        # Attributes to be included in serialization
-        # Always append to it because it carries contents
-        # from predecessors.
-        self.ser_attrs += ['options', 'kind']
-
-    def merge(self, exc_list, exc_final):
-        """
-        Merge Excellon objects found in exc_list parameter into exc_final object.
-        Options are always copied from source .
-
-        Tools are disregarded, what is taken in consideration is the unique drill diameters found as values in the
-        exc_list tools dict's. In the reconstruction section for each unique tool diameter it will be created a
-        tool_name to be used in the final Excellon object, exc_final.
-
-        If only one object is in exc_list parameter then this function will copy that object in the exc_final
-
-        :param exc_list: List or one object of FlatCAMExcellon Objects to join.
-        :param exc_final: Destination FlatCAMExcellon object.
-        :return: None
-        """
-
-        try:
-            decimals_exc = self.decimals
-        except AttributeError:
-            decimals_exc = 4
-
-        # flag to signal that we need to reorder the tools dictionary and drills and slots lists
-        flag_order = False
-
-        try:
-            flattened_list = list(itertools.chain(*exc_list))
-        except TypeError:
-            flattened_list = exc_list
-
-        # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
-        # values will be list of Shapely Points; for drills
-        custom_dict_drills = {}
-
-        # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
-        # values will be list of Shapely Points; for slots
-        custom_dict_slots = {}
-
-        for exc in flattened_list:
-            # copy options of the current excellon obj to the final excellon obj
-            for option in exc.options:
-                if option != 'name':
-                    try:
-                        exc_final.options[option] = exc.options[option]
-                    except Exception:
-                        exc.app.log.warning("Failed to copy option.", option)
-
-            for drill in exc.drills:
-                exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[drill['tool']]['C']))
-
-                if exc_tool_dia not in custom_dict_drills:
-                    custom_dict_drills[exc_tool_dia] = [drill['point']]
-                else:
-                    custom_dict_drills[exc_tool_dia].append(drill['point'])
-
-            for slot in exc.slots:
-                exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[slot['tool']]['C']))
-
-                if exc_tool_dia not in custom_dict_slots:
-                    custom_dict_slots[exc_tool_dia] = [[slot['start'], slot['stop']]]
-                else:
-                    custom_dict_slots[exc_tool_dia].append([slot['start'], slot['stop']])
-
-            # add the zeros and units to the exc_final object
-            exc_final.zeros = exc.zeros
-            exc_final.units = exc.units
-
-        # ##########################################
-        # Here we add data to the exc_final object #
-        # ##########################################
-
-        # variable to make tool_name for the tools
-        current_tool = 0
-        # The tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of
-        # drills
-        for tool_dia in custom_dict_drills:
-            # we create a tool name for each key in the drill_dia dict (the key is a unique drill diameter)
-            current_tool += 1
-
-            tool_name = str(current_tool)
-            spec = {"C": float(tool_dia)}
-            exc_final.tools[tool_name] = spec
-
-            # rebuild the drills list of dict's that belong to the exc_final object
-            for point in custom_dict_drills[tool_dia]:
-                exc_final.drills.append(
-                    {
-                        "point": point,
-                        "tool": str(current_tool)
-                    }
-                )
-
-        # The tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop])
-        # of two Shapely Points in case of slots
-        for tool_dia in custom_dict_slots:
-            # we create a tool name for each key in the slot_dia dict (the key is a unique slot diameter)
-            # but only if there are no drills
-            if not exc_final.tools:
-                current_tool += 1
-                tool_name = str(current_tool)
-                spec = {"C": float(tool_dia)}
-                exc_final.tools[tool_name] = spec
-            else:
-                dia_list = []
-                for v in exc_final.tools.values():
-                    dia_list.append(float(v["C"]))
-
-                if tool_dia not in dia_list:
-                    flag_order = True
-
-                    current_tool = len(dia_list) + 1
-                    tool_name = str(current_tool)
-                    spec = {"C": float(tool_dia)}
-                    exc_final.tools[tool_name] = spec
-
-                else:
-                    for k, v in exc_final.tools.items():
-                        if v["C"] == tool_dia:
-                            current_tool = int(k)
-                            break
-
-            # rebuild the slots list of dict's that belong to the exc_final object
-            for point in custom_dict_slots[tool_dia]:
-                exc_final.slots.append(
-                    {
-                        "start": point[0],
-                        "stop": point[1],
-                        "tool": str(current_tool)
-                    }
-                )
-
-        # flag_order == True means that there was an slot diameter not in the tools and we also have drills
-        # and the new tool was added to self.tools therefore we need to reorder the tools and drills and slots
-        current_tool = 0
-        if flag_order is True:
-            dia_list = []
-            temp_drills = []
-            temp_slots = []
-            temp_tools = {}
-            for v in exc_final.tools.values():
-                dia_list.append(float(v["C"]))
-            dia_list.sort()
-            for ordered_dia in dia_list:
-                current_tool += 1
-                tool_name_temp = str(current_tool)
-                spec_temp = {"C": float(ordered_dia)}
-                temp_tools[tool_name_temp] = spec_temp
-
-                for drill in exc_final.drills:
-                    exc_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[drill['tool']]['C']))
-                    if exc_tool_dia == ordered_dia:
-                        temp_drills.append(
-                            {
-                                "point": drill["point"],
-                                "tool": str(current_tool)
-                            }
-                        )
-
-                for slot in exc_final.slots:
-                    slot_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[slot['tool']]['C']))
-                    if slot_tool_dia == ordered_dia:
-                        temp_slots.append(
-                            {
-                                "start": slot["start"],
-                                "stop": slot["stop"],
-                                "tool": str(current_tool)
-                            }
-                        )
-
-            # delete the exc_final tools, drills and slots
-            exc_final.tools = {}
-            exc_final.drills[:] = []
-            exc_final.slots[:] = []
-
-            # update the exc_final tools, drills and slots with the ordered values
-            exc_final.tools = temp_tools
-            exc_final.drills[:] = temp_drills
-            exc_final.slots[:] = temp_slots
-
-        # create the geometry for the exc_final object
-        exc_final.create_geometry()
-
-    def build_ui(self):
-        FlatCAMObj.build_ui(self)
-
-        self.units = self.app.defaults['units'].upper()
-
-        for row in range(self.ui.tools_table.rowCount()):
-            try:
-                # if connected, disconnect the signal from the slot on item_changed as it creates issues
-                offset_spin_widget = self.ui.tools_table.cellWidget(row, 4)
-                offset_spin_widget.valueChanged.disconnect()
-            except (TypeError, AttributeError):
-                pass
-
-        n = len(self.tools)
-        # we have (n+2) rows because there are 'n' tools, each a row, plus the last 2 rows for totals.
-        self.ui.tools_table.setRowCount(n + 2)
-
-        self.tot_drill_cnt = 0
-        self.tot_slot_cnt = 0
-
-        self.tool_row = 0
-
-        sort = []
-        for k, v in list(self.tools.items()):
-            sort.append((k, v.get('C')))
-        sorted_tools = sorted(sort, key=lambda t1: t1[1])
-        tools = [i[0] for i in sorted_tools]
-
-        new_options = {}
-        for opt in self.options:
-            new_options[opt] = self.options[opt]
-
-        for tool_no in tools:
-
-            # add the data dictionary for each tool with the default values
-            self.tools[tool_no]['data'] = deepcopy(new_options)
-            # self.tools[tool_no]['data']["tooldia"] = self.tools[tool_no]["C"]
-            # self.tools[tool_no]['data']["slot_tooldia"] = self.tools[tool_no]["C"]
-
-            drill_cnt = 0  # variable to store the nr of drills per tool
-            slot_cnt = 0  # variable to store the nr of slots per tool
-
-            # Find no of drills for the current tool
-            for drill in self.drills:
-                if drill['tool'] == tool_no:
-                    drill_cnt += 1
-
-            self.tot_drill_cnt += drill_cnt
-
-            # Find no of slots for the current tool
-            for slot in self.slots:
-                if slot['tool'] == tool_no:
-                    slot_cnt += 1
-
-            self.tot_slot_cnt += slot_cnt
-
-            exc_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool_no))
-            exc_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-
-            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.tools[tool_no]['C']))
-            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-            drill_count_item = QtWidgets.QTableWidgetItem('%d' % drill_cnt)
-            drill_count_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-            # if the slot number is zero is better to not clutter the GUI with zero's so we print a space
-            slot_count_str = '%d' % slot_cnt if slot_cnt > 0 else ''
-            slot_count_item = QtWidgets.QTableWidgetItem(slot_count_str)
-            slot_count_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-            plot_item = FCCheckBox()
-            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
-            if self.ui.plot_cb.isChecked():
-                plot_item.setChecked(True)
-
-            self.ui.tools_table.setItem(self.tool_row, 0, exc_id_item)  # Tool name/id
-            self.ui.tools_table.setItem(self.tool_row, 1, dia_item)  # Diameter
-            self.ui.tools_table.setItem(self.tool_row, 2, drill_count_item)  # Number of drills per tool
-            self.ui.tools_table.setItem(self.tool_row, 3, slot_count_item)  # Number of drills per tool
-            empty_plot_item = QtWidgets.QTableWidgetItem('')
-            empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-            self.ui.tools_table.setItem(self.tool_row, 5, empty_plot_item)
-            self.ui.tools_table.setCellWidget(self.tool_row, 5, plot_item)
-
-            self.tool_row += 1
-
-        # add a last row with the Total number of drills
-        empty_1 = QtWidgets.QTableWidgetItem('')
-        empty_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        empty_1_1 = QtWidgets.QTableWidgetItem('')
-        empty_1_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        empty_1_2 = QtWidgets.QTableWidgetItem('')
-        empty_1_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        empty_1_3 = QtWidgets.QTableWidgetItem('')
-        empty_1_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-
-        label_tot_drill_count = QtWidgets.QTableWidgetItem(_('Total Drills'))
-        tot_drill_count = QtWidgets.QTableWidgetItem('%d' % self.tot_drill_cnt)
-        label_tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled)
-        tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled)
-
-        self.ui.tools_table.setItem(self.tool_row, 0, empty_1)
-        self.ui.tools_table.setItem(self.tool_row, 1, label_tot_drill_count)
-        self.ui.tools_table.setItem(self.tool_row, 2, tot_drill_count)  # Total number of drills
-        self.ui.tools_table.setItem(self.tool_row, 3, empty_1_1)
-        self.ui.tools_table.setItem(self.tool_row, 5, empty_1_3)
-
-        font = QtGui.QFont()
-        font.setBold(True)
-        font.setWeight(75)
-
-        for k in [1, 2]:
-            self.ui.tools_table.item(self.tool_row, k).setForeground(QtGui.QColor(127, 0, 255))
-            self.ui.tools_table.item(self.tool_row, k).setFont(font)
-
-        self.tool_row += 1
-
-        # add a last row with the Total number of slots
-        empty_2 = QtWidgets.QTableWidgetItem('')
-        empty_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        empty_2_1 = QtWidgets.QTableWidgetItem('')
-        empty_2_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        empty_2_2 = QtWidgets.QTableWidgetItem('')
-        empty_2_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        empty_2_3 = QtWidgets.QTableWidgetItem('')
-        empty_2_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-
-        label_tot_slot_count = QtWidgets.QTableWidgetItem(_('Total Slots'))
-        tot_slot_count = QtWidgets.QTableWidgetItem('%d' % self.tot_slot_cnt)
-        label_tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled)
-        tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled)
-
-        self.ui.tools_table.setItem(self.tool_row, 0, empty_2)
-        self.ui.tools_table.setItem(self.tool_row, 1, label_tot_slot_count)
-        self.ui.tools_table.setItem(self.tool_row, 2, empty_2_1)
-        self.ui.tools_table.setItem(self.tool_row, 3, tot_slot_count)  # Total number of slots
-        self.ui.tools_table.setItem(self.tool_row, 5, empty_2_3)
-
-        for kl in [1, 2, 3]:
-            self.ui.tools_table.item(self.tool_row, kl).setFont(font)
-            self.ui.tools_table.item(self.tool_row, kl).setForeground(QtGui.QColor(0, 70, 255))
-
-        # sort the tool diameter column
-        # self.ui.tools_table.sortItems(1)
-
-        # all the tools are selected by default
-        self.ui.tools_table.selectColumn(0)
-
-        self.ui.tools_table.resizeColumnsToContents()
-        self.ui.tools_table.resizeRowsToContents()
-
-        vertical_header = self.ui.tools_table.verticalHeader()
-        # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
-        vertical_header.hide()
-        self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        horizontal_header = self.ui.tools_table.horizontalHeader()
-        horizontal_header.setMinimumSectionSize(10)
-        horizontal_header.setDefaultSectionSize(70)
-        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
-
-        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
-
-        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
-        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
-        horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(5, 17)
-        self.ui.tools_table.setColumnWidth(5, 17)
-
-        # horizontal_header.setStretchLastSection(True)
-        # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents)
-
-        # horizontal_header.setStretchLastSection(True)
-        self.ui.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        self.ui.tools_table.setSortingEnabled(False)
-
-        self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight())
-        self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight())
-
-        if not self.drills:
-            self.ui.tooldia_entry.hide()
-            self.ui.generate_milling_button.hide()
-        else:
-            self.ui.tooldia_entry.show()
-            self.ui.generate_milling_button.show()
-
-        if not self.slots:
-            self.ui.slot_tooldia_entry.hide()
-            self.ui.generate_milling_slots_button.hide()
-        else:
-            self.ui.slot_tooldia_entry.show()
-            self.ui.generate_milling_slots_button.show()
-
-        # set the text on tool_data_label after loading the object
-        sel_items = self.ui.tools_table.selectedItems()
-        sel_rows = [it.row() for it in sel_items]
-        if len(sel_rows) > 1:
-            self.ui.tool_data_label.setText(
-                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
-            )
-
-        self.ui_connect()
-
-    def set_ui(self, ui):
-        """
-        Configures the user interface for this object.
-        Connects options to form fields.
-
-        :param ui: User interface object.
-        :type ui: ExcellonObjectUI
-        :return: None
-        """
-        FlatCAMObj.set_ui(self, ui)
-
-        FlatCAMApp.App.log.debug("FlatCAMExcellon.set_ui()")
-
-        self.units = self.app.defaults['units'].upper()
-
-        self.form_fields.update({
-            "plot": self.ui.plot_cb,
-            "solid": self.ui.solid_cb,
-
-            "operation": self.ui.operation_radio,
-            "milling_type": self.ui.milling_type_radio,
-
-            "milling_dia": self.ui.mill_dia_entry,
-            "cutz": self.ui.cutz_entry,
-            "multidepth": self.ui.mpass_cb,
-            "depthperpass": self.ui.maxdepth_entry,
-            "travelz": self.ui.travelz_entry,
-            "feedrate_z": self.ui.feedrate_z_entry,
-            "feedrate": self.ui.xyfeedrate_entry,
-            "feedrate_rapid": self.ui.feedrate_rapid_entry,
-            "tooldia": self.ui.tooldia_entry,
-            "slot_tooldia": self.ui.slot_tooldia_entry,
-            "toolchange": self.ui.toolchange_cb,
-            "toolchangez": self.ui.toolchangez_entry,
-            "extracut": self.ui.extracut_cb,
-            "extracut_length": self.ui.e_cut_entry,
-
-            "spindlespeed": self.ui.spindlespeed_entry,
-            "dwell": self.ui.dwell_cb,
-            "dwelltime": self.ui.dwelltime_entry,
-
-            "startz": self.ui.estartz_entry,
-            "endz": self.ui.endz_entry,
-            "endxy": self.ui.endxy_entry,
-
-            "offset": self.ui.offset_entry,
-
-            "ppname_e": self.ui.pp_excellon_name_cb,
-            "ppname_g": self.ui.pp_geo_name_cb,
-            "z_pdepth": self.ui.pdepth_entry,
-            "feedrate_probe": self.ui.feedrate_probe_entry,
-            # "gcode_type": self.ui.excellon_gcode_type_radio
-        })
-
-        self.name2option = {
-            "e_operation": "operation",
-            "e_milling_type": "milling_type",
-            "e_milling_dia": "milling_dia",
-            "e_cutz": "cutz",
-            "e_multidepth": "multidepth",
-            "e_depthperpass": "depthperpass",
-
-            "e_travelz": "travelz",
-            "e_feedratexy": "feedrate",
-            "e_feedratez": "feedrate_z",
-            "e_fr_rapid": "feedrate_rapid",
-            "e_extracut": "extracut",
-            "e_extracut_length": "extracut_length",
-            "e_spindlespeed": "spindlespeed",
-            "e_dwell": "dwell",
-            "e_dwelltime": "dwelltime",
-            "e_offset": "offset",
-        }
-
-        # populate Excellon preprocessor combobox list
-        for name in list(self.app.preprocessors.keys()):
-            # the HPGL preprocessor is only for Geometry not for Excellon job therefore don't add it
-            if name == 'hpgl':
-                continue
-            self.ui.pp_excellon_name_cb.addItem(name)
-
-        # populate Geometry (milling) preprocessor combobox list
-        for name in list(self.app.preprocessors.keys()):
-            self.ui.pp_geo_name_cb.addItem(name)
-
-        # Fill form fields
-        self.to_form()
-
-        # update the changes in UI depending on the selected preprocessor in Preferences
-        # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the
-        # self.ui.pp_excellon_name_cb combobox
-        self.on_pp_changed()
-
-        # Show/Hide Advanced Options
-        if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
-
-            self.ui.tools_table.setColumnHidden(4, True)
-            self.ui.tools_table.setColumnHidden(5, True)
-            self.ui.estartz_label.hide()
-            self.ui.estartz_entry.hide()
-            self.ui.feedrate_rapid_label.hide()
-            self.ui.feedrate_rapid_entry.hide()
-            self.ui.pdepth_label.hide()
-            self.ui.pdepth_entry.hide()
-            self.ui.feedrate_probe_label.hide()
-            self.ui.feedrate_probe_entry.hide()
-        else:
-            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
-
-        assert isinstance(self.ui, ExcellonObjectUI), \
-            "Expected a ExcellonObjectUI, got %s" % type(self.ui)
-        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
-        self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
-        self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
-        self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
-        self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click)
-
-        self.on_operation_type(val='drill')
-        self.ui.operation_radio.activated_custom.connect(self.on_operation_type)
-
-        self.ui.pp_excellon_name_cb.activated.connect(self.on_pp_changed)
-
-        self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
-
-        self.units_found = self.app.defaults['units']
-
-        # ########################################
-        # #######3 TEMP SETTINGS #################
-        # ########################################
-        self.ui.operation_radio.set_value("drill")
-        self.ui.operation_radio.setEnabled(False)
-
-    def ui_connect(self):
-
-        # selective plotting
-        for row in range(self.ui.tools_table.rowCount() - 2):
-            self.ui.tools_table.cellWidget(row, 5).clicked.connect(self.on_plot_cb_click_table)
-        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
-
-        # rows selected
-        self.ui.tools_table.clicked.connect(self.on_row_selection_change)
-        self.ui.tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change)
-
-        # value changed in the particular parameters of a tool
-        for key, option in self.name2option.items():
-            current_widget = self.form_fields[option]
-
-            if isinstance(current_widget, FCCheckBox):
-                current_widget.stateChanged.connect(self.form_to_storage)
-            if isinstance(current_widget, RadioSet):
-                current_widget.activated_custom.connect(self.form_to_storage)
-            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
-                current_widget.returnPressed.connect(self.form_to_storage)
-
-    def ui_disconnect(self):
-        # selective plotting
-        for row in range(self.ui.tools_table.rowCount()):
-            try:
-                self.ui.tools_table.cellWidget(row, 5).clicked.disconnect()
-            except (TypeError, AttributeError):
-                pass
-        try:
-            self.ui.plot_cb.stateChanged.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        # rows selected
-        try:
-            self.ui.tools_table.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-        try:
-            self.ui.tools_table.horizontalHeader().sectionClicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        # value changed in the particular parameters of a tool
-        for key, option in self.name2option.items():
-            current_widget = self.form_fields[option]
-
-            if isinstance(current_widget, FCCheckBox):
-                try:
-                    current_widget.stateChanged.disconnect(self.form_to_storage)
-                except (TypeError, ValueError):
-                    pass
-            if isinstance(current_widget, RadioSet):
-                try:
-                    current_widget.activated_custom.disconnect(self.form_to_storage)
-                except (TypeError, ValueError):
-                    pass
-            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
-                try:
-                    current_widget.returnPressed.disconnect(self.form_to_storage)
-                except (TypeError, ValueError):
-                    pass
-
-    def on_row_selection_change(self):
-        self.ui_disconnect()
-
-        sel_rows = []
-        sel_items = self.ui.tools_table.selectedItems()
-        for it in sel_items:
-            sel_rows.append(it.row())
-
-        if not sel_rows:
-            self.ui.tool_data_label.setText(
-                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("No Tool Selected"))
-            )
-            self.ui.generate_cnc_button.setDisabled(True)
-            self.ui.generate_milling_button.setDisabled(True)
-            self.ui.generate_milling_slots_button.setDisabled(True)
-            self.ui_connect()
-            return
-        else:
-            self.ui.generate_cnc_button.setDisabled(False)
-            self.ui.generate_milling_button.setDisabled(False)
-            self.ui.generate_milling_slots_button.setDisabled(False)
-
-        if len(sel_rows) == 1:
-            # update the QLabel that shows for which Tool we have the parameters in the UI form
-            tooluid = int(self.ui.tools_table.item(sel_rows[0], 0).text())
-            self.ui.tool_data_label.setText(
-                "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), tooluid)
-            )
-        else:
-            self.ui.tool_data_label.setText(
-                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
-            )
-
-        for c_row in sel_rows:
-            # populate the form with the data from the tool associated with the row parameter
-            try:
-                item = self.ui.tools_table.item(c_row, 0)
-                if type(item) is not None:
-                    tooluid = item.text()
-                    self.storage_to_form(self.tools[str(tooluid)]['data'])
-                else:
-                    self.ui_connect()
-                    return
-            except Exception as e:
-                log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e))
-                self.ui_connect()
-                return
-
-        self.ui_connect()
-
-    def storage_to_form(self, dict_storage):
-        for form_key in self.form_fields:
-            for storage_key in dict_storage:
-                if form_key == storage_key and form_key not in \
-                        ["toolchange", "toolchangez", "startz", "endz", "ppname_e", "ppname_g"]:
-                    try:
-                        self.form_fields[form_key].set_value(dict_storage[form_key])
-                    except Exception as e:
-                        log.debug("FlatCAMExcellon.storage_to_form() --> %s" % str(e))
-                        pass
-
-    def form_to_storage(self):
-        if self.ui.tools_table.rowCount() == 0:
-            # there is no tool in tool table so we can't save the GUI elements values to storage
-            return
-
-        self.ui_disconnect()
-
-        widget_changed = self.sender()
-        wdg_objname = widget_changed.objectName()
-        option_changed = self.name2option[wdg_objname]
-
-        # row = self.ui.tools_table.currentRow()
-        rows = sorted(set(index.row() for index in self.ui.tools_table.selectedIndexes()))
-        for row in rows:
-            if row < 0:
-                row = 0
-            tooluid_item = int(self.ui.tools_table.item(row, 0).text())
-
-            for tooluid_key, tooluid_val in self.tools.items():
-                if int(tooluid_key) == tooluid_item:
-                    new_option_value = self.form_fields[option_changed].get_value()
-                    if option_changed in tooluid_val:
-                        tooluid_val[option_changed] = new_option_value
-                    if option_changed in tooluid_val['data']:
-                        tooluid_val['data'][option_changed] = new_option_value
-
-        self.ui_connect()
-
-    def on_operation_type(self, val):
-        if val == 'mill':
-            self.ui.mill_type_label.show()
-            self.ui.milling_type_radio.show()
-            self.ui.mill_dia_label.show()
-            self.ui.mill_dia_entry.show()
-            self.ui.frxylabel.show()
-            self.ui.xyfeedrate_entry.show()
-            self.ui.extracut_cb.show()
-            self.ui.e_cut_entry.show()
-
-            # if 'laser' not in self.ui.pp_excellon_name_cb.get_value().lower():
-            #     self.ui.mpass_cb.show()
-            #     self.ui.maxdepth_entry.show()
-        else:
-            self.ui.mill_type_label.hide()
-            self.ui.milling_type_radio.hide()
-            self.ui.mill_dia_label.hide()
-            self.ui.mill_dia_entry.hide()
-            # self.ui.mpass_cb.hide()
-            # self.ui.maxdepth_entry.hide()
-            self.ui.frxylabel.hide()
-            self.ui.xyfeedrate_entry.hide()
-            self.ui.extracut_cb.hide()
-            self.ui.e_cut_entry.hide()
-
-    def get_selected_tools_list(self):
-        """
-        Returns the keys to the self.tools dictionary corresponding
-        to the selections on the tool list in the GUI.
-
-        :return: List of tools.
-        :rtype: list
-        """
-
-        return [str(x.text()) for x in self.ui.tools_table.selectedItems()]
-
-    def get_selected_tools_table_items(self):
-        """
-        Returns a list of lists, each list in the list is made out of row elements
-
-        :return: List of table_tools items.
-        :rtype: list
-        """
-        table_tools_items = []
-        for x in self.ui.tools_table.selectedItems():
-            # from the columnCount we subtract a value of 1 which represent the last column (plot column)
-            # which does not have text
-            txt = ''
-            elem = []
-
-            for column in range(0, self.ui.tools_table.columnCount() - 1):
-                try:
-                    txt = self.ui.tools_table.item(x.row(), column).text()
-                except AttributeError:
-                    try:
-                        txt = self.ui.tools_table.cellWidget(x.row(), column).currentText()
-                    except AttributeError:
-                        pass
-                elem.append(txt)
-            table_tools_items.append(deepcopy(elem))
-            # table_tools_items.append([self.ui.tools_table.item(x.row(), column).text()
-            #                           for column in range(0, self.ui.tools_table.columnCount() - 1)])
-        for item in table_tools_items:
-            item[0] = str(item[0])
-        return table_tools_items
-
-    def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'):
-        """
-        Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code
-        :return: has_slots and Excellon_code
-        """
-
-        excellon_code = ''
-
-        # store here if the file has slots, return 1 if any slots, 0 if only drills
-        has_slots = 0
-
-        # drills processing
-        try:
-            if self.drills:
-                length = whole + fract
-                for tool in self.tools:
-                    excellon_code += 'T0%s\n' % str(tool) if int(tool) < 10 else 'T%s\n' % str(tool)
-
-                    for drill in self.drills:
-                        if form == 'dec' and tool == drill['tool']:
-                            drill_x = drill['point'].x * factor
-                            drill_y = drill['point'].y * factor
-                            excellon_code += "X{:.{dec}f}Y{:.{dec}f}\n".format(drill_x, drill_y, dec=fract)
-                        elif e_zeros == 'LZ' and tool == drill['tool']:
-                            drill_x = drill['point'].x * factor
-                            drill_y = drill['point'].y * factor
-
-                            exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract)
-                            exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract)
-
-                            # extract whole part and decimal part
-                            exc_x_formatted = exc_x_formatted.partition('.')
-                            exc_y_formatted = exc_y_formatted.partition('.')
-
-                            # left padd the 'whole' part with zeros
-                            x_whole = exc_x_formatted[0].rjust(whole, '0')
-                            y_whole = exc_y_formatted[0].rjust(whole, '0')
-
-                            # restore the coordinate padded in the left with 0 and added the decimal part
-                            # without the decinal dot
-                            exc_x_formatted = x_whole + exc_x_formatted[2]
-                            exc_y_formatted = y_whole + exc_y_formatted[2]
-
-                            excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted,
-                                                                         yform=exc_y_formatted)
-                        elif tool == drill['tool']:
-                            drill_x = drill['point'].x * factor
-                            drill_y = drill['point'].y * factor
-
-                            exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract).replace('.', '')
-                            exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract).replace('.', '')
-
-                            # pad with rear zeros
-                            exc_x_formatted.ljust(length, '0')
-                            exc_y_formatted.ljust(length, '0')
-
-                            excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted,
-                                                                         yform=exc_y_formatted)
-        except Exception as e:
-            log.debug(str(e))
-
-        # slots processing
-        try:
-            if self.slots:
-                has_slots = 1
-                for tool in self.tools:
-                    excellon_code += 'G05\n'
-
-                    if int(tool) < 10:
-                        excellon_code += 'T0' + str(tool) + '\n'
-                    else:
-                        excellon_code += 'T' + str(tool) + '\n'
-
-                    for slot in self.slots:
-                        if form == 'dec' and tool == slot['tool']:
-                            start_slot_x = slot['start'].x * factor
-                            start_slot_y = slot['start'].y * factor
-                            stop_slot_x = slot['stop'].x * factor
-                            stop_slot_y = slot['stop'].y * factor
-                            if slot_type == 'routing':
-                                excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM15\n".format(start_slot_x,
-                                                                                           start_slot_y,
-                                                                                           dec=fract)
-                                excellon_code += "G01X{:.{dec}f}Y{:.{dec}f}\nM16\n".format(stop_slot_x,
-                                                                                           stop_slot_y,
-                                                                                           dec=fract)
-                            elif slot_type == 'drilling':
-                                excellon_code += "X{:.{dec}f}Y{:.{dec}f}G85X{:.{dec}f}Y{:.{dec}f}\nG05\n".format(
-                                    start_slot_x, start_slot_y, stop_slot_x, stop_slot_y, dec=fract
-                                )
-
-                        elif e_zeros == 'LZ' and tool == slot['tool']:
-                            start_slot_x = slot['start'].x * factor
-                            start_slot_y = slot['start'].y * factor
-                            stop_slot_x = slot['stop'].x * factor
-                            stop_slot_y = slot['stop'].y * factor
-
-                            start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '')
-                            start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '')
-                            stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '')
-                            stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '')
-
-                            # extract whole part and decimal part
-                            start_slot_x_formatted = start_slot_x_formatted.partition('.')
-                            start_slot_y_formatted = start_slot_y_formatted.partition('.')
-                            stop_slot_x_formatted = stop_slot_x_formatted.partition('.')
-                            stop_slot_y_formatted = stop_slot_y_formatted.partition('.')
-
-                            # left padd the 'whole' part with zeros
-                            start_x_whole = start_slot_x_formatted[0].rjust(whole, '0')
-                            start_y_whole = start_slot_y_formatted[0].rjust(whole, '0')
-                            stop_x_whole = stop_slot_x_formatted[0].rjust(whole, '0')
-                            stop_y_whole = stop_slot_y_formatted[0].rjust(whole, '0')
-
-                            # restore the coordinate padded in the left with 0 and added the decimal part
-                            # without the decinal dot
-                            start_slot_x_formatted = start_x_whole + start_slot_x_formatted[2]
-                            start_slot_y_formatted = start_y_whole + start_slot_y_formatted[2]
-                            stop_slot_x_formatted = stop_x_whole + stop_slot_x_formatted[2]
-                            stop_slot_y_formatted = stop_y_whole + stop_slot_y_formatted[2]
-
-                            if slot_type == 'routing':
-                                excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
-                                                                                       ystart=start_slot_y_formatted)
-                                excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
-                                                                                     ystop=stop_slot_y_formatted)
-                            elif slot_type == 'drilling':
-                                excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format(
-                                    xstart=start_slot_x_formatted, ystart=start_slot_y_formatted,
-                                    xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted
-                                )
-                        elif tool == slot['tool']:
-                            start_slot_x = slot['start'].x * factor
-                            start_slot_y = slot['start'].y * factor
-                            stop_slot_x = slot['stop'].x * factor
-                            stop_slot_y = slot['stop'].y * factor
-                            length = whole + fract
-
-                            start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '')
-                            start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '')
-                            stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '')
-                            stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '')
-
-                            # pad with rear zeros
-                            start_slot_x_formatted.ljust(length, '0')
-                            start_slot_y_formatted.ljust(length, '0')
-                            stop_slot_x_formatted.ljust(length, '0')
-                            stop_slot_y_formatted.ljust(length, '0')
-
-                            if slot_type == 'routing':
-                                excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
-                                                                                       ystart=start_slot_y_formatted)
-                                excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
-                                                                                     ystop=stop_slot_y_formatted)
-                            elif slot_type == 'drilling':
-                                excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format(
-                                    xstart=start_slot_x_formatted, ystart=start_slot_y_formatted,
-                                    xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted
-                                )
-        except Exception as e:
-            log.debug(str(e))
-
-        if not self.drills and not self.slots:
-            log.debug("FlatCAMObj.FlatCAMExcellon.export_excellon() --> Excellon Object is empty: no drills, no slots.")
-            return 'fail'
-
-        return has_slots, excellon_code
-
-    def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
-        """
-        Note: This method is a good template for generic operations as
-        it takes it's options from parameters or otherwise from the
-        object's options and returns a (success, msg) tuple as feedback
-        for shell operations.
-
-        :return: Success/failure condition tuple (bool, str).
-        :rtype: tuple
-        """
-
-        # Get the tools from the list. These are keys
-        # to self.tools
-        if tools is None:
-            tools = self.get_selected_tools_list()
-
-        if outname is None:
-            outname = self.options["name"] + "_mill"
-
-        if tooldia is None:
-            tooldia = float(self.options["tooldia"])
-
-        # Sort tools by diameter. items() -> [('name', diameter), ...]
-        # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
-
-        sort = []
-        for k, v in self.tools.items():
-            sort.append((k, v.get('C')))
-        sorted_tools = sorted(sort, key=lambda t1: t1[1])
-
-        if tools == "all":
-            tools = [i[0] for i in sorted_tools]  # List if ordered tool names.
-            log.debug("Tools 'all' and sorted are: %s" % str(tools))
-
-        if len(tools) == 0:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Please select one or more tools from the list and try again."))
-            return False, "Error: No tools."
-
-        for tool in tools:
-            if tooldia > self.tools[tool]["C"]:
-                self.app.inform.emit(
-                    '[ERROR_NOTCL] %s %s: %s' % (
-                        _("Milling tool for DRILLS is larger than hole size. Cancelled."),
-                        _("Tool"),
-                        str(tool)
-                    )
-                )
-                return False, "Error: Milling tool is larger than hole."
-
-        def geo_init(geo_obj, app_obj):
-            assert isinstance(geo_obj, FlatCAMGeometry), \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
-
-            # ## Add properties to the object
-
-            # get the tool_table items in a list of row items
-            tool_table_items = self.get_selected_tools_table_items()
-            # insert an information only element in the front
-            tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")])
-
-            geo_obj.options['Tools_in_use'] = tool_table_items
-            geo_obj.options['type'] = 'Excellon Geometry'
-            geo_obj.options["cnctooldia"] = str(tooldia)
-
-            geo_obj.solid_geometry = []
-
-            # 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 hole in self.drills:
-                if hole['tool'] in tools:
-                    buffer_value = self.tools[hole['tool']]["C"] / 2 - tooldia / 2
-                    if buffer_value == 0:
-                        geo_obj.solid_geometry.append(
-                            Point(hole['point']).buffer(0.0000001).exterior)
-                    else:
-                        geo_obj.solid_geometry.append(
-                            Point(hole['point']).buffer(buffer_value).exterior)
-        if use_thread:
-            def geo_thread(app_obj):
-                app_obj.new_object("geometry", outname, geo_init, plot=plot)
-
-            # Create a promise with the new name
-            self.app.collection.promise(outname)
-
-            # Send to worker
-            self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
-        else:
-            self.app.new_object("geometry", outname, geo_init, plot=plot)
-
-        return True, ""
-
-    def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False):
-        """
-        Note: This method is a good template for generic operations as
-        it takes it's options from parameters or otherwise from the
-        object's options and returns a (success, msg) tuple as feedback
-        for shell operations.
-
-        :return: Success/failure condition tuple (bool, str).
-        :rtype: tuple
-        """
-
-        # Get the tools from the list. These are keys
-        # to self.tools
-        if tools is None:
-            tools = self.get_selected_tools_list()
-
-        if outname is None:
-            outname = self.options["name"] + "_mill"
-
-        if tooldia is None:
-            tooldia = float(self.options["slot_tooldia"])
-
-        # Sort tools by diameter. items() -> [('name', diameter), ...]
-        # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
-
-        sort = []
-        for k, v in self.tools.items():
-            sort.append((k, v.get('C')))
-        sorted_tools = sorted(sort, key=lambda t1: t1[1])
-
-        if tools == "all":
-            tools = [i[0] for i in sorted_tools]  # List if ordered tool names.
-            log.debug("Tools 'all' and sorted are: %s" % str(tools))
-
-        if len(tools) == 0:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Please select one or more tools from the list and try again."))
-            return False, "Error: No tools."
-
-        for tool in tools:
-            # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
-            adj_toolstable_tooldia = float('%.*f' % (self.decimals, float(tooldia)))
-            adj_file_tooldia = float('%.*f' % (self.decimals, float(self.tools[tool]["C"])))
-            if adj_toolstable_tooldia > adj_file_tooldia + 0.0001:
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("Milling tool for SLOTS is larger than hole size. Cancelled."))
-                return False, "Error: Milling tool is larger than hole."
-
-        def geo_init(geo_obj, app_obj):
-            assert isinstance(geo_obj, FlatCAMGeometry), \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
-
-            # ## Add properties to the object
-
-            # get the tool_table items in a list of row items
-            tool_table_items = self.get_selected_tools_table_items()
-            # insert an information only element in the front
-            tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")])
-
-            geo_obj.options['Tools_in_use'] = tool_table_items
-            geo_obj.options['type'] = 'Excellon Geometry'
-            geo_obj.options["cnctooldia"] = str(tooldia)
-
-            geo_obj.solid_geometry = []
-
-            # 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 slot in self.slots:
-                if slot['tool'] in tools:
-                    toolstable_tool = float('%.*f' % (self.decimals, float(tooldia)))
-                    file_tool = float('%.*f' % (self.decimals, float(self.tools[tool]["C"])))
-
-                    # 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)
-                    buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001
-                    if buffer_value == 0:
-                        start = slot['start']
-                        stop = slot['stop']
-
-                        lines_string = LineString([start, stop])
-                        poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior
-                        geo_obj.solid_geometry.append(poly)
-                    else:
-                        start = slot['start']
-                        stop = slot['stop']
-
-                        lines_string = LineString([start, stop])
-                        poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior
-                        geo_obj.solid_geometry.append(poly)
-
-        if use_thread:
-            def geo_thread(app_obj):
-                app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
-
-            # Create a promise with the new name
-            self.app.collection.promise(outname)
-
-            # Send to worker
-            self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
-        else:
-            self.app.new_object("geometry", outname + '_slot', geo_init, plot=plot)
-
-        return True, ""
-
-    def on_generate_milling_button_click(self, *args):
-        self.app.report_usage("excellon_on_create_milling_drills button")
-        self.read_form()
-
-        self.generate_milling_drills(use_thread=False)
-
-    def on_generate_milling_slots_button_click(self, *args):
-        self.app.report_usage("excellon_on_create_milling_slots_button")
-        self.read_form()
-
-        self.generate_milling_slots(use_thread=False)
-
-    def on_pp_changed(self):
-        current_pp = self.ui.pp_excellon_name_cb.get_value()
-
-        if "toolchange_probe" in current_pp.lower():
-            self.ui.pdepth_entry.setVisible(True)
-            self.ui.pdepth_label.show()
-
-            self.ui.feedrate_probe_entry.setVisible(True)
-            self.ui.feedrate_probe_label.show()
-        else:
-            self.ui.pdepth_entry.setVisible(False)
-            self.ui.pdepth_label.hide()
-
-            self.ui.feedrate_probe_entry.setVisible(False)
-            self.ui.feedrate_probe_label.hide()
-
-        if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
-            self.ui.feedrate_rapid_label.show()
-            self.ui.feedrate_rapid_entry.show()
-        else:
-            self.ui.feedrate_rapid_label.hide()
-            self.ui.feedrate_rapid_entry.hide()
-
-        if 'laser' in current_pp.lower():
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_entry.hide()
-            try:
-                self.ui.mpass_cb.hide()
-                self.ui.maxdepth_entry.hide()
-            except AttributeError:
-                pass
-
-            if 'marlin' in current_pp.lower():
-                self.ui.travelzlabel.setText('%s:' % _("Focus Z"))
-                self.ui.endz_label.show()
-                self.ui.endz_entry.show()
-            else:
-                self.ui.travelzlabel.hide()
-                self.ui.travelz_entry.hide()
-
-                self.ui.endz_label.hide()
-                self.ui.endz_entry.hide()
-
-            try:
-                self.ui.frzlabel.hide()
-                self.ui.feedrate_z_entry.hide()
-            except AttributeError:
-                pass
-
-            self.ui.dwell_cb.hide()
-            self.ui.dwelltime_entry.hide()
-
-            self.ui.spindle_label.setText('%s:' % _("Laser Power"))
-
-            try:
-                self.ui.tool_offset_label.hide()
-                self.ui.offset_entry.hide()
-            except AttributeError:
-                pass
-        else:
-            self.ui.cutzlabel.show()
-            self.ui.cutz_entry.show()
-            try:
-                self.ui.mpass_cb.show()
-                self.ui.maxdepth_entry.show()
-            except AttributeError:
-                pass
-
-            self.ui.travelzlabel.setText('%s:' % _('Travel Z'))
-
-            self.ui.travelzlabel.show()
-            self.ui.travelz_entry.show()
-
-            self.ui.endz_label.show()
-            self.ui.endz_entry.show()
-
-            try:
-                self.ui.frzlabel.show()
-                self.ui.feedrate_z_entry.show()
-            except AttributeError:
-                pass
-            self.ui.dwell_cb.show()
-            self.ui.dwelltime_entry.show()
-
-            self.ui.spindle_label.setText('%s:' % _('Spindle speed'))
-
-            try:
-                self.ui.tool_offset_lbl.show()
-                self.ui.offset_entry.show()
-            except AttributeError:
-                pass
-
-    def on_create_cncjob_button_click(self, *args):
-        self.app.report_usage("excellon_on_create_cncjob_button")
-        self.read_form()
-
-        # Get the tools from the list
-        tools = self.get_selected_tools_list()
-
-        if len(tools) == 0:
-            # if there is a single tool in the table (remember that the last 2 rows are for totals and do not count in
-            # tool number) it means that there are 3 rows (1 tool and 2 totals).
-            # in this case regardless of the selection status of that tool, use it.
-            if self.ui.tools_table.rowCount() == 3:
-                tools.append(self.ui.tools_table.item(0, 0).text())
-            else:
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("Please select one or more tools from the list and try again."))
-                return
-
-        xmin = self.options['xmin']
-        ymin = self.options['ymin']
-        xmax = self.options['xmax']
-        ymax = self.options['ymax']
-
-        job_name = self.options["name"] + "_cnc"
-        pp_excellon_name = self.options["ppname_e"]
-
-        # Object initialization function for app.new_object()
-        def job_init(job_obj, app_obj):
-            assert isinstance(job_obj, FlatCAMCNCjob), \
-                "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
-
-            # get the tool_table items in a list of row items
-            tool_table_items = self.get_selected_tools_table_items()
-            # insert an information only element in the front
-            tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")])
-
-            # ## Add properties to the object
-
-            job_obj.origin_kind = 'excellon'
-
-            job_obj.options['Tools_in_use'] = tool_table_items
-            job_obj.options['type'] = 'Excellon'
-            job_obj.options['ppname_e'] = pp_excellon_name
-
-            job_obj.multidepth = self.options["multidepth"]
-            job_obj.z_depthpercut = self.options["depthperpass"]
-
-            job_obj.z_move = float(self.options["travelz"])
-            job_obj.feedrate = float(self.options["feedrate_z"])
-            job_obj.z_feedrate = float(self.options["feedrate_z"])
-            job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
-
-            job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] != 0 else None
-            job_obj.spindledir = self.app.defaults['excellon_spindledir']
-            job_obj.dwell = self.options["dwell"]
-            job_obj.dwelltime = float(self.options["dwelltime"])
-
-            job_obj.pp_excellon_name = pp_excellon_name
-
-            job_obj.toolchange_xy_type = "excellon"
-            job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
-            job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
-
-            job_obj.options['xmin'] = xmin
-            job_obj.options['ymin'] = ymin
-            job_obj.options['xmax'] = xmax
-            job_obj.options['ymax'] = ymax
-
-            job_obj.z_pdepth = float(self.options["z_pdepth"])
-            job_obj.feedrate_probe = float(self.options["feedrate_probe"])
-
-            job_obj.z_cut = float(self.options['cutz'])
-            job_obj.toolchange = self.options["toolchange"]
-            job_obj.xy_toolchange = self.app.defaults["excellon_toolchangexy"]
-            job_obj.z_toolchange = float(self.options["toolchangez"])
-            job_obj.startz = float(self.options["startz"]) if self.options["startz"] else None
-            job_obj.endz = float(self.options["endz"])
-            job_obj.xy_end = self.options["endxy"]
-            job_obj.excellon_optimization_type = self.app.defaults["excellon_optimization_type"]
-
-            tools_csv = ','.join(tools)
-            ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, use_ui=True)
-
-            if ret_val == 'fail':
-                return 'fail'
-
-            job_obj.gcode_parse()
-            job_obj.create_geometry()
-
-        # To be run in separate thread
-        def job_thread(app_obj):
-            with self.app.proc_container.new(_("Generating CNC Code")):
-                app_obj.new_object("cncjob", job_name, job_init)
-
-        # Create promise for the new name.
-        self.app.collection.promise(job_name)
-
-        # Send to worker
-        # self.app.worker.add_task(job_thread, [self.app])
-        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
-
-    def convert_units(self, units):
-        log.debug("FlatCAMObj.FlatCAMExcellon.convert_units()")
-
-        Excellon.convert_units(self, units)
-
-        # factor = Excellon.convert_units(self, units)
-        # self.options['drillz'] = float(self.options['drillz']) * factor
-        # self.options['travelz'] = float(self.options['travelz']) * factor
-        # self.options['feedrate'] = float(self.options['feedrate']) * factor
-        # self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
-        # self.options['toolchangez'] = float(self.options['toolchangez']) * factor
-        #
-        # if self.app.defaults["excellon_toolchangexy"] == '':
-        #     self.options['toolchangexy'] = "0.0, 0.0"
-        # else:
-        #     coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")]
-        #     if len(coords_xy) < 2:
-        #         self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be "
-        #                                               "in the format (x, y) \n"
-        #                                               "but now there is only one value, not two. "))
-        #         return 'fail'
-        #     coords_xy[0] *= factor
-        #     coords_xy[1] *= factor
-        #     self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
-        #
-        # if self.options['startz'] is not None:
-        #     self.options['startz'] = float(self.options['startz']) * factor
-        # self.options['endz'] = float(self.options['endz']) * factor
-
-    def on_solid_cb_click(self, *args):
-        if self.muted_ui:
-            return
-        self.read_form_item('solid')
-        self.plot()
-
-    def on_plot_cb_click(self, *args):
-        if self.muted_ui:
-            return
-        self.plot()
-        self.read_form_item('plot')
-
-        self.ui_disconnect()
-        cb_flag = self.ui.plot_cb.isChecked()
-        for row in range(self.ui.tools_table.rowCount() - 2):
-            table_cb = self.ui.tools_table.cellWidget(row, 5)
-            if cb_flag:
-                table_cb.setChecked(True)
-            else:
-                table_cb.setChecked(False)
-
-        self.ui_connect()
-
-    def on_plot_cb_click_table(self):
-        # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked)
-        self.ui_disconnect()
-        # cw = self.sender()
-        # cw_index = self.ui.tools_table.indexAt(cw.pos())
-        # cw_row = cw_index.row()
-        check_row = 0
-
-        self.shapes.clear(update=True)
-        for tool_key in self.tools:
-            solid_geometry = self.tools[tool_key]['solid_geometry']
-
-            # find the geo_tool_table row associated with the tool_key
-            for row in range(self.ui.tools_table.rowCount()):
-                tool_item = int(self.ui.tools_table.item(row, 0).text())
-                if tool_item == int(tool_key):
-                    check_row = row
-                    break
-            if self.ui.tools_table.cellWidget(check_row, 5).isChecked():
-                self.options['plot'] = True
-                # self.plot_element(element=solid_geometry, visible=True)
-                # Plot excellon (All polygons?)
-                if self.options["solid"]:
-                    for geo in solid_geometry:
-                        self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
-                                       visible=self.options['plot'],
-                                       layer=2)
-                else:
-                    for geo in solid_geometry:
-                        self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
-                        for ints in geo.interiors:
-                            self.add_shape(shape=ints, color='green', visible=self.options['plot'])
-        self.shapes.redraw()
-
-        # make sure that the general plot is disabled if one of the row plot's are disabled and
-        # if all the row plot's are enabled also enable the general plot checkbox
-        cb_cnt = 0
-        total_row = self.ui.tools_table.rowCount()
-        for row in range(total_row - 2):
-            if self.ui.tools_table.cellWidget(row, 5).isChecked():
-                cb_cnt += 1
-            else:
-                cb_cnt -= 1
-        if cb_cnt < total_row - 2:
-            self.ui.plot_cb.setChecked(False)
-        else:
-            self.ui.plot_cb.setChecked(True)
-        self.ui_connect()
-
-    def plot(self, visible=None, kind=None):
-
-        # Does all the required setup and returns False
-        # if the 'ptint' option is set to False.
-        if not FlatCAMObj.plot(self):
-            return
-
-        # try:
-        #     # Plot Excellon (All polygons?)
-        #     if self.options["solid"]:
-        #         for tool in self.tools:
-        #             for geo in self.tools[tool]['solid_geometry']:
-        #                 self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
-        #                                visible=self.options['plot'],
-        #                                layer=2)
-        #     else:
-        #         for tool in self.tools:
-        #             for geo in self.tools[tool]['solid_geometry']:
-        #                 self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
-        #                 for ints in geo.interiors:
-        #                     self.add_shape(shape=ints, color='orange', visible=self.options['plot'])
-        #
-        #     self.shapes.redraw()
-        #     return
-        # except (ObjectDeleted, AttributeError, KeyError):
-        #     self.shapes.clear(update=True)
-
-        # this stays for compatibility reasons, in case we try to open old projects
-        try:
-            __ = iter(self.solid_geometry)
-        except TypeError:
-            self.solid_geometry = [self.solid_geometry]
-
-        visible = visible if visible else self.options['plot']
-
-        try:
-            # Plot Excellon (All polygons?)
-            if self.options["solid"]:
-                for geo in self.solid_geometry:
-                    self.add_shape(shape=geo,
-                                   color=self.outline_color,
-                                   face_color=self.fill_color,
-                                   visible=visible,
-                                   layer=2)
-            else:
-                for geo in self.solid_geometry:
-                    self.add_shape(shape=geo.exterior, color='red', visible=visible)
-                    for ints in geo.interiors:
-                        self.add_shape(shape=ints, color='orange', visible=visible)
-
-            self.shapes.redraw()
-        except (ObjectDeleted, AttributeError):
-            self.shapes.clear(update=True)
-
-    def on_apply_param_to_all_clicked(self):
-        if self.ui.tools_table.rowCount() == 0:
-            # there is no tool in tool table so we can't save the GUI elements values to storage
-            log.debug("FlatCAMExcellon.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
-            return
-
-        self.ui_disconnect()
-
-        row = self.ui.tools_table.currentRow()
-        if row < 0:
-            row = 0
-
-        tooluid_item = int(self.ui.tools_table.item(row, 0).text())
-        temp_tool_data = {}
-
-        for tooluid_key, tooluid_val in self.tools.items():
-            if int(tooluid_key) == tooluid_item:
-                # this will hold the 'data' key of the self.tools[tool] dictionary that corresponds to
-                # the current row in the tool table
-                temp_tool_data = tooluid_val['data']
-                break
-
-        for tooluid_key, tooluid_val in self.tools.items():
-            tooluid_val['data'] = deepcopy(temp_tool_data)
-
-        self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools."))
-
-        self.ui_connect()
-
-
-class FlatCAMGeometry(FlatCAMObj, Geometry):
-    """
-    Geometric object not associated with a specific
-    format.
-    """
-    optionChanged = QtCore.pyqtSignal(str)
-    ui_type = GeometryObjectUI
-
-    def __init__(self, name):
-        self.decimals = self.app.decimals
-
-        self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
-
-        FlatCAMObj.__init__(self, name)
-        Geometry.__init__(self, geo_steps_per_circle=self.circle_steps)
-
-        self.kind = "geometry"
-
-        self.options.update({
-            "plot": True,
-            "cutz": -0.002,
-            "vtipdia": 0.1,
-            "vtipangle": 30,
-            "travelz": 0.1,
-            "feedrate": 5.0,
-            "feedrate_z": 5.0,
-            "feedrate_rapid": 5.0,
-            "spindlespeed": 0,
-            "dwell": True,
-            "dwelltime": 1000,
-            "multidepth": False,
-            "depthperpass": 0.002,
-            "extracut": False,
-            "extracut_length": 0.1,
-            "endz": 2.0,
-            "endxy": '',
-
-            "startz": None,
-            "toolchange": False,
-            "toolchangez": 1.0,
-            "toolchangexy": "0.0, 0.0",
-            "ppname_g": 'default',
-            "z_pdepth": -0.02,
-            "feedrate_probe": 3.0,
-        })
-
-        if "cnctooldia" not in self.options:
-            if type(self.app.defaults["geometry_cnctooldia"]) == float:
-                self.options["cnctooldia"] = self.app.defaults["geometry_cnctooldia"]
-            else:
-                try:
-                    tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
-                    tools_diameters = [eval(a) for a in tools_string if a != '']
-                    self.options["cnctooldia"] = tools_diameters[0] if tools_diameters else 0.0
-                except Exception as e:
-                    log.debug("FlatCAMObj.FlatCAMGeometry.init() --> %s" % str(e))
-
-        self.options["startz"] = self.app.defaults["geometry_startz"]
-
-        # this will hold the tool unique ID that is useful when having multiple tools with same diameter
-        self.tooluid = 0
-
-        '''
-            self.tools = {}
-            This is a dictionary. Each dict key is associated with a tool used in geo_tools_table. The key is the 
-            tool_id of the tools and the value is another dict that will hold the data under the following form:
-                {tooluid:   {
-                            'tooldia': 1,
-                            'offset': 'Path',
-                            'offset_value': 0.0
-                            'type': 'Rough',
-                            'tool_type': 'C1',
-                            'data': self.default_tool_data
-                            'solid_geometry': []
-                            }
-                }
-        '''
-        self.tools = {}
-
-        # this dict is to store those elements (tools) of self.tools that are selected in the self.geo_tools_table
-        # those elements are the ones used for generating GCode
-        self.sel_tools = {}
-
-        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"]
-
-        # flag to store if the V-Shape tool is selected in self.ui.geo_tools_table
-        self.v_tool_type = None
-
-        # flag to store if the Geometry is type 'multi-geometry' meaning that each tool has it's own geometry
-        # the default value is False
-        self.multigeo = False
-
-        # flag to store if the geometry is part of a special group of geometries that can't be processed by the default
-        # engine of FlatCAM. Most likely are generated by some of tools and are special cases of geometries.
-        self.special_group = None
-
-        self.old_pp_state = self.app.defaults["geometry_multidepth"]
-        self.old_toolchangeg_state = self.app.defaults["geometry_toolchange"]
-        self.units_found = self.app.defaults['units']
-
-        # this variable can be updated by the Object that generates the geometry
-        self.tool_type = 'C1'
-
-        # save here the old value for the Cut Z before it is changed by selecting a V-shape type tool in the tool table
-        self.old_cutz = self.app.defaults["geometry_cutz"]
-
-        self.fill_color = self.app.defaults['geometry_plot_line']
-        self.outline_color = self.app.defaults['geometry_plot_line']
-        self.alpha_level = 'FF'
-
-        self.param_fields = {}
-
-        # Attributes to be included in serialization
-        # Always append to it because it carries contents
-        # from predecessors.
-        self.ser_attrs += ['options', 'kind', 'tools', 'multigeo']
-
-    def build_ui(self):
-        self.ui_disconnect()
-        FlatCAMObj.build_ui(self)
-
-        self.units = self.app.defaults['units']
-
-        tool_idx = 0
-
-        n = len(self.tools)
-        self.ui.geo_tools_table.setRowCount(n)
-
-        for tooluid_key, tooluid_value in self.tools.items():
-            tool_idx += 1
-            row_no = tool_idx - 1
-
-            tool_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
-            tool_id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-            self.ui.geo_tools_table.setItem(row_no, 0, tool_id)  # Tool name/id
-
-            # Make sure that the tool diameter when in MM is with no more than 2 decimals.
-            # There are no tool bits in MM with more than 3 decimals diameter.
-            # For INCH the decimals should be no more than 3. There are no tools under 10mils.
-
-            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooluid_value['tooldia'])))
-
-            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-            offset_item = FCComboBox()
-            for item in self.offset_item_options:
-                offset_item.addItem(item)
-            # offset_item.setStyleSheet('background-color: rgb(255,255,255)')
-            idx = offset_item.findText(tooluid_value['offset'])
-            offset_item.setCurrentIndex(idx)
-
-            type_item = FCComboBox()
-            for item in self.type_item_options:
-                type_item.addItem(item)
-            # type_item.setStyleSheet('background-color: rgb(255,255,255)')
-            idx = type_item.findText(tooluid_value['type'])
-            type_item.setCurrentIndex(idx)
-
-            tool_type_item = FCComboBox()
-            for item in self.tool_type_item_options:
-                tool_type_item.addItem(item)
-                # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
-            idx = tool_type_item.findText(tooluid_value['tool_type'])
-            tool_type_item.setCurrentIndex(idx)
-
-            tool_uid_item = QtWidgets.QTableWidgetItem(str(tooluid_key))
-
-            plot_item = FCCheckBox()
-            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
-            if self.ui.plot_cb.isChecked():
-                plot_item.setChecked(True)
-
-            self.ui.geo_tools_table.setItem(row_no, 1, dia_item)  # Diameter
-            self.ui.geo_tools_table.setCellWidget(row_no, 2, offset_item)
-            self.ui.geo_tools_table.setCellWidget(row_no, 3, type_item)
-            self.ui.geo_tools_table.setCellWidget(row_no, 4, tool_type_item)
-
-            # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY ###
-            self.ui.geo_tools_table.setItem(row_no, 5, tool_uid_item)  # Tool unique ID
-            self.ui.geo_tools_table.setCellWidget(row_no, 6, plot_item)
-
-            try:
-                self.ui.tool_offset_entry.set_value(tooluid_value['offset_value'])
-            except Exception as e:
-                log.debug("build_ui() --> Could not set the 'offset_value' key in self.tools. Error: %s" % str(e))
-
-        # make the diameter column editable
-        for row in range(tool_idx):
-            self.ui.geo_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable |
-                                                          QtCore.Qt.ItemIsEditable |
-                                                          QtCore.Qt.ItemIsEnabled)
-
-        # sort the tool diameter column
-        # self.ui.geo_tools_table.sortItems(1)
-        # all the tools are selected by default
-        # self.ui.geo_tools_table.selectColumn(0)
-
-        self.ui.geo_tools_table.resizeColumnsToContents()
-        self.ui.geo_tools_table.resizeRowsToContents()
-
-        vertical_header = self.ui.geo_tools_table.verticalHeader()
-        # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
-        vertical_header.hide()
-        self.ui.geo_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        horizontal_header = self.ui.geo_tools_table.horizontalHeader()
-        horizontal_header.setMinimumSectionSize(10)
-        horizontal_header.setDefaultSectionSize(70)
-        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
-        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
-        # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents)
-        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
-        horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(4, 40)
-        horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(4, 17)
-        # horizontal_header.setStretchLastSection(True)
-        self.ui.geo_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        self.ui.geo_tools_table.setColumnWidth(0, 20)
-        self.ui.geo_tools_table.setColumnWidth(4, 40)
-        self.ui.geo_tools_table.setColumnWidth(6, 17)
-
-        # self.ui.geo_tools_table.setSortingEnabled(True)
-
-        self.ui.geo_tools_table.setMinimumHeight(self.ui.geo_tools_table.getHeight())
-        self.ui.geo_tools_table.setMaximumHeight(self.ui.geo_tools_table.getHeight())
-
-        # update UI for all rows - useful after units conversion but only if there is at least one row
-        row_cnt = self.ui.geo_tools_table.rowCount()
-        if row_cnt > 0:
-            for r in range(row_cnt):
-                self.update_ui(r)
-
-        # select only the first tool / row
-        selected_row = 0
-        try:
-            self.select_tools_table_row(selected_row, clearsel=True)
-            # update the Geometry UI
-            self.update_ui()
-        except Exception as e:
-            # when the tools table is empty there will be this error but once the table is populated it will go away
-            log.debug(str(e))
-
-        # disable the Plot column in Tool Table if the geometry is SingleGeo as it is not needed
-        # and can create some problems
-        if self.multigeo is False:
-            self.ui.geo_tools_table.setColumnHidden(6, True)
-        else:
-            self.ui.geo_tools_table.setColumnHidden(6, False)
-
-        self.set_tool_offset_visibility(selected_row)
-
-        # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
-        # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
-        self.ui.name_entry.set_value(self.options['name'])
-        self.ui_connect()
-
-        self.ui.e_cut_entry.setDisabled(False) if self.ui.extracut_cb.get_value() else \
-            self.ui.e_cut_entry.setDisabled(True)
-
-        # set the text on tool_data_label after loading the object
-        sel_rows = []
-        sel_items = self.ui.geo_tools_table.selectedItems()
-        for it in sel_items:
-            sel_rows.append(it.row())
-        if len(sel_rows) > 1:
-            self.ui.tool_data_label.setText(
-                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
-            )
-
-    def set_ui(self, ui):
-        FlatCAMObj.set_ui(self, ui)
-
-        log.debug("FlatCAMGeometry.set_ui()")
-
-        assert isinstance(self.ui, GeometryObjectUI), \
-            "Expected a GeometryObjectUI, got %s" % type(self.ui)
-
-        self.units = self.app.defaults['units'].upper()
-        self.units_found = self.app.defaults['units']
-
-        # populate preprocessor names in the combobox
-        for name in list(self.app.preprocessors.keys()):
-            self.ui.pp_geometry_name_cb.addItem(name)
-
-        self.form_fields.update({
-            "plot": self.ui.plot_cb,
-            "cutz": self.ui.cutz_entry,
-            "vtipdia": self.ui.tipdia_entry,
-            "vtipangle": self.ui.tipangle_entry,
-            "travelz": self.ui.travelz_entry,
-            "feedrate": self.ui.cncfeedrate_entry,
-            "feedrate_z": self.ui.feedrate_z_entry,
-            "feedrate_rapid": self.ui.feedrate_rapid_entry,
-            "spindlespeed": self.ui.cncspindlespeed_entry,
-            "dwell": self.ui.dwell_cb,
-            "dwelltime": self.ui.dwelltime_entry,
-            "multidepth": self.ui.mpass_cb,
-            "ppname_g": self.ui.pp_geometry_name_cb,
-            "z_pdepth": self.ui.pdepth_entry,
-            "feedrate_probe": self.ui.feedrate_probe_entry,
-            "depthperpass": self.ui.maxdepth_entry,
-            "extracut": self.ui.extracut_cb,
-            "extracut_length": self.ui.e_cut_entry,
-            "toolchange": self.ui.toolchangeg_cb,
-            "toolchangez": self.ui.toolchangez_entry,
-            "endz": self.ui.endz_entry,
-            "endxy": self.ui.endxy_entry,
-            "cnctooldia": self.ui.addtool_entry
-        })
-
-        self.param_fields.update({
-            "vtipdia": self.ui.tipdia_entry,
-            "vtipangle": self.ui.tipangle_entry,
-            "cutz": self.ui.cutz_entry,
-            "depthperpass": self.ui.maxdepth_entry,
-            "multidepth": self.ui.mpass_cb,
-            "travelz": self.ui.travelz_entry,
-            "feedrate": self.ui.cncfeedrate_entry,
-            "feedrate_z": self.ui.feedrate_z_entry,
-            "feedrate_rapid": self.ui.feedrate_rapid_entry,
-            "extracut": self.ui.extracut_cb,
-            "extracut_length": self.ui.e_cut_entry,
-            "spindlespeed": self.ui.cncspindlespeed_entry,
-            "dwelltime": self.ui.dwelltime_entry,
-            "dwell": self.ui.dwell_cb,
-            "pdepth": self.ui.pdepth_entry,
-            "pfeedrate": self.ui.feedrate_probe_entry,
-        })
-        # Fill form fields only on object create
-        self.to_form()
-
-        # update the changes in UI depending on the selected preprocessor in Preferences
-        # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the
-        # self.ui.pp_geometry_name_cb combobox
-        self.on_pp_changed()
-
-        self.ui.tipdialabel.hide()
-        self.ui.tipdia_entry.hide()
-        self.ui.tipanglelabel.hide()
-        self.ui.tipangle_entry.hide()
-        self.ui.cutz_entry.setDisabled(False)
-
-        # store here the default data for Geometry Data
-        self.default_data = {}
-        self.default_data.update({
-            "name": None,
-            "plot": None,
-            "cutz": None,
-            "vtipdia": None,
-            "vtipangle": None,
-            "travelz": None,
-            "feedrate": None,
-            "feedrate_z": None,
-            "feedrate_rapid": None,
-            "dwell": None,
-            "dwelltime": None,
-            "multidepth": None,
-            "ppname_g": None,
-            "depthperpass": None,
-            "extracut": None,
-            "extracut_length": None,
-            "toolchange": None,
-            "toolchangez": None,
-            "endz": None,
-            "endxy": '',
-            "spindlespeed": 0,
-            "toolchangexy": None,
-            "startz": None
-        })
-
-        # fill in self.default_data values from self.options
-        for def_key in self.default_data:
-            for opt_key, opt_val in self.options.items():
-                if def_key == opt_key:
-                    self.default_data[def_key] = deepcopy(opt_val)
-
-        if type(self.options["cnctooldia"]) == float:
-            tools_list = [self.options["cnctooldia"]]
-        else:
-            try:
-                temp_tools = self.options["cnctooldia"].split(",")
-                tools_list = [
-                    float(eval(dia)) for dia in temp_tools if dia != ''
-                ]
-            except Exception as e:
-                log.error("FlatCAMGeometry.set_ui() -> At least one tool diameter needed. "
-                          "Verify in Edit -> Preferences -> Geometry General -> Tool dia. %s" % str(e))
-                return
-
-        self.tooluid += 1
-
-        if not self.tools:
-            for toold in tools_list:
-                new_data = deepcopy(self.default_data)
-                self.tools.update({
-                    self.tooluid: {
-                        'tooldia': float('%.*f' % (self.decimals, float(toold))),
-                        'offset': 'Path',
-                        'offset_value': 0.0,
-                        'type': _('Rough'),
-                        'tool_type': self.tool_type,
-                        'data': new_data,
-                        'solid_geometry': self.solid_geometry
-                    }
-                })
-                self.tooluid += 1
-        else:
-            # if self.tools is not empty then it can safely be assumed that it comes from an opened project.
-            # Because of the serialization the self.tools list on project save, the dict keys (members of self.tools
-            # are each a dict) are turned into strings so we rebuild the self.tools elements so the keys are
-            # again float type; dict's don't like having keys changed when iterated through therefore the need for the
-            # following convoluted way of changing the keys from string to float type
-            temp_tools = {}
-            for tooluid_key in self.tools:
-                val = deepcopy(self.tools[tooluid_key])
-                new_key = deepcopy(int(tooluid_key))
-                temp_tools[new_key] = val
-
-            self.tools.clear()
-            self.tools = deepcopy(temp_tools)
-
-        self.ui.tool_offset_entry.hide()
-        self.ui.tool_offset_lbl.hide()
-
-        # used to store the state of the mpass_cb if the selected preprocessor for geometry is hpgl
-        self.old_pp_state = self.default_data['multidepth']
-        self.old_toolchangeg_state = self.default_data['toolchange']
-
-        if not isinstance(self.ui, GeometryObjectUI):
-            log.debug("Expected a GeometryObjectUI, got %s" % type(self.ui))
-            return
-
-        self.ui.geo_tools_table.setupContextMenu()
-        self.ui.geo_tools_table.addContextMenu(
-            _("Add from Tool 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"))
-
-        # Show/Hide Advanced Options
-        if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
-
-            self.ui.geo_tools_table.setColumnHidden(2, True)
-            self.ui.geo_tools_table.setColumnHidden(3, True)
-            # 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.deltool_btn.hide()
-            # self.ui.endz_label.hide()
-            # self.ui.endz_entry.hide()
-            self.ui.fr_rapidlabel.hide()
-            self.ui.feedrate_rapid_entry.hide()
-            self.ui.extracut_cb.hide()
-            self.ui.e_cut_entry.hide()
-            self.ui.pdepth_label.hide()
-            self.ui.pdepth_entry.hide()
-            self.ui.feedrate_probe_label.hide()
-            self.ui.feedrate_probe_entry.hide()
-        else:
-            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
-
-        self.ui.e_cut_entry.setDisabled(False) if self.app.defaults['geometry_extracut'] else \
-            self.ui.e_cut_entry.setDisabled(True)
-        self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state))
-
-        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
-        self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
-        self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
-        self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
-        self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed)
-
-        self.ui.tipdia_entry.valueChanged.connect(self.update_cutz)
-        self.ui.tipangle_entry.valueChanged.connect(self.update_cutz)
-
-        self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
-        self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
-        self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed)
-
-    def on_cut_z_changed(self):
-        self.old_cutz = self.ui.cutz_entry.get_value()
-
-    def set_tool_offset_visibility(self, current_row):
-        if current_row is None:
-            return
-        try:
-            tool_offset = self.ui.geo_tools_table.cellWidget(current_row, 2)
-            if tool_offset is not None:
-                tool_offset_txt = tool_offset.currentText()
-                if tool_offset_txt == 'Custom':
-                    self.ui.tool_offset_entry.show()
-                    self.ui.tool_offset_lbl.show()
-                else:
-                    self.ui.tool_offset_entry.hide()
-                    self.ui.tool_offset_lbl.hide()
-        except Exception as e:
-            log.debug("set_tool_offset_visibility() --> " + str(e))
-            return
-
-    def on_offset_value_edited(self):
-        """
-        This will save the offset_value into self.tools storage whenever the offset value is edited
-        :return:
-        """
-
-        for current_row in self.ui.geo_tools_table.selectedItems():
-            # sometime the header get selected and it has row number -1
-            # we don't want to do anything with the header :)
-            if current_row.row() < 0:
-                continue
-            tool_uid = int(self.ui.geo_tools_table.item(current_row.row(), 5).text())
-            self.set_tool_offset_visibility(current_row.row())
-
-            for tooluid_key, tooluid_value in self.tools.items():
-                if int(tooluid_key) == tool_uid:
-                    try:
-                        tooluid_value['offset_value'] = float(self.ui.tool_offset_entry.get_value())
-                    except ValueError:
-                        # try to convert comma to decimal point. if it's still not working error message and return
-                        try:
-                            tooluid_value['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."))
-                            return
-
-    def ui_connect(self):
-        # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the
-        # changes in geometry UI
-        for i in self.param_fields:
-            current_widget = self.param_fields[i]
-            if isinstance(current_widget, FCCheckBox):
-                current_widget.stateChanged.connect(self.gui_form_to_storage)
-            elif isinstance(current_widget, FCComboBox):
-                current_widget.currentIndexChanged.connect(self.gui_form_to_storage)
-            elif isinstance(current_widget, FloatEntry) or isinstance(current_widget, LengthEntry) or \
-                    isinstance(current_widget, FCEntry) or isinstance(current_widget, IntEntry):
-                current_widget.editingFinished.connect(self.gui_form_to_storage)
-            elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner):
-                current_widget.returnPressed.connect(self.gui_form_to_storage)
-
-        for row in range(self.ui.geo_tools_table.rowCount()):
-            for col in [2, 3, 4]:
-                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.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
-        self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete())
-
-        # self.ui.geo_tools_table.currentItemChanged.connect(self.on_row_selection_change)
-        self.ui.geo_tools_table.clicked.connect(self.on_row_selection_change)
-        self.ui.geo_tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change)
-
-        self.ui.geo_tools_table.itemChanged.connect(self.on_tool_edit)
-        self.ui.tool_offset_entry.returnPressed.connect(self.on_offset_value_edited)
-
-        for row in range(self.ui.geo_tools_table.rowCount()):
-            self.ui.geo_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table)
-        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
-
-        # common parameters update
-        self.ui.pp_geometry_name_cb.currentIndexChanged.connect(self.update_common_param_in_storage)
-
-    def ui_disconnect(self):
-
-        # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the
-        # changes in geometry UI
-        for i in self.param_fields:
-            # current_widget = self.ui.grid3.itemAt(i).widget()
-            current_widget = self.param_fields[i]
-            if isinstance(current_widget, FCCheckBox):
-                try:
-                    current_widget.stateChanged.disconnect(self.gui_form_to_storage)
-                except (TypeError, AttributeError):
-                    pass
-            elif isinstance(current_widget, FCComboBox):
-                try:
-                    current_widget.currentIndexChanged.disconnect(self.gui_form_to_storage)
-                except (TypeError, AttributeError):
-                    pass
-            elif isinstance(current_widget, LengthEntry) or isinstance(current_widget, IntEntry) or \
-                    isinstance(current_widget, FCEntry) or isinstance(current_widget, FloatEntry):
-                try:
-                    current_widget.editingFinished.disconnect(self.gui_form_to_storage)
-                except (TypeError, AttributeError):
-                    pass
-            elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner):
-                try:
-                    current_widget.returnPressed.disconnect(self.gui_form_to_storage)
-                except TypeError:
-                    pass
-
-        for row in range(self.ui.geo_tools_table.rowCount()):
-            for col in [2, 3, 4]:
-                try:
-                    self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.disconnect()
-                except (TypeError, AttributeError):
-                    pass
-
-        try:
-            self.ui.addtool_btn.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.copytool_btn.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.deltool_btn.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.geo_tools_table.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-        try:
-            self.ui.geo_tools_table.horizontalHeader().sectionClicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.geo_tools_table.itemChanged.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.tool_offset_entry.returnPressed.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        for row in range(self.ui.geo_tools_table.rowCount()):
-            try:
-                self.ui.geo_tools_table.cellWidget(row, 6).clicked.disconnect()
-            except (TypeError, AttributeError):
-                pass
-
-        try:
-            self.ui.plot_cb.stateChanged.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-    def on_row_selection_change(self):
-        self.update_ui()
-
-    def update_ui(self, row=None):
-        self.ui_disconnect()
-
-        if row is None:
-            sel_rows = []
-            sel_items = self.ui.geo_tools_table.selectedItems()
-            for it in sel_items:
-                sel_rows.append(it.row())
-        else:
-            sel_rows = row if type(row) == list else [row]
-
-        if not sel_rows:
-            sel_rows = [0]
-
-        for current_row in sel_rows:
-            self.set_tool_offset_visibility(current_row)
-
-            # populate the form with the data from the tool associated with the row parameter
-            try:
-                item = self.ui.geo_tools_table.item(current_row, 5)
-                if type(item) is not None:
-                    tooluid = int(item.text())
-                else:
-                    self.ui_connect()
-                    return
-            except Exception as e:
-                log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e))
-                self.ui_connect()
-                return
-
-            # update the QLabel that shows for which Tool we have the parameters in the UI form
-            if len(sel_rows) == 1:
-                self.ui.tool_data_label.setText(
-                    "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), tooluid)
-                )
-
-                # update the form with the V-Shape fields if V-Shape selected in the geo_tool_table
-                # also modify the Cut Z form entry to reflect the calculated Cut Z from values got from V-Shape Fields
-                try:
-                    item = self.ui.geo_tools_table.cellWidget(current_row, 4)
-                    if item is not None:
-                        tool_type_txt = item.currentText()
-                        self.ui_update_v_shape(tool_type_txt=tool_type_txt)
-                    else:
-                        self.ui_connect()
-                        return
-                except Exception as e:
-                    log.debug("Tool missing in ui_update_v_shape(). Add a tool in Geo Tool Table. %s" % str(e))
-                    return
-
-                try:
-                    # set the form with data from the newly selected tool
-                    for tooluid_key, tooluid_value in list(self.tools.items()):
-                        if int(tooluid_key) == tooluid:
-                            for key, value in list(tooluid_value.items()):
-                                if key == 'data':
-                                    form_value_storage = tooluid_value['data']
-                                    self.update_form(form_value_storage)
-                                if key == 'offset_value':
-                                    # update the offset value in the entry even if the entry is hidden
-                                    self.ui.tool_offset_entry.set_value(tooluid_value['offset_value'])
-
-                                if key == 'tool_type' and value == 'V':
-                                    self.update_cutz()
-                except Exception as e:
-                    log.debug("FlatCAMGeometry.update_ui() -> %s " % str(e))
-
-            else:
-                self.ui.tool_data_label.setText(
-                    "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
-                )
-
-        self.ui_connect()
-
-    def on_tool_add(self, dia=None):
-        self.ui_disconnect()
-
-        self.units = self.app.defaults['units'].upper()
-
-        if dia is not None:
-            tooldia = dia
-        else:
-            tooldia = float(self.ui.addtool_entry.get_value())
-
-        # construct a list of all 'tooluid' in the self.tools
-        # tool_uid_list = []
-        # for tooluid_key in self.tools:
-        #     tool_uid_list.append(int(tooluid_key))
-        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
-
-        tooldia = float('%.*f' % (self.decimals, tooldia))
-
-        # 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
-        if self.tools:
-            last_data = self.tools[max_uid]['data']
-            last_offset = self.tools[max_uid]['offset']
-            last_offset_value = self.tools[max_uid]['offset_value']
-            last_type = self.tools[max_uid]['type']
-            last_tool_type = self.tools[max_uid]['tool_type']
-            last_solid_geometry = self.tools[max_uid]['solid_geometry']
-
-            # if previous geometry was empty (it may happen for the first tool added)
-            # then copy the object.solid_geometry
-            if not last_solid_geometry:
-                last_solid_geometry = self.solid_geometry
-
-            self.tools.update({
-                self.tooluid: {
-                    'tooldia': tooldia,
-                    'offset': last_offset,
-                    'offset_value': last_offset_value,
-                    'type': last_type,
-                    'tool_type': last_tool_type,
-                    'data': deepcopy(last_data),
-                    'solid_geometry': deepcopy(last_solid_geometry)
-                }
-            })
-        else:
-            self.tools.update({
-                self.tooluid: {
-                    'tooldia': tooldia,
-                    'offset': 'Path',
-                    'offset_value': 0.0,
-                    'type': _('Rough'),
-                    'tool_type': 'C1',
-                    'data': deepcopy(self.default_data),
-                    'solid_geometry': self.solid_geometry
-                }
-            })
-
-        self.tools[self.tooluid]['data']['name'] = self.options['name']
-
-        self.ui.tool_offset_entry.hide()
-        self.ui.tool_offset_lbl.hide()
-
-        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
-        try:
-            self.ser_attrs.remove('tools')
-        except TypeError:
-            pass
-        self.ser_attrs.append('tools')
-
-        self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
-        self.ui_connect()
-        self.build_ui()
-
-        # if there is no 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)
-
-    def on_tool_add_from_db_clicked(self):
-        """
-        Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object
-        and display the Tools Database tab in the form needed for the Tool adding
-        :return: None
-        """
-
-        # if the Tools Database is already opened focus on it
-        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.setCurrentWidget(self.app.tools_db_tab)
-                break
-        self.app.on_tools_database()
-        self.app.tools_db_tab.ok_to_add = True
-        self.app.tools_db_tab.buttons_frame.hide()
-        self.app.tools_db_tab.add_tool_from_db.show()
-        self.app.tools_db_tab.cancel_tool_from_db.show()
-
-    def on_tool_from_db_inserted(self, tool):
-        """
-        Called from the Tools DB object through a App method when adding a tool from Tools Database
-        :param tool: a dict with the tool data
-        :return: None
-        """
-
-        self.ui_disconnect()
-        self.units = self.app.defaults['units'].upper()
-
-        tooldia = float(tool['tooldia'])
-
-        # construct a list of all 'tooluid' in the self.tools
-        tool_uid_list = []
-        for tooluid_key in self.tools:
-            tool_uid_item = int(tooluid_key)
-            tool_uid_list.append(tool_uid_item)
-
-        # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
-        if not tool_uid_list:
-            max_uid = 0
-        else:
-            max_uid = max(tool_uid_list)
-        self.tooluid = max_uid + 1
-
-        tooldia = float('%.*f' % (self.decimals, tooldia))
-
-        self.tools.update({
-            self.tooluid: {
-                'tooldia': tooldia,
-                'offset': tool['offset'],
-                'offset_value': float(tool['offset_value']),
-                'type': tool['type'],
-                'tool_type': tool['tool_type'],
-                'data': deepcopy(tool['data']),
-                'solid_geometry': self.solid_geometry
-            }
-        })
-
-        self.tools[self.tooluid]['data']['name'] = self.options['name']
-
-        self.ui.tool_offset_entry.hide()
-        self.ui.tool_offset_lbl.hide()
-
-        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
-        try:
-            self.ser_attrs.remove('tools')
-        except TypeError:
-            pass
-        self.ser_attrs.append('tools')
-
-        self.ui_connect()
-        self.build_ui()
-
-        # if there is no 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)
-
-    def on_tool_copy(self, all_tools=None):
-        self.ui_disconnect()
-
-        # find the tool_uid maximum value in the self.tools
-        uid_list = []
-        for key in self.tools:
-            uid_list.append(int(key))
-        try:
-            max_uid = max(uid_list, key=int)
-        except ValueError:
-            max_uid = 0
-
-        if all_tools is None:
-            if self.ui.geo_tools_table.selectedItems():
-                for current_row in self.ui.geo_tools_table.selectedItems():
-                    # sometime the header get selected and it has row number -1
-                    # we don't want to do anything with the header :)
-                    if current_row.row() < 0:
-                        continue
-                    try:
-                        tooluid_copy = int(self.ui.geo_tools_table.item(current_row.row(), 5).text())
-                        self.set_tool_offset_visibility(current_row.row())
-                        max_uid += 1
-                        self.tools[int(max_uid)] = deepcopy(self.tools[tooluid_copy])
-                    except AttributeError:
-                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy."))
-                        self.ui_connect()
-                        self.build_ui()
-                        return
-                    except Exception as e:
-                        log.debug("on_tool_copy() --> " + str(e))
-                # deselect the table
-                # self.ui.geo_tools_table.clearSelection()
-            else:
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy."))
-                self.ui_connect()
-                self.build_ui()
-                return
-        else:
-            # we copy all tools in geo_tools_table
-            try:
-                temp_tools = deepcopy(self.tools)
-                max_uid += 1
-                for tooluid in temp_tools:
-                    self.tools[int(max_uid)] = deepcopy(temp_tools[tooluid])
-                temp_tools.clear()
-            except Exception as e:
-                log.debug("on_tool_copy() --> " + str(e))
-
-        # if there are no more tools in geo tools table then hide the tool offset
-        if not self.tools:
-            self.ui.tool_offset_entry.hide()
-            self.ui.tool_offset_lbl.hide()
-
-        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
-        try:
-            self.ser_attrs.remove('tools')
-        except ValueError:
-            pass
-        self.ser_attrs.append('tools')
-
-        self.ui_connect()
-        self.build_ui()
-        self.app.inform.emit('[success] %s' % _("Tool was copied in Tool Table."))
-
-    def on_tool_edit(self, current_item):
-        self.ui_disconnect()
-
-        current_row = current_item.row()
-        try:
-            d = float(self.ui.geo_tools_table.item(current_row, 1).text())
-        except ValueError:
-            # try to convert comma to decimal point. if it's still not working error message and return
-            try:
-                d = float(self.ui.geo_tools_table.item(current_row, 1).text().replace(',', '.'))
-            except ValueError:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
-                return
-
-        tool_dia = float('%.*f' % (self.decimals, d))
-        tooluid = int(self.ui.geo_tools_table.item(current_row, 5).text())
-
-        self.tools[tooluid]['tooldia'] = tool_dia
-
-        try:
-            self.ser_attrs.remove('tools')
-            self.ser_attrs.append('tools')
-        except (TypeError, ValueError):
-            pass
-
-        self.app.inform.emit('[success] %s' % _("Tool was edited in Tool Table."))
-        self.ui_connect()
-        self.build_ui()
-
-    def on_tool_delete(self, all_tools=None):
-        self.ui_disconnect()
-
-        if all_tools is None:
-            if self.ui.geo_tools_table.selectedItems():
-                for current_row in self.ui.geo_tools_table.selectedItems():
-                    # sometime the header get selected and it has row number -1
-                    # we don't want to do anything with the header :)
-                    if current_row.row() < 0:
-                        continue
-                    try:
-                        tooluid_del = int(self.ui.geo_tools_table.item(current_row.row(), 5).text())
-                        self.set_tool_offset_visibility(current_row.row())
-
-                        temp_tools = deepcopy(self.tools)
-                        for tooluid_key in self.tools:
-                            if int(tooluid_key) == tooluid_del:
-                                # if the self.tools has only one tool and we delete it then we move the solid_geometry
-                                # as a property of the object otherwise there will be nothing to hold it
-                                if len(self.tools) == 1:
-                                    self.solid_geometry = deepcopy(self.tools[tooluid_key]['solid_geometry'])
-                                temp_tools.pop(tooluid_del, None)
-                        self.tools = deepcopy(temp_tools)
-                        temp_tools.clear()
-                    except AttributeError:
-                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete."))
-                        self.ui_connect()
-                        self.build_ui()
-                        return
-                    except Exception as e:
-                        log.debug("on_tool_delete() --> " + str(e))
-                # deselect the table
-                # self.ui.geo_tools_table.clearSelection()
-            else:
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete."))
-                self.ui_connect()
-                self.build_ui()
-                return
-        else:
-            # we delete all tools in geo_tools_table
-            self.tools.clear()
-
-        self.app.plot_all()
-
-        # if there are no more tools in geo tools table then hide the tool offset
-        if not self.tools:
-            self.ui.tool_offset_entry.hide()
-            self.ui.tool_offset_lbl.hide()
-
-        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
-        try:
-            self.ser_attrs.remove('tools')
-        except TypeError:
-            pass
-        self.ser_attrs.append('tools')
-
-        self.ui_connect()
-        self.build_ui()
-        self.app.inform.emit('[success] %s' % _("Tool was deleted in Tool Table."))
-
-        obj_active = self.app.collection.get_active()
-        # if the object was MultiGeo and now it has no tool at all (therefore no geometry)
-        # we make it back SingleGeo
-        if self.ui.geo_tools_table.rowCount() <= 0:
-            obj_active.multigeo = False
-            obj_active.options['xmin'] = 0
-            obj_active.options['ymin'] = 0
-            obj_active.options['xmax'] = 0
-            obj_active.options['ymax'] = 0
-
-        if obj_active.multigeo is True:
-            try:
-                xmin, ymin, xmax, ymax = obj_active.bounds()
-                obj_active.options['xmin'] = xmin
-                obj_active.options['ymin'] = ymin
-                obj_active.options['xmax'] = xmax
-                obj_active.options['ymax'] = ymax
-            except Exception:
-                obj_active.options['xmin'] = 0
-                obj_active.options['ymin'] = 0
-                obj_active.options['xmax'] = 0
-                obj_active.options['ymax'] = 0
-
-        # if there is no tool left in the Tools Table, disable the parameters GUI
-        if self.ui.geo_tools_table.rowCount() == 0:
-            self.ui.geo_param_frame.setDisabled(True)
-
-    def ui_update_v_shape(self, tool_type_txt):
-        if tool_type_txt == 'V':
-            self.ui.tipdialabel.show()
-            self.ui.tipdia_entry.show()
-            self.ui.tipanglelabel.show()
-            self.ui.tipangle_entry.show()
-            self.ui.cutz_entry.setDisabled(True)
-
-            self.update_cutz()
-        else:
-            self.ui.tipdialabel.hide()
-            self.ui.tipdia_entry.hide()
-            self.ui.tipanglelabel.hide()
-            self.ui.tipangle_entry.hide()
-            self.ui.cutz_entry.setDisabled(False)
-
-    def update_cutz(self):
-        vdia = float(self.ui.tipdia_entry.get_value())
-        half_vangle = float(self.ui.tipangle_entry.get_value()) / 2
-
-        row = self.ui.geo_tools_table.currentRow()
-        tool_uid_item = self.ui.geo_tools_table.item(row, 5)
-        if tool_uid_item is None:
-            return
-        tool_uid = int(tool_uid_item.text())
-
-        tool_dia_item = self.ui.geo_tools_table.item(row, 1)
-        if tool_dia_item is None:
-            return
-        tooldia = float(tool_dia_item.text())
-
-        try:
-            new_cutz = (tooldia - vdia) / (2 * math.tan(math.radians(half_vangle)))
-        except ZeroDivisionError:
-            new_cutz = self.old_cutz
-
-        new_cutz = float('%.*f' % (self.decimals, new_cutz)) * -1.0   # this value has to be negative
-
-        self.ui.cutz_entry.set_value(new_cutz)
-
-        # store the new CutZ value into storage (self.tools)
-        for tooluid_key, tooluid_value in self.tools.items():
-            if int(tooluid_key) == tool_uid:
-                tooluid_value['data']['cutz'] = new_cutz
-
-    def on_tooltable_cellwidget_change(self):
-        cw = self.sender()
-        cw_index = self.ui.geo_tools_table.indexAt(cw.pos())
-        cw_row = cw_index.row()
-        cw_col = cw_index.column()
-        current_uid = int(self.ui.geo_tools_table.item(cw_row, 5).text())
-
-        # store the text of the cellWidget that changed it's index in the self.tools
-        for tooluid_key, tooluid_value in self.tools.items():
-            if int(tooluid_key) == current_uid:
-                cb_txt = cw.currentText()
-                if cw_col == 2:
-                    tooluid_value['offset'] = cb_txt
-                    if cb_txt == 'Custom':
-                        self.ui.tool_offset_entry.show()
-                        self.ui.tool_offset_lbl.show()
-                    else:
-                        self.ui.tool_offset_entry.hide()
-                        self.ui.tool_offset_lbl.hide()
-                        # reset the offset_value in storage self.tools
-                        tooluid_value['offset_value'] = 0.0
-                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'))
-                        self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx)
-                    else:
-                        tooluid_value['type'] = cb_txt
-                elif cw_col == 4:
-                    tooluid_value['tool_type'] = cb_txt
-
-                    # 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'))
-                        self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx)
-                    else:
-                        self.ui.cutz_entry.set_value(self.old_cutz)
-
-                self.ui_update_v_shape(tool_type_txt=self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText())
-
-    def update_form(self, dict_storage):
-        for form_key in self.form_fields:
-            for storage_key in dict_storage:
-                if form_key == storage_key:
-                    try:
-                        self.form_fields[form_key].set_value(dict_storage[form_key])
-                    except Exception as e:
-                        log.debug(str(e))
-
-        # this is done here because those buttons control through OptionalInputSelection if some entry's are Enabled
-        # or not. But due of using the ui_disconnect() status is no longer updated and I had to do it here
-        self.ui.ois_dwell_geo.on_cb_change()
-        self.ui.ois_mpass_geo.on_cb_change()
-        self.ui.ois_tcz_geo.on_cb_change()
-
-    def on_apply_param_to_all_clicked(self):
-        if self.ui.geo_tools_table.rowCount() == 0:
-            # there is no tool in tool table so we can't save the GUI elements values to storage
-            log.debug("FlatCAMGeometry.gui_form_to_storage() --> no tool in Tools Table, aborting.")
-            return
-
-        self.ui_disconnect()
-
-        row = self.ui.geo_tools_table.currentRow()
-        if row < 0:
-            row = 0
-
-        # store all the data associated with the row parameter to the self.tools storage
-        tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text())
-        offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText()
-        type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText()
-        tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText()
-
-        offset_value_item = float(self.ui.tool_offset_entry.get_value())
-
-        # this new dict will hold the actual useful data, another dict that is the value of key 'data'
-        temp_tools = {}
-        temp_dia = {}
-        temp_data = {}
-
-        for tooluid_key, tooluid_value in self.tools.items():
-            for key, value in tooluid_value.items():
-                if key == 'tooldia':
-                    temp_dia[key] = tooldia_item
-                # update the 'offset', 'type' and 'tool_type' sections
-                if key == 'offset':
-                    temp_dia[key] = offset_item
-                if key == 'type':
-                    temp_dia[key] = type_item
-                if key == 'tool_type':
-                    temp_dia[key] = tool_type_item
-                if key == 'offset_value':
-                    temp_dia[key] = offset_value_item
-
-                if key == 'data':
-                    # update the 'data' section
-                    for data_key in tooluid_value[key].keys():
-                        for form_key, form_value in self.form_fields.items():
-                            if form_key == data_key:
-                                temp_data[data_key] = form_value.get_value()
-                        # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
-                        # updated from self.app.defaults
-                        if data_key not in self.form_fields:
-                            temp_data[data_key] = value[data_key]
-                    temp_dia[key] = deepcopy(temp_data)
-                    temp_data.clear()
-
-                if key == 'solid_geometry':
-                    temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry'])
-
-                temp_tools[tooluid_key] = deepcopy(temp_dia)
-
-        self.tools.clear()
-        self.tools = deepcopy(temp_tools)
-        temp_tools.clear()
-
-        self.ui_connect()
-
-    def gui_form_to_storage(self):
-        if self.ui.geo_tools_table.rowCount() == 0:
-            # there is no tool in tool table so we can't save the GUI elements values to storage
-            log.debug("FlatCAMGeometry.gui_form_to_storage() --> no tool in Tools Table, aborting.")
-            return
-
-        self.ui_disconnect()
-        widget_changed = self.sender()
-        try:
-            widget_idx = self.ui.grid3.indexOf(widget_changed)
-        except Exception:
-            return
-
-        # those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z
-        if widget_idx == 1 or widget_idx == 3:
-            self.update_cutz()
-
-        # the original connect() function of the OptionalInputSelection is no longer working because of the
-        # ui_diconnect() so I use this 'hack'
-        if isinstance(widget_changed, FCCheckBox):
-            if widget_changed.text() == 'Multi-Depth:':
-                self.ui.ois_mpass_geo.on_cb_change()
-
-            if widget_changed.text() == 'Tool change':
-                self.ui.ois_tcz_geo.on_cb_change()
-
-            if widget_changed.text() == 'Dwell:':
-                self.ui.ois_dwell_geo.on_cb_change()
-
-        row = self.ui.geo_tools_table.currentRow()
-        if row < 0:
-            row = 0
-
-        # store all the data associated with the row parameter to the self.tools storage
-        tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text())
-        offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText()
-        type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText()
-        tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText()
-        tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text())
-
-        offset_value_item = float(self.ui.tool_offset_entry.get_value())
-
-        # this new dict will hold the actual useful data, another dict that is the value of key 'data'
-        temp_tools = {}
-        temp_dia = {}
-        temp_data = {}
-
-        for tooluid_key, tooluid_value in self.tools.items():
-            if int(tooluid_key) == tooluid_item:
-                for key, value in tooluid_value.items():
-                    if key == 'tooldia':
-                        temp_dia[key] = tooldia_item
-                    # update the 'offset', 'type' and 'tool_type' sections
-                    if key == 'offset':
-                        temp_dia[key] = offset_item
-                    if key == 'type':
-                        temp_dia[key] = type_item
-                    if key == 'tool_type':
-                        temp_dia[key] = tool_type_item
-                    if key == 'offset_value':
-                        temp_dia[key] = offset_value_item
-
-                    if key == 'data':
-                        # update the 'data' section
-                        for data_key in tooluid_value[key].keys():
-                            for form_key, form_value in self.form_fields.items():
-                                if form_key == data_key:
-                                    temp_data[data_key] = form_value.get_value()
-                            # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
-                            # updated from self.app.defaults
-                            if data_key not in self.form_fields:
-                                temp_data[data_key] = value[data_key]
-                        temp_dia[key] = deepcopy(temp_data)
-                        temp_data.clear()
-
-                    if key == 'solid_geometry':
-                        temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry'])
-
-                    temp_tools[tooluid_key] = deepcopy(temp_dia)
-            else:
-                temp_tools[tooluid_key] = deepcopy(tooluid_value)
-
-        self.tools.clear()
-        self.tools = deepcopy(temp_tools)
-        temp_tools.clear()
-        self.ui_connect()
-
-    def update_common_param_in_storage(self):
-        for tooluid_value in self.tools.values():
-            tooluid_value['data']['ppname_g'] = self.ui.pp_geometry_name_cb.get_value()
-
-    def select_tools_table_row(self, row, clearsel=None):
-        if clearsel:
-            self.ui.geo_tools_table.clearSelection()
-
-        if self.ui.geo_tools_table.rowCount() > 0:
-            # self.ui.geo_tools_table.item(row, 0).setSelected(True)
-            self.ui.geo_tools_table.setCurrentItem(self.ui.geo_tools_table.item(row, 0))
-
-    def export_dxf(self):
-        dwg = None
-        try:
-            dwg = ezdxf.new('R2010')
-            msp = dwg.modelspace()
-
-            def g2dxf(dxf_space, geo_obj):
-                if isinstance(geo_obj, MultiPolygon):
-                    for poly in geo_obj:
-                        ext_points = list(poly.exterior.coords)
-                        dxf_space.add_lwpolyline(ext_points)
-                        for interior in poly.interiors:
-                            dxf_space.add_lwpolyline(list(interior.coords))
-                if isinstance(geo_obj, Polygon):
-                    ext_points = list(geo_obj.exterior.coords)
-                    dxf_space.add_lwpolyline(ext_points)
-                    for interior in geo_obj.interiors:
-                        dxf_space.add_lwpolyline(list(interior.coords))
-                if isinstance(geo_obj, MultiLineString):
-                    for line in geo_obj:
-                        dxf_space.add_lwpolyline(list(line.coords))
-                if isinstance(geo_obj, LineString) or isinstance(geo_obj, LinearRing):
-                    dxf_space.add_lwpolyline(list(geo_obj.coords))
-
-            multigeo_solid_geometry = []
-            if self.multigeo:
-                for tool in self.tools:
-                    multigeo_solid_geometry += self.tools[tool]['solid_geometry']
-            else:
-                multigeo_solid_geometry = self.solid_geometry
-
-            for geo in multigeo_solid_geometry:
-                if type(geo) == list:
-                    for g in geo:
-                        g2dxf(msp, g)
-                else:
-                    g2dxf(msp, geo)
-
-                # points = FlatCAMGeometry.get_pts(geo)
-                # msp.add_lwpolyline(points)
-        except Exception as e:
-            log.debug(str(e))
-
-        return dwg
-
-    def get_selected_tools_table_items(self):
-        """
-        Returns a list of lists, each list in the list is made out of row elements
-
-        :return: List of table_tools items.
-        :rtype: list
-        """
-        table_tools_items = []
-        if self.multigeo:
-            for x in self.ui.geo_tools_table.selectedItems():
-                elem = []
-                txt = ''
-
-                for column in range(0, self.ui.geo_tools_table.columnCount()):
-                    try:
-                        txt = self.ui.geo_tools_table.item(x.row(), column).text()
-                    except AttributeError:
-                        try:
-                            txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText()
-                        except AttributeError:
-                            pass
-                    elem.append(txt)
-                table_tools_items.append(deepcopy(elem))
-                # table_tools_items.append([self.ui.geo_tools_table.item(x.row(), column).text()
-                #                           for column in range(0, self.ui.geo_tools_table.columnCount())])
-        else:
-            for x in self.ui.geo_tools_table.selectedItems():
-                r = []
-                txt = ''
-
-                # the last 2 columns for single-geo geometry are irrelevant and create problems reading
-                # so we don't read them
-                for column in range(0, self.ui.geo_tools_table.columnCount() - 2):
-                    # the columns have items that have text but also have items that are widgets
-                    # for which the text they hold has to be read differently
-                    try:
-                        txt = self.ui.geo_tools_table.item(x.row(), column).text()
-                    except AttributeError:
-                        try:
-                            txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText()
-                        except AttributeError:
-                            pass
-                    r.append(txt)
-                table_tools_items.append(r)
-
-        for item in table_tools_items:
-            item[0] = str(item[0])
-        return table_tools_items
-
-    def on_pp_changed(self):
-        current_pp = self.ui.pp_geometry_name_cb.get_value()
-        if current_pp == 'hpgl':
-            self.old_pp_state = self.ui.mpass_cb.get_value()
-            self.old_toolchangeg_state = self.ui.toolchangeg_cb.get_value()
-
-            self.ui.mpass_cb.set_value(False)
-            self.ui.mpass_cb.setDisabled(True)
-
-            self.ui.toolchangeg_cb.set_value(True)
-            self.ui.toolchangeg_cb.setDisabled(True)
-        else:
-            self.ui.mpass_cb.set_value(self.old_pp_state)
-            self.ui.mpass_cb.setDisabled(False)
-
-            self.ui.toolchangeg_cb.set_value(self.old_toolchangeg_state)
-            self.ui.toolchangeg_cb.setDisabled(False)
-
-        if "toolchange_probe" in current_pp.lower():
-            self.ui.pdepth_entry.setVisible(True)
-            self.ui.pdepth_label.show()
-
-            self.ui.feedrate_probe_entry.setVisible(True)
-            self.ui.feedrate_probe_label.show()
-        else:
-            self.ui.pdepth_entry.setVisible(False)
-            self.ui.pdepth_label.hide()
-
-            self.ui.feedrate_probe_entry.setVisible(False)
-            self.ui.feedrate_probe_label.hide()
-
-        if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
-            self.ui.fr_rapidlabel.show()
-            self.ui.feedrate_rapid_entry.show()
-        else:
-            self.ui.fr_rapidlabel.hide()
-            self.ui.feedrate_rapid_entry.hide()
-
-        if 'laser' in current_pp.lower():
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_entry.hide()
-            try:
-                self.ui.mpass_cb.hide()
-                self.ui.maxdepth_entry.hide()
-            except AttributeError:
-                pass
-
-            if 'marlin' in current_pp.lower():
-                self.ui.travelzlabel.setText('%s:' % _("Focus Z"))
-                self.ui.endz_label.show()
-                self.ui.endz_entry.show()
-            else:
-                self.ui.travelzlabel.hide()
-                self.ui.travelz_entry.hide()
-
-                self.ui.endz_label.hide()
-                self.ui.endz_entry.hide()
-
-            try:
-                self.ui.frzlabel.hide()
-                self.ui.feedrate_z_entry.hide()
-            except AttributeError:
-                pass
-
-            self.ui.dwell_cb.hide()
-            self.ui.dwelltime_entry.hide()
-
-            self.ui.spindle_label.setText('%s:' % _("Laser Power"))
-
-            try:
-                self.ui.tool_offset_label.hide()
-                self.ui.offset_entry.hide()
-            except AttributeError:
-                pass
-        else:
-            self.ui.cutzlabel.show()
-            self.ui.cutz_entry.show()
-            try:
-                self.ui.mpass_cb.show()
-                self.ui.maxdepth_entry.show()
-            except AttributeError:
-                pass
-
-            self.ui.travelzlabel.setText('%s:' % _('Travel Z'))
-
-            self.ui.travelzlabel.show()
-            self.ui.travelz_entry.show()
-
-            self.ui.endz_label.show()
-            self.ui.endz_entry.show()
-
-            try:
-                self.ui.frzlabel.show()
-                self.ui.feedrate_z_entry.show()
-            except AttributeError:
-                pass
-            self.ui.dwell_cb.show()
-            self.ui.dwelltime_entry.show()
-
-            self.ui.spindle_label.setText('%s:' % _('Spindle speed'))
-
-            try:
-                self.ui.tool_offset_lbl.show()
-                self.ui.offset_entry.show()
-            except AttributeError:
-                pass
-
-    def on_generatecnc_button_click(self, *args):
-        log.debug("Generating CNCJob from Geometry ...")
-        self.app.report_usage("geometry_on_generatecnc_button")
-
-        # this reads the values in the UI form to the self.options dictionary
-        self.read_form()
-
-        self.sel_tools = {}
-
-        try:
-            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"))
-                )
-                return
-        except AttributeError:
-            pass
-
-        # test to see if we have tools available in the tool table
-        if self.ui.geo_tools_table.selectedItems():
-            for x in self.ui.geo_tools_table.selectedItems():
-                # try:
-                #     tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text())
-                # except ValueError:
-                #     # try to convert comma to decimal point. if it's still not working error message and return
-                #     try:
-                #         tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text().replace(',', '.'))
-                #     except ValueError:
-                #         self.app.inform.emit('[ERROR_NOTCL] %s' %
-                #                              _("Wrong value format entered, use a number."))
-                #         return
-                tooluid = int(self.ui.geo_tools_table.item(x.row(), 5).text())
-
-                for tooluid_key, tooluid_value in self.tools.items():
-                    if int(tooluid_key) == tooluid:
-                        self.sel_tools.update({
-                            tooluid: deepcopy(tooluid_value)
-                        })
-            self.mtool_gen_cncjob()
-            self.ui.geo_tools_table.clearSelection()
-
-        elif self.ui.geo_tools_table.rowCount() == 1:
-            tooluid = int(self.ui.geo_tools_table.item(0, 5).text())
-
-            for tooluid_key, tooluid_value in self.tools.items():
-                if int(tooluid_key) == tooluid:
-                    self.sel_tools.update({
-                        tooluid: deepcopy(tooluid_value)
-                    })
-            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 ..."))
-
-    def mtool_gen_cncjob(self, outname=None, tools_dict=None, tools_in_use=None, segx=None, segy=None,
-                         plot=True, use_thread=True):
-        """
-        Creates a multi-tool CNCJob out of this Geometry object.
-        The actual work is done by the target FlatCAMCNCjob object's
-        `generate_from_geometry_2()` method.
-
-        :param tools_dict: a dictionary that holds the whole data needed to create the Gcode
-        (including the solid_geometry)
-
-        :param tools_in_use: the tools that are used, needed by some preprocessors
-        :type list of lists, each list in the list is made out of row elements of tools table from GUI
-
-        :param outname:
-        :param tools_dict:
-        :param tools_in_use:
-        :param segx:            number of segments on the X axis, for auto-levelling
-        :param segy:            number of segments on the Y axis, for auto-levelling
-        :param plot:            if True the generated object will be plotted; if False will not be plotted
-        :param use_thread:      if True use threading
-        :return:                None
-        """
-
-        # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
-        outname = "%s_%s" % (self.options["name"], 'cnc') if outname is None else outname
-
-        tools_dict = self.sel_tools if tools_dict is None else tools_dict
-        tools_in_use = tools_in_use if tools_in_use is not None else self.get_selected_tools_table_items()
-        segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
-        segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
-
-        try:
-            xmin = self.options['xmin']
-            ymin = self.options['ymin']
-            xmax = self.options['xmax']
-            ymax = self.options['ymax']
-        except Exception as e:
-            log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e))
-
-            msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
-            msg += '%s %s' % ('FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() -->', str(e))
-            msg += traceback.format_exc()
-            self.app.inform.emit(msg)
-            return
-
-        # Object initialization function for app.new_object()
-        # RUNNING ON SEPARATE THREAD!
-        def job_init_single_geometry(job_obj, app_obj):
-            log.debug("Creating a CNCJob out of a single-geometry")
-            assert isinstance(job_obj, FlatCAMCNCjob), \
-                "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
-
-            job_obj.options['xmin'] = xmin
-            job_obj.options['ymin'] = ymin
-            job_obj.options['xmax'] = xmax
-            job_obj.options['ymax'] = ymax
-
-            # count the tools
-            tool_cnt = 0
-
-            dia_cnc_dict = {}
-
-            # this turn on the FlatCAMCNCJob plot for multiple tools
-            job_obj.multitool = True
-            job_obj.multigeo = False
-            job_obj.cnc_tools.clear()
-
-            job_obj.options['Tools_in_use'] = tools_in_use
-            job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"])
-            job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"])
-
-            job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"])
-            job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"])
-
-            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'])))
-                dia_cnc_dict.update({
-                    'tooldia': tooldia_val
-                })
-
-                if dia_cnc_dict['offset'] == 'in':
-                    tool_offset = -dia_cnc_dict['tooldia'] / 2
-                elif dia_cnc_dict['offset'].lower() == 'out':
-                    tool_offset = dia_cnc_dict['tooldia'] / 2
-                elif dia_cnc_dict['offset'].lower() == 'custom':
-                    try:
-                        offset_value = float(self.ui.tool_offset_entry.get_value())
-                    except ValueError:
-                        # try to convert comma to decimal point. if it's still not working error message and return
-                        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."))
-                            return
-                    if offset_value:
-                        tool_offset = float(offset_value)
-                    else:
-                        self.app.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.")
-                        )
-                        return
-                else:
-                    tool_offset = 0.0
-
-                dia_cnc_dict.update({
-                    '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']
-                tool_solid_geometry = self.solid_geometry
-
-                job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
-                job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
-
-                # Propagate options
-                job_obj.options["tooldia"] = tooldia_val
-                job_obj.options['type'] = 'Geometry'
-                job_obj.options['tool_dia'] = tooldia_val
-
-                # 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_val, offset=tool_offset, tolerance=tol,
-                    z_cut=z_cut, z_move=z_move,
-                    feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
-                    spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
-                    multidepth=multidepth, depthpercut=depthpercut,
-                    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)
-
-                if res == 'fail':
-                    log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
-                    return 'fail'
-                else:
-                    dia_cnc_dict['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"
-
-                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..."))
-
-                # 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'] = cascaded_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"))
-                except Exception as e:
-                    self.app.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(e)))
-
-                job_obj.cnc_tools.update({
-                    tooluid_key: deepcopy(dia_cnc_dict)
-                })
-                dia_cnc_dict.clear()
-
-        # Object initialization function for app.new_object()
-        # RUNNING ON SEPARATE THREAD!
-        def job_init_multi_geometry(job_obj, app_obj):
-            log.debug("Creating a CNCJob out of a multi-geometry")
-            assert isinstance(job_obj, FlatCAMCNCjob), \
-                "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
-
-            current_uid = int(1)
-
-            job_obj.options['xmin'] = xmin
-            job_obj.options['ymin'] = ymin
-            job_obj.options['xmax'] = xmax
-            job_obj.options['ymax'] = ymax
-
-            # count the tools
-            tool_cnt = 0
-
-            dia_cnc_dict = {}
-
-            # this turn on the FlatCAMCNCJob plot for multiple tools
-            job_obj.multitool = True
-            job_obj.multigeo = True
-            job_obj.cnc_tools.clear()
-
-            job_obj.options['Tools_in_use'] = tools_in_use
-            job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"])
-            job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"])
-
-            job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"])
-            job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"])
-
-            # make sure that trying to make a CNCJob from an empty file is not creating an app crash
-            if not self.solid_geometry:
-                a = 0
-                for tooluid_key in self.tools:
-                    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'))
-                    return 'fail'
-
-            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'])))
-
-                dia_cnc_dict.update({
-                    'tooldia': tooldia_val
-                })
-
-                # find the tool_dia associated with the tooluid_key
-                # search in the self.tools for the sel_tool_dia and when found see what tooluid has
-                # on the found tooluid in self.tools we also have the solid_geometry that interest us
-                # for k, v in self.tools.items():
-                #     if float('%.*f' % (self.decimals, float(v['tooldia']))) == tooldia_val:
-                #         current_uid = int(k)
-                #         break
-
-                if dia_cnc_dict['offset'] == 'in':
-                    tool_offset = -tooldia_val / 2
-                elif dia_cnc_dict['offset'].lower() == 'out':
-                    tool_offset = tooldia_val / 2
-                elif dia_cnc_dict['offset'].lower() == 'custom':
-                    offset_value = float(self.ui.tool_offset_entry.get_value())
-                    if offset_value:
-                        tool_offset = float(offset_value)
-                    else:
-                        self.app.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."))
-                        return
-                else:
-                    tool_offset = 0.0
-
-                dia_cnc_dict.update({
-                    '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']
-                tool_solid_geometry = self.tools[tooluid_key]['solid_geometry']
-
-                job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
-                job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
-
-                # Propagate options
-                job_obj.options["tooldia"] = tooldia_val
-                job_obj.options['type'] = 'Geometry'
-                job_obj.options['tool_dia'] = tooldia_val
-
-                # 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_multitool_geometry(
-                    tool_solid_geometry, 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,
-                    spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
-                    multidepth=multidepth, depthpercut=depthpercut,
-                    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)
-
-                if res == 'fail':
-                    log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
-                    return 'fail'
-                else:
-                    dia_cnc_dict['gcode'] = res
-
-                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..."))
-
-                # 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 = cascaded_union([
-                #     geo['geom'] for geo in dia_cnc_dict['gcode_parsed'] if geo['geom'].is_valid is True
-                # ])
-                try:
-                    dia_cnc_dict['solid_geometry'] = deepcopy(tool_solid_geometry)
-                    self.app.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)))
-
-                # 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.cnc_tools.update({
-                    tooluid_key: deepcopy(dia_cnc_dict)
-                })
-                dia_cnc_dict.clear()
-
-        if use_thread:
-            # To be run in separate thread
-            def job_thread(app_obj):
-                if self.multigeo is False:
-                    with self.app.proc_container.new(_("Generating CNC Code")):
-                        if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
-                            app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
-                else:
-                    with self.app.proc_container.new(_("Generating CNC Code")):
-                        if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
-                            app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
-
-            # Create a promise with the name
-            self.app.collection.promise(outname)
-            # Send to worker
-            self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
-        else:
-            if self.solid_geometry:
-                self.app.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
-            else:
-                self.app.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
-
-    def generatecncjob(
-            self, outname=None,
-            dia=None, offset=None,
-            z_cut=None, z_move=None,
-            feedrate=None, feedrate_z=None, feedrate_rapid=None,
-            spindlespeed=None, dwell=None, dwelltime=None,
-            multidepth=None, depthperpass=None,
-            toolchange=None, toolchangez=None, toolchangexy=None,
-            extracut=None, extracut_length=None, startz=None, endz=None,
-            pp=None,
-            segx=None, segy=None,
-            use_thread=True,
-            plot=True):
-        """
-        Only used for TCL Command.
-        Creates a CNCJob out of this Geometry object. The actual
-        work is done by the target camlib.CNCjob
-        `generate_from_geometry_2()` method.
-
-        :param outname:         Name of the new object
-        :param dia:             Tool diameter
-        :param offset:
-        :param z_cut:           Cut depth (negative value)
-        :param z_move:          Height of the tool when travelling (not cutting)
-        :param feedrate:        Feed rate while cutting on X - Y plane
-        :param feedrate_z:      Feed rate while cutting on Z plane
-        :param feedrate_rapid:  Feed rate while moving with rapids
-        :param spindlespeed:    Spindle speed (RPM)
-        :param dwell:
-        :param dwelltime:
-        :param multidepth:
-        :param depthperpass:
-        :param toolchange:
-        :param toolchangez:
-        :param toolchangexy:
-        :param extracut:
-        :param extracut_length:
-        :param startz:
-        :param endz:
-        :param pp:              Name of the preprocessor
-        :param segx:
-        :param segy:
-        :param use_thread:
-        :param plot:
-        :return: None
-        """
-
-        tooldia = dia if dia else float(self.options["cnctooldia"])
-        outname = outname if outname is not None else self.options["name"]
-
-        z_cut = z_cut if z_cut is not None else float(self.options["cutz"])
-        z_move = z_move if z_move is not None else float(self.options["travelz"])
-
-        feedrate = feedrate if feedrate is not None else float(self.options["feedrate"])
-        feedrate_z = feedrate_z if feedrate_z is not None else float(self.options["feedrate_z"])
-        feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"])
-
-        multidepth = multidepth if multidepth is not None else self.options["multidepth"]
-        depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"])
-
-        segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
-        segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
-
-        extracut = extracut if extracut is not None else float(self.options["extracut"])
-        extracut_length = extracut_length if extracut_length is not None else float(self.options["extracut_length"])
-
-        startz = startz if startz is not None else self.options["startz"]
-        endz = endz if endz is not None else float(self.options["endz"])
-        endxy = self.options["endxy"]
-
-        toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
-        toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
-        toolchange = toolchange if toolchange else self.options["toolchange"]
-
-        offset = offset if offset else 0.0
-
-        # int or None.
-        spindlespeed = spindlespeed if spindlespeed else self.options['spindlespeed']
-        dwell = dwell if dwell else self.options["dwell"]
-        dwelltime = dwelltime if dwelltime else float(self.options["dwelltime"])
-
-        ppname_g = pp if pp else self.options["ppname_g"]
-
-        # Object initialization function for app.new_object()
-        # RUNNING ON SEPARATE THREAD!
-        def job_init(job_obj, app_obj):
-            assert isinstance(job_obj, FlatCAMCNCjob), "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
-
-            # Propagate options
-            job_obj.options["tooldia"] = tooldia
-
-            job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
-            job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
-
-            job_obj.options['type'] = 'Geometry'
-            job_obj.options['tool_dia'] = tooldia
-
-            job_obj.segx = segx
-            job_obj.segy = segy
-
-            job_obj.z_pdepth = float(self.options["z_pdepth"])
-            job_obj.feedrate_probe = float(self.options["feedrate_probe"])
-
-            job_obj.options['xmin'] = self.options['xmin']
-            job_obj.options['ymin'] = self.options['ymin']
-            job_obj.options['xmax'] = self.options['xmax']
-            job_obj.options['ymax'] = self.options['ymax']
-
-            # 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
-            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
-            )
-
-            # 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..."))
-
-        if use_thread:
-            # To be run in separate thread
-            def job_thread(app_obj):
-                with self.app.proc_container.new(_("Generating CNC Code")):
-                    app_obj.new_object("cncjob", outname, job_init, plot=plot)
-                    app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname)
-
-            # Create a promise with the name
-            self.app.collection.promise(outname)
-            # Send to worker
-            self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
-        else:
-            self.app.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 scale(self, xfactor, yfactor=None, point=None):
-        """
-        Scales all geometry by a given factor.
-
-        :param xfactor: Factor by which to scale the object's geometry/
-        :type xfactor: float
-        :param yfactor: Factor by which to scale the object's geometry/
-        :type yfactor: float
-        :return: None
-        :rtype: None
-        """
-        log.debug("FlatCAMObj.FlatCAMGeometry.scale()")
-
-        try:
-            xfactor = float(xfactor)
-        except Exception:
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float."))
-            return
-
-        if yfactor is None:
-            yfactor = xfactor
-        else:
-            try:
-                yfactor = float(yfactor)
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float."))
-                return
-
-        if xfactor == 1 and yfactor == 1:
-            return
-
-        if point is None:
-            px = 0
-            py = 0
-        else:
-            px, py = point
-
-        self.geo_len = 0
-        self.old_disp_number = 0
-        self.el_count = 0
-
-        def scale_recursion(geom):
-            if type(geom) is list:
-                geoms = []
-                for local_geom in geom:
-                    geoms.append(scale_recursion(local_geom))
-                return geoms
-            else:
-                try:
-                    self.el_count += 1
-                    disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
-                    if self.old_disp_number < disp_number <= 100:
-                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                        self.old_disp_number = disp_number
-
-                    return affinity.scale(geom, xfactor, yfactor, origin=(px, py))
-                except AttributeError:
-                    return geom
-
-        if self.multigeo is True:
-            for tool in self.tools:
-                # variables to display the percentage of work done
-                self.geo_len = 0
-                try:
-                    self.geo_len = len(self.tools[tool]['solid_geometry'])
-                except TypeError:
-                    self.geo_len = 1
-                self.old_disp_number = 0
-                self.el_count = 0
-
-                self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry'])
-
-        try:
-            # variables to display the percentage of work done
-            self.geo_len = 0
-            try:
-                self.geo_len = len(self.solid_geometry)
-            except TypeError:
-                self.geo_len = 1
-            self.old_disp_number = 0
-            self.el_count = 0
-
-            self.solid_geometry = scale_recursion(self.solid_geometry)
-        except AttributeError:
-            self.solid_geometry = []
-            return
-
-        self.app.proc_container.new_text = ''
-        self.app.inform.emit('[success] %s' % _("Geometry Scale done."))
-
-    def offset(self, vect):
-        """
-        Offsets all geometry by a given vector/
-
-        :param vect: (x, y) vector by which to offset the object's geometry.
-        :type vect: tuple
-        :return: None
-        :rtype: None
-        """
-        log.debug("FlatCAMObj.FlatCAMGeometry.offset()")
-
-        try:
-            dx, dy = vect
-        except TypeError:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("An (x,y) pair of values are needed. "
-                                   "Probable you entered only one value in the Offset field.")
-                                 )
-            return
-
-        if dx == 0 and dy == 0:
-            return
-
-        self.geo_len = 0
-        self.old_disp_number = 0
-        self.el_count = 0
-
-        def translate_recursion(geom):
-            if type(geom) is list:
-                geoms = []
-                for local_geom in geom:
-                    geoms.append(translate_recursion(local_geom))
-                return geoms
-            else:
-                try:
-                    self.el_count += 1
-                    disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
-                    if self.old_disp_number < disp_number <= 100:
-                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                        self.old_disp_number = disp_number
-
-                    return affinity.translate(geom, xoff=dx, yoff=dy)
-                except AttributeError:
-                    return geom
-
-        if self.multigeo is True:
-            for tool in self.tools:
-                # variables to display the percentage of work done
-                self.geo_len = 0
-                try:
-                    self.geo_len = len(self.tools[tool]['solid_geometry'])
-                except TypeError:
-                    self.geo_len = 1
-                self.old_disp_number = 0
-                self.el_count = 0
-
-                self.tools[tool]['solid_geometry'] = translate_recursion(self.tools[tool]['solid_geometry'])
-
-        # variables to display the percentage of work done
-        self.geo_len = 0
-        try:
-            self.geo_len = len(self.solid_geometry)
-        except TypeError:
-            self.geo_len = 1
-
-        self.old_disp_number = 0
-        self.el_count = 0
-
-        self.solid_geometry = translate_recursion(self.solid_geometry)
-
-        self.app.proc_container.new_text = ''
-        self.app.inform.emit('[success] %s' % _("Geometry Offset done."))
-
-    def convert_units(self, units):
-        log.debug("FlatCAMObj.FlatCAMGeometry.convert_units()")
-
-        self.ui_disconnect()
-
-        factor = Geometry.convert_units(self, units)
-
-        self.options['cutz'] = float(self.options['cutz']) * factor
-        self.options['depthperpass'] = float(self.options['depthperpass']) * factor
-        self.options['travelz'] = float(self.options['travelz']) * factor
-        self.options['feedrate'] = float(self.options['feedrate']) * factor
-        self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor
-        self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
-        self.options['endz'] = float(self.options['endz']) * factor
-        # self.options['cnctooldia'] *= factor
-        # self.options['painttooldia'] *= factor
-        # self.options['paintmargin'] *= factor
-        # self.options['paintoverlap'] *= factor
-
-        self.options["toolchangez"] = float(self.options["toolchangez"]) * factor
-
-        if self.app.defaults["geometry_toolchangexy"] == '':
-            self.options['toolchangexy'] = "0.0, 0.0"
-        else:
-            coords_xy = [float(eval(coord)) for coord in self.app.defaults["geometry_toolchangexy"].split(",")]
-            if len(coords_xy) < 2:
-                self.app.inform.emit('[ERROR] %s' %
-                                     _("The Toolchange X,Y field in Edit -> Preferences "
-                                       "has to be in the format (x, y)\n"
-                                       "but now there is only one value, not two.")
-                                     )
-                return 'fail'
-            coords_xy[0] *= factor
-            coords_xy[1] *= factor
-            self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
-
-        if self.options['startz'] is not None:
-            self.options['startz'] = float(self.options['startz']) * factor
-
-        param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
-                      'endz', 'toolchangez']
-
-        if isinstance(self, FlatCAMGeometry):
-            temp_tools_dict = {}
-            tool_dia_copy = {}
-            data_copy = {}
-            for tooluid_key, tooluid_value in self.tools.items():
-                for dia_key, dia_value in tooluid_value.items():
-                    if dia_key == 'tooldia':
-                        dia_value *= factor
-                        dia_value = float('%.*f' % (self.decimals, dia_value))
-                        tool_dia_copy[dia_key] = dia_value
-                    if dia_key == 'offset':
-                        tool_dia_copy[dia_key] = dia_value
-                    if dia_key == 'offset_value':
-                        dia_value *= factor
-                        tool_dia_copy[dia_key] = dia_value
-
-                        # convert the value in the Custom Tool Offset entry in UI
-                        custom_offset = None
-                        try:
-                            custom_offset = float(self.ui.tool_offset_entry.get_value())
-                        except ValueError:
-                            # try to convert comma to decimal point. if it's still not working error message and return
-                            try:
-                                custom_offset = 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."))
-                                return
-                        except TypeError:
-                            pass
-
-                        if custom_offset:
-                            custom_offset *= factor
-                            self.ui.tool_offset_entry.set_value(custom_offset)
-
-                    if dia_key == 'type':
-                        tool_dia_copy[dia_key] = dia_value
-                    if dia_key == 'tool_type':
-                        tool_dia_copy[dia_key] = dia_value
-                    if dia_key == 'data':
-                        for data_key, data_value in dia_value.items():
-                            # convert the form fields that are convertible
-                            for param in param_list:
-                                if data_key == param and data_value is not None:
-                                    data_copy[data_key] = data_value * factor
-                            # copy the other dict entries that are not convertible
-                            if data_key not in param_list:
-                                data_copy[data_key] = data_value
-                        tool_dia_copy[dia_key] = deepcopy(data_copy)
-                        data_copy.clear()
-
-                temp_tools_dict.update({
-                    tooluid_key: deepcopy(tool_dia_copy)
-                })
-                tool_dia_copy.clear()
-
-            self.tools.clear()
-            self.tools = deepcopy(temp_tools_dict)
-
-        # if there is a value in the new tool field then convert that one too
-        try:
-            self.ui.addtool_entry.returnPressed.disconnect()
-        except TypeError:
-            pass
-        tooldia = self.ui.addtool_entry.get_value()
-        if tooldia:
-            tooldia *= factor
-            tooldia = float('%.*f' % (self.decimals, tooldia))
-
-            self.ui.addtool_entry.set_value(tooldia)
-        self.ui.addtool_entry.returnPressed.connect(self.on_tool_add)
-
-        return factor
-
-    def plot_element(self, element, color=None, visible=None):
-
-        if color is None:
-            color = '#FF0000FF'
-
-        visible = visible if visible else self.options['plot']
-        try:
-            for sub_el in element:
-                self.plot_element(sub_el, color=color)
-
-        except TypeError:  # Element is not iterable...
-            # if self.app.is_legacy is False:
-            self.add_shape(shape=element, color=color, visible=visible, layer=0)
-
-    def plot(self, visible=None, kind=None):
-        """
-        Plot the object.
-
-        :param visible: Controls if the added shape is visible of not
-        :param kind: added so there is no error when a project is loaded and it has both geometry and CNCJob, because
-        CNCJob require the 'kind' parameter. Perhaps the FlatCAMObj.plot() has to be rewrited
-        :return:
-        """
-
-        # Does all the required setup and returns False
-        # if the 'ptint' option is set to False.
-        if not FlatCAMObj.plot(self):
-            return
-
-        try:
-            # plot solid geometries found as members of self.tools attribute dict
-            # for MultiGeo
-            if self.multigeo is True:  # geo multi tool usage
-                for tooluid_key in self.tools:
-                    solid_geometry = self.tools[tooluid_key]['solid_geometry']
-                    self.plot_element(solid_geometry, visible=visible,
-                                      color=self.app.defaults["geometry_plot_line"])
-            else:
-                # plot solid geometry that may be an direct attribute of the geometry object
-                # for SingleGeo
-                if self.solid_geometry:
-                    self.plot_element(self.solid_geometry, visible=visible,
-                                      color=self.app.defaults["geometry_plot_line"])
-
-            # self.plot_element(self.solid_geometry, visible=self.options['plot'])
-
-            self.shapes.redraw()
-
-        except (ObjectDeleted, AttributeError):
-            self.shapes.clear(update=True)
-
-    def on_plot_cb_click(self, *args):
-        if self.muted_ui:
-            return
-        self.read_form_item('plot')
-        self.plot()
-
-        self.ui_disconnect()
-        cb_flag = self.ui.plot_cb.isChecked()
-        for row in range(self.ui.geo_tools_table.rowCount()):
-            table_cb = self.ui.geo_tools_table.cellWidget(row, 6)
-            if cb_flag:
-                table_cb.setChecked(True)
-            else:
-                table_cb.setChecked(False)
-        self.ui_connect()
-
-    def on_plot_cb_click_table(self):
-        # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked)
-        self.ui_disconnect()
-        # cw = self.sender()
-        # cw_index = self.ui.geo_tools_table.indexAt(cw.pos())
-        # cw_row = cw_index.row()
-        check_row = 0
-
-        self.shapes.clear(update=True)
-        for tooluid_key in self.tools:
-            solid_geometry = self.tools[tooluid_key]['solid_geometry']
-
-            # find the geo_tool_table row associated with the tooluid_key
-            for row in range(self.ui.geo_tools_table.rowCount()):
-                tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text())
-                if tooluid_item == int(tooluid_key):
-                    check_row = row
-                    break
-            if self.ui.geo_tools_table.cellWidget(check_row, 6).isChecked():
-                self.plot_element(element=solid_geometry, visible=True)
-        self.shapes.redraw()
-
-        # make sure that the general plot is disabled if one of the row plot's are disabled and
-        # if all the row plot's are enabled also enable the general plot checkbox
-        cb_cnt = 0
-        total_row = self.ui.geo_tools_table.rowCount()
-        for row in range(total_row):
-            if self.ui.geo_tools_table.cellWidget(row, 6).isChecked():
-                cb_cnt += 1
-            else:
-                cb_cnt -= 1
-        if cb_cnt < total_row:
-            self.ui.plot_cb.setChecked(False)
-        else:
-            self.ui.plot_cb.setChecked(True)
-        self.ui_connect()
-
-    def merge(self, geo_list, geo_final, multigeo=None):
-        """
-        Merges the geometry of objects in grb_list into
-        the geometry of geo_final.
-
-        :param geo_list: List of FlatCAMGerber Objects to join.
-        :param geo_final: Destination FlatCAMGerber object.
-        :param multigeo: if the merged geometry objects are of type MultiGeo
-        :return: None
-        """
-
-        if geo_final.solid_geometry is None:
-            geo_final.solid_geometry = []
-
-        try:
-            __ = iter(geo_final.solid_geometry)
-        except TypeError:
-            geo_final.solid_geometry = [geo_final.solid_geometry]
-
-        new_solid_geometry = []
-        new_options = {}
-        new_tools = {}
-
-        for geo_obj in geo_list:
-            for option in geo_obj.options:
-                if option != 'name':
-                    try:
-                        new_options[option] = deepcopy(geo_obj.options[option])
-                    except Exception as e:
-                        log.warning("Failed to copy option %s. Error: %s" % (str(option), str(e)))
-
-            # Expand lists
-            if type(geo_obj) is list:
-                FlatCAMGeometry.merge(self, geo_list=geo_obj, geo_final=geo_final)
-            # If not list, just append
-            else:
-                if multigeo is None or multigeo is False:
-                    geo_final.multigeo = False
-                else:
-                    geo_final.multigeo = True
-
-                try:
-                    new_solid_geometry += deepcopy(geo_obj.solid_geometry)
-                except Exception as e:
-                    log.debug("FlatCAMGeometry.merge() --> %s" % str(e))
-
-                # find the tool_uid maximum value in the geo_final
-                try:
-                    max_uid = max([int(i) for i in new_tools.keys()])
-                except ValueError:
-                    max_uid = 0
-
-                # add and merge tools. If what we try to merge as Geometry is Excellon's and/or Gerber's then don't try
-                # to merge the obj.tools as it is likely there is none to merge.
-                if not isinstance(geo_obj, FlatCAMGerber) and not isinstance(geo_obj, FlatCAMExcellon):
-                    for tool_uid in geo_obj.tools:
-                        max_uid += 1
-                        new_tools[max_uid] = deepcopy(geo_obj.tools[tool_uid])
-
-        geo_final.options.update(new_options)
-        geo_final.solid_geometry = new_solid_geometry
-        geo_final.tools = new_tools
-
-    @staticmethod
-    def get_pts(o):
-        """
-        Returns a list of all points in the object, where
-        the object can be a MultiPolygon, Polygon, Not a polygon, or a list
-        of such. Search is done recursively.
-
-        :param: geometric object
-        :return: List of points
-        :rtype: list
-        """
-        pts = []
-
-        # Iterable: descend into each item.
-        try:
-            for subo in o:
-                pts += FlatCAMGeometry.get_pts(subo)
-
-        # Non-iterable
-        except TypeError:
-            if o is not None:
-                if type(o) == MultiPolygon:
-                    for poly in o:
-                        pts += FlatCAMGeometry.get_pts(poly)
-                # ## Descend into .exerior and .interiors
-                elif type(o) == Polygon:
-                    pts += FlatCAMGeometry.get_pts(o.exterior)
-                    for i in o.interiors:
-                        pts += FlatCAMGeometry.get_pts(i)
-                elif type(o) == MultiLineString:
-                    for line in o:
-                        pts += FlatCAMGeometry.get_pts(line)
-                # ## Has .coords: list them.
-                else:
-                    pts += list(o.coords)
-            else:
-                return
-        return pts
-
-
-class FlatCAMCNCjob(FlatCAMObj, CNCjob):
-    """
-    Represents G-Code.
-    """
-    optionChanged = QtCore.pyqtSignal(str)
-    ui_type = CNCObjectUI
-
-    def __init__(self, name, units="in", kind="generic", z_move=0.1,
-                 feedrate=3.0, feedrate_rapid=3.0, z_cut=-0.002, tooldia=0.0,
-                 spindlespeed=None):
-
-        FlatCAMApp.App.log.debug("Creating CNCJob object...")
-
-        self.decimals = self.app.decimals
-
-        CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
-                        feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia,
-                        spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"]))
-
-        FlatCAMObj.__init__(self, name)
-
-        self.kind = "cncjob"
-
-        self.options.update({
-            "plot": True,
-            "tooldia": 0.03937,  # 0.4mm in inches
-            "append": "",
-            "prepend": "",
-            "dwell": False,
-            "dwelltime": 1,
-            "type": 'Geometry',
-            "toolchange_macro": '',
-            "toolchange_macro_enable": False
-        })
-
-        '''
-            This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the 
-            diameter of the tools and the value is another dict that will hold the data under the following form:
-               {tooldia:   {
-                           'tooluid': 1,
-                           'offset': 'Path',
-                           'type_item': 'Rough',
-                           'tool_type': 'C1',
-                           'data': {} # a dict to hold the parameters
-                           'gcode': "" # a string with the actual GCODE
-                           'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry 
-                           (cut or move)
-                           'solid_geometry': []
-                           },
-                           ...
-               }
-            It is populated in the FlatCAMGeometry.mtool_gen_cncjob()
-            BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ...
-        '''
-        self.cnc_tools = {}
-
-        '''
-           This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the 
-           diameter of the tools and the value is another dict that will hold the data under the following form:
-              {tooldia:   {
-                          'tool': int,
-                          'nr_drills': int,
-                          'nr_slots': int,
-                          'offset': float,
-                          'data': {} # a dict to hold the parameters
-                          'gcode': "" # a string with the actual GCODE
-                          'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry (cut or move)
-                          'solid_geometry': []
-                          },
-                          ...
-              }
-           It is populated in the FlatCAMExcellon.on_create_cncjob_click() but actually 
-           it's done in camlib.CNCJob.generate_from_excellon_by_tool()
-           BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ...
-       '''
-        self.exc_cnc_tools = {}
-
-        # flag to store if the CNCJob is part of a special group of CNCJob objects that can't be processed by the
-        # default engine of FlatCAM. They generated by some of tools and are special cases of CNCJob objects.
-        self.special_group = None
-
-        # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool
-        # (like the one in the TCL Command), False
-        self.multitool = False
-
-        # determine if the GCode was generated out of a Excellon object or a Geometry object
-        self.origin_kind = None
-
-        # used for parsing the GCode lines to adjust the GCode when the GCode is offseted or scaled
-        gcodex_re_string = r'(?=.*(X[-\+]?\d*\.\d*))'
-        self.g_x_re = re.compile(gcodex_re_string)
-        gcodey_re_string = r'(?=.*(Y[-\+]?\d*\.\d*))'
-        self.g_y_re = re.compile(gcodey_re_string)
-        gcodez_re_string = r'(?=.*(Z[-\+]?\d*\.\d*))'
-        self.g_z_re = re.compile(gcodez_re_string)
-
-        gcodef_re_string = r'(?=.*(F[-\+]?\d*\.\d*))'
-        self.g_f_re = re.compile(gcodef_re_string)
-        gcodet_re_string = r'(?=.*(\=\s*[-\+]?\d*\.\d*))'
-        self.g_t_re = re.compile(gcodet_re_string)
-
-        gcodenr_re_string = r'([+-]?\d*\.\d+)'
-        self.g_nr_re = re.compile(gcodenr_re_string)
-
-        # Attributes to be included in serialization
-        # Always append to it because it carries contents
-        # from predecessors.
-        self.ser_attrs += ['options', 'kind', 'origin_kind', 'cnc_tools', 'exc_cnc_tools', 'multitool']
-
-        if self.app.is_legacy is False:
-            self.text_col = self.app.plotcanvas.new_text_collection()
-            self.text_col.enabled = True
-            self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
-
-        self.gcode_editor_tab = None
-
-        self.units_found = self.app.defaults['units']
-
-    def build_ui(self):
-        self.ui_disconnect()
-
-        FlatCAMObj.build_ui(self)
-        self.units = self.app.defaults['units'].upper()
-
-        # if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it
-        self.ui.cnc_tools_table.hide()
-        if self.cnc_tools:
-            self.ui.cnc_tools_table.show()
-            self.build_cnc_tools_table()
-
-        self.ui.exc_cnc_tools_table.hide()
-        if self.exc_cnc_tools:
-            self.ui.exc_cnc_tools_table.show()
-            self.build_excellon_cnc_tools()
-        #
-        self.ui_connect()
-
-    def build_cnc_tools_table(self):
-        tool_idx = 0
-
-        n = len(self.cnc_tools)
-        self.ui.cnc_tools_table.setRowCount(n)
-
-        for dia_key, dia_value in self.cnc_tools.items():
-
-            tool_idx += 1
-            row_no = tool_idx - 1
-
-            t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
-            # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-            self.ui.cnc_tools_table.setItem(row_no, 0, t_id)  # Tool name/id
-
-            # Make sure that the tool diameter when in MM is with no more than 2 decimals.
-            # There are no tool bits in MM with more than 2 decimals diameter.
-            # For INCH the decimals should be no more than 4. There are no tools under 10mils.
-
-            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['tooldia'])))
-
-            offset_txt = list(str(dia_value['offset']))
-            offset_txt[0] = offset_txt[0].upper()
-            offset_item = QtWidgets.QTableWidgetItem(''.join(offset_txt))
-            type_item = QtWidgets.QTableWidgetItem(str(dia_value['type']))
-            tool_type_item = QtWidgets.QTableWidgetItem(str(dia_value['tool_type']))
-
-            t_id.setFlags(QtCore.Qt.ItemIsEnabled)
-            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            offset_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            type_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            tool_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-            # hack so the checkbox stay centered in the table cell
-            # used this:
-            # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row
-            # plot_item = QtWidgets.QWidget()
-            # checkbox = FCCheckBox()
-            # checkbox.setCheckState(QtCore.Qt.Checked)
-            # qhboxlayout = QtWidgets.QHBoxLayout(plot_item)
-            # qhboxlayout.addWidget(checkbox)
-            # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
-            # qhboxlayout.setContentsMargins(0, 0, 0, 0)
-            plot_item = FCCheckBox()
-            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
-            tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_key))
-            if self.ui.plot_cb.isChecked():
-                plot_item.setChecked(True)
-
-            self.ui.cnc_tools_table.setItem(row_no, 1, dia_item)  # Diameter
-            self.ui.cnc_tools_table.setItem(row_no, 2, offset_item)  # Offset
-            self.ui.cnc_tools_table.setItem(row_no, 3, type_item)  # Toolpath Type
-            self.ui.cnc_tools_table.setItem(row_no, 4, tool_type_item)  # Tool Type
-
-            # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
-            self.ui.cnc_tools_table.setItem(row_no, 5, tool_uid_item)  # Tool unique ID)
-            self.ui.cnc_tools_table.setCellWidget(row_no, 6, plot_item)
-
-        # make the diameter column editable
-        # for row in range(tool_idx):
-        #     self.ui.cnc_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable |
-        #                                                   QtCore.Qt.ItemIsEnabled)
-
-        for row in range(tool_idx):
-            self.ui.cnc_tools_table.item(row, 0).setFlags(
-                self.ui.cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable)
-
-        self.ui.cnc_tools_table.resizeColumnsToContents()
-        self.ui.cnc_tools_table.resizeRowsToContents()
-
-        vertical_header = self.ui.cnc_tools_table.verticalHeader()
-        # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
-        vertical_header.hide()
-        self.ui.cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        horizontal_header = self.ui.cnc_tools_table.horizontalHeader()
-        horizontal_header.setMinimumSectionSize(10)
-        horizontal_header.setDefaultSectionSize(70)
-        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
-        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
-        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
-        horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(4, 40)
-        horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(4, 17)
-        # horizontal_header.setStretchLastSection(True)
-        self.ui.cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        self.ui.cnc_tools_table.setColumnWidth(0, 20)
-        self.ui.cnc_tools_table.setColumnWidth(4, 40)
-        self.ui.cnc_tools_table.setColumnWidth(6, 17)
-
-        # self.ui.geo_tools_table.setSortingEnabled(True)
-
-        self.ui.cnc_tools_table.setMinimumHeight(self.ui.cnc_tools_table.getHeight())
-        self.ui.cnc_tools_table.setMaximumHeight(self.ui.cnc_tools_table.getHeight())
-
-    def build_excellon_cnc_tools(self):
-        tool_idx = 0
-
-        n = len(self.exc_cnc_tools)
-        self.ui.exc_cnc_tools_table.setRowCount(n)
-
-        for tooldia_key, dia_value in self.exc_cnc_tools.items():
-
-            tool_idx += 1
-            row_no = tool_idx - 1
-
-            t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
-            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_z']) + self.z_cut))
-
-            t_id.setFlags(QtCore.Qt.ItemIsEnabled)
-            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            nr_drills_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            nr_slots_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            cutz_item.setFlags(QtCore.Qt.ItemIsEnabled)
-
-            # hack so the checkbox stay centered in the table cell
-            # used this:
-            # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row
-            # plot_item = QtWidgets.QWidget()
-            # checkbox = FCCheckBox()
-            # checkbox.setCheckState(QtCore.Qt.Checked)
-            # qhboxlayout = QtWidgets.QHBoxLayout(plot_item)
-            # qhboxlayout.addWidget(checkbox)
-            # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
-            # qhboxlayout.setContentsMargins(0, 0, 0, 0)
-
-            plot_item = FCCheckBox()
-            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
-            tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_value['tool']))
-            if self.ui.plot_cb.isChecked():
-                plot_item.setChecked(True)
-
-            # TODO until the feature of individual plot for an Excellon tool is implemented
-            plot_item.setDisabled(True)
-
-            self.ui.exc_cnc_tools_table.setItem(row_no, 0, t_id)  # Tool name/id
-            self.ui.exc_cnc_tools_table.setItem(row_no, 1, dia_item)  # Diameter
-            self.ui.exc_cnc_tools_table.setItem(row_no, 2, nr_drills_item)  # Nr of drills
-            self.ui.exc_cnc_tools_table.setItem(row_no, 3, nr_slots_item)  # Nr of slots
-
-            # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
-            self.ui.exc_cnc_tools_table.setItem(row_no, 4, tool_uid_item)  # Tool unique ID)
-            self.ui.exc_cnc_tools_table.setItem(row_no, 5, cutz_item)
-            self.ui.exc_cnc_tools_table.setCellWidget(row_no, 6, plot_item)
-
-        for row in range(tool_idx):
-            self.ui.exc_cnc_tools_table.item(row, 0).setFlags(
-                self.ui.exc_cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable)
-
-        self.ui.exc_cnc_tools_table.resizeColumnsToContents()
-        self.ui.exc_cnc_tools_table.resizeRowsToContents()
-
-        vertical_header = self.ui.exc_cnc_tools_table.verticalHeader()
-        vertical_header.hide()
-        self.ui.exc_cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        horizontal_header = self.ui.exc_cnc_tools_table.horizontalHeader()
-        horizontal_header.setMinimumSectionSize(10)
-        horizontal_header.setDefaultSectionSize(70)
-        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
-        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
-        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
-        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
-        horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.ResizeToContents)
-
-        horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
-
-        # horizontal_header.setStretchLastSection(True)
-        self.ui.exc_cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-
-        self.ui.exc_cnc_tools_table.setColumnWidth(0, 20)
-        self.ui.exc_cnc_tools_table.setColumnWidth(6, 17)
-
-        self.ui.exc_cnc_tools_table.setMinimumHeight(self.ui.exc_cnc_tools_table.getHeight())
-        self.ui.exc_cnc_tools_table.setMaximumHeight(self.ui.exc_cnc_tools_table.getHeight())
-
-    def set_ui(self, ui):
-        FlatCAMObj.set_ui(self, ui)
-
-        FlatCAMApp.App.log.debug("FlatCAMCNCJob.set_ui()")
-
-        assert isinstance(self.ui, CNCObjectUI), \
-            "Expected a CNCObjectUI, got %s" % type(self.ui)
-
-        self.units = self.app.defaults['units'].upper()
-        self.units_found = self.app.defaults['units']
-
-        # this signal has to be connected to it's slot before the defaults are populated
-        # the decision done in the slot has to override the default value set bellow
-        self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked)
-
-        self.form_fields.update({
-            "plot": self.ui.plot_cb,
-            "tooldia": self.ui.tooldia_entry,
-            "append": self.ui.append_text,
-            "prepend": self.ui.prepend_text,
-            "toolchange_macro": self.ui.toolchange_text,
-            "toolchange_macro_enable": self.ui.toolchange_cb
-        })
-
-        # Fill form fields only on object create
-        self.to_form()
-
-        # this means that the object that created this CNCJob was an Excellon or Geometry
-        try:
-            if self.travel_distance:
-                self.ui.t_distance_label.show()
-                self.ui.t_distance_entry.setVisible(True)
-                self.ui.t_distance_entry.setDisabled(True)
-                self.ui.t_distance_entry.set_value('%.*f' % (self.decimals, float(self.travel_distance)))
-                self.ui.units_label.setText(str(self.units).lower())
-                self.ui.units_label.setDisabled(True)
-
-                self.ui.t_time_label.show()
-                self.ui.t_time_entry.setVisible(True)
-                self.ui.t_time_entry.setDisabled(True)
-                # if time is more than 1 then we have minutes, else we have seconds
-                if self.routing_time > 1:
-                    self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(self.routing_time))))
-                    self.ui.units_time_label.setText('min')
-                else:
-                    time_r = self.routing_time * 60
-                    self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(time_r))))
-                    self.ui.units_time_label.setText('sec')
-                self.ui.units_time_label.setDisabled(True)
-        except AttributeError:
-            pass
-
-        if self.multitool is False:
-            self.ui.tooldia_entry.show()
-            self.ui.updateplot_button.show()
-        else:
-            self.ui.tooldia_entry.hide()
-            self.ui.updateplot_button.hide()
-
-        # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob
-        self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"])
-
-        try:
-            self.ui.annotation_cb.stateChanged.disconnect(self.on_annotation_change)
-        except (TypeError, AttributeError):
-            pass
-        self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change)
-
-        # set if to display text annotations
-        self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
-
-        # 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.cnc_frame.hide()
-        else:
-            self.ui.level.setText(_(
-                '<span style="color:red;"><b>Advanced</b></span>'
-            ))
-            self.ui.cnc_frame.show()
-
-        self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
-        self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
-        self.ui.modify_gcode_button.clicked.connect(self.on_edit_code_click)
-
-        self.ui.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters)
-
-        self.ui.cncplot_method_combo.activated_custom.connect(self.on_plot_kind_change)
-
-    def on_cnc_custom_parameters(self, signal_text):
-        if signal_text == 'Parameters':
-            return
-        else:
-            self.ui.toolchange_text.insertPlainText('%%%s%%' % signal_text)
-
-    def ui_connect(self):
-        for row in range(self.ui.cnc_tools_table.rowCount()):
-            self.ui.cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table)
-        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
-
-    def ui_disconnect(self):
-        for row in range(self.ui.cnc_tools_table.rowCount()):
-            self.ui.cnc_tools_table.cellWidget(row, 6).clicked.disconnect(self.on_plot_cb_click_table)
-        try:
-            self.ui.plot_cb.stateChanged.disconnect(self.on_plot_cb_click)
-        except (TypeError, AttributeError):
-            pass
-
-    def on_updateplot_button_click(self, *args):
-        """
-        Callback for the "Updata Plot" button. Reads the form for updates
-        and plots the object.
-        """
-        self.read_form()
-        self.on_plot_kind_change()
-
-    def on_plot_kind_change(self):
-        kind = self.ui.cncplot_method_combo.get_value()
-
-        def worker_task():
-            with self.app.proc_container.new(_("Plotting...")):
-                self.plot(kind=kind)
-
-        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
-
-    def on_exportgcode_button_click(self, *args):
-        """
-        Handler activated by a button clicked when exporting GCode.
-
-        :param args:
-        :return:
-        """
-        self.app.report_usage("cncjob_on_exportgcode_button")
-
-        self.read_form()
-        name = self.app.collection.get_active().options['name']
-        save_gcode = False
-
-        if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name:
-            _filter_ = "RML1 Files .rol (*.rol);;All Files (*.*)"
-        elif 'hpgl' in self.pp_geometry_name:
-            _filter_ = "HPGL Files .plt (*.plt);;All Files (*.*)"
-        else:
-            save_gcode = True
-            _filter_ = self.app.defaults['cncjob_save_filters']
-
-        try:
-            dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
-            filename, _f = FCFileSaveDialog.get_saved_filename(
-                caption=_("Export Machine Code ..."),
-                directory=dir_file_to_save,
-                filter=_filter_
-            )
-        except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_)
-
-        filename = str(filename)
-
-        if filename == '':
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Machine Code cancelled ..."))
-            return
-        else:
-            if save_gcode is True:
-                used_extension = filename.rpartition('.')[2]
-                self.update_filters(last_ext=used_extension, filter_string='cncjob_save_filters')
-
-        new_name = os.path.split(str(filename))[1].rpartition('.')[0]
-        self.ui.name_entry.set_value(new_name)
-        self.on_name_activate(silent=True)
-
-        preamble = str(self.ui.prepend_text.get_value())
-        postamble = str(self.ui.append_text.get_value())
-
-        gc = self.export_gcode(filename, preamble=preamble, postamble=postamble)
-        if gc == 'fail':
-            return
-
-        if self.app.defaults["global_open_style"] is False:
-            self.app.file_opened.emit("gcode", filename)
-        self.app.file_saved.emit("gcode", filename)
-        self.app.inform.emit('[success] %s: %s' % (_("Machine Code file saved to"), filename))
-
-    def on_edit_code_click(self, *args):
-        """
-        Handler activated by a button clicked when editing GCode.
-
-        :param args:
-        :return:
-        """
-
-        self.app.proc_container.view.set_busy(_("Loading..."))
-
-        preamble = str(self.ui.prepend_text.get_value())
-        postamble = str(self.ui.append_text.get_value())
-
-        gco = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
-        if gco == 'fail':
-            return
-        else:
-            self.app.gcode_edited = gco
-
-        self.gcode_editor_tab = TextEditor(app=self.app, plain_text=True)
-
-        # add the tab if it was closed
-        self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Code Editor"))
-        self.gcode_editor_tab.setObjectName('code_editor_tab')
-
-        # delete the absolute and relative position and messages in the infobar
-        self.app.ui.position_label.setText("")
-        self.app.ui.rel_position_label.setText("")
-
-        # first clear previous text in text editor (if any)
-        self.gcode_editor_tab.code_editor.clear()
-        self.gcode_editor_tab.code_editor.setReadOnly(False)
-
-        self.gcode_editor_tab.code_editor.completer_enable = False
-        self.gcode_editor_tab.buttonRun.hide()
-
-        # Switch plot_area to CNCJob tab
-        self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab)
-
-        self.gcode_editor_tab.t_frame.hide()
-        # then append the text from GCode to the text editor
-        try:
-            self.gcode_editor_tab.code_editor.setPlainText(self.app.gcode_edited.getvalue())
-            # for line in self.app.gcode_edited:
-            #     QtWidgets.QApplication.processEvents()
-            #
-            #     proc_line = str(line).strip('\n')
-            #     self.gcode_editor_tab.code_editor.append(proc_line)
-        except Exception as e:
-            log.debug('FlatCAMCNNJob.on_edit_code_click() -->%s' % str(e))
-            self.app.inform.emit('[ERROR] %s %s' % ('FlatCAMCNNJob.on_edit_code_click() -->', str(e)))
-            return
-
-        self.gcode_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start)
-
-        self.gcode_editor_tab.handleTextChanged()
-        self.gcode_editor_tab.t_frame.show()
-        self.app.proc_container.view.set_idle()
-
-        self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor'))
-
-    def gcode_header(self, comment_start_symbol=None, comment_stop_symbol=None):
-        """
-        Will create a header to be added to all GCode files generated by FlatCAM
-
-        :param comment_start_symbol:    A symbol to be used as the first symbol in a comment
-        :param comment_stop_symbol:     A symbol to be used as the last symbol in a comment
-        :return:                        A string with a GCode header
-        """
-
-        log.debug("FlatCAMCNCJob.gcode_header()")
-        time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
-        marlin = False
-        hpgl = False
-        probe_pp = False
-
-        start_comment = comment_start_symbol if comment_start_symbol is not None else '('
-        stop_comment = comment_stop_symbol if comment_stop_symbol is not None else ')'
-
-        try:
-            for key in self.cnc_tools:
-                ppg = self.cnc_tools[key]['data']['ppname_g']
-                if 'marlin' in ppg.lower() or 'repetier' in ppg.lower():
-                    marlin = True
-                    break
-                if ppg == 'hpgl':
-                    hpgl = True
-                    break
-                if "toolchange_probe" in ppg.lower():
-                    probe_pp = True
-                    break
-        except KeyError:
-            # log.debug("FlatCAMCNCJob.gcode_header() error: --> %s" % str(e))
-            pass
-
-        try:
-            if 'marlin' in self.options['ppname_e'].lower() or 'repetier' in self.options['ppname_e'].lower():
-                marlin = True
-        except KeyError:
-            # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
-            pass
-
-        try:
-            if "toolchange_probe" in self.options['ppname_e'].lower():
-                probe_pp = True
-        except KeyError:
-            # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
-            pass
-
-        if marlin is True:
-            gcode = ';Marlin(Repetier) G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date:    %s\n' % \
-                    (str(self.app.version), str(self.app.version_date)) + '\n'
-
-            gcode += ';Name: ' + str(self.options['name']) + '\n'
-            gcode += ';Type: ' + "G-code from " + str(self.options['type']) + '\n'
-
-            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
-
-            gcode += ';Units: ' + self.units.upper() + '\n' + "\n"
-            gcode += ';Created on ' + time_str + '\n' + '\n'
-        elif hpgl is True:
-            gcode = 'CO "HPGL CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date:    %s' % \
-                    (str(self.app.version), str(self.app.version_date)) + '";\n'
-
-            gcode += 'CO "Name: ' + str(self.options['name']) + '";\n'
-            gcode += 'CO "Type: ' + "HPGL code from " + str(self.options['type']) + '";\n'
-
-            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
-
-            gcode += 'CO "Units: ' + self.units.upper() + '";\n'
-            gcode += 'CO "Created on ' + time_str + '";\n'
-        elif probe_pp is True:
-            gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
-                    (str(self.app.version), str(self.app.version_date)) + '\n'
-
-            gcode += '(This GCode tool change is done by using a Probe.)\n' \
-                     '(Make sure that before you start the job you first do a rough zero for Z axis.)\n' \
-                     '(This means that you need to zero the CNC axis and then jog to the toolchange X, Y location,)\n' \
-                     '(mount the probe and adjust the Z so more or less the probe tip touch the plate. ' \
-                     'Then zero the Z axis.)\n' + '\n'
-
-            gcode += '(Name: ' + str(self.options['name']) + ')\n'
-            gcode += '(Type: ' + "G-code from " + str(self.options['type']) + ')\n'
-
-            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
-
-            gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
-            gcode += '(Created on ' + time_str + ')\n' + '\n'
-        else:
-            gcode = '%sG-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s%s\n' % \
-                    (start_comment, str(self.app.version), str(self.app.version_date), stop_comment) + '\n'
-
-            gcode += '%sName: ' % start_comment + str(self.options['name']) + '%s\n' % stop_comment
-            gcode += '%sType: ' % start_comment + "G-code from " + str(self.options['type']) + '%s\n' % stop_comment
-
-            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
-
-            gcode += '%sUnits: ' % start_comment + self.units.upper() + '%s\n' % stop_comment + "\n"
-            gcode += '%sCreated on ' % start_comment + time_str + '%s\n' % stop_comment + '\n'
-
-        return gcode
-
-    def gcode_footer(self, end_command=None):
-        """
-        Will add the M02 to the end of GCode, if requested.
-
-        :param end_command: 'M02' or 'M30' - String
-        :return:
-        """
-        if end_command:
-            return end_command
-        else:
-            return 'M02'
-
-    def export_gcode(self, filename=None, preamble='', postamble='', to_file=False):
-        """
-        This will save the GCode from the Gcode object to a file on the OS filesystem
-
-        :param filename:    filename for the GCode file
-        :param preamble:    a custom Gcode block to be added at the beginning of the Gcode file
-        :param postamble:   a custom Gcode block to be added at the end of the Gcode file
-        :param to_file:     if False then no actual file is saved but the app will know that a file was created
-        :return:            None
-        """
-        # gcode = ''
-        # roland = False
-        # hpgl = False
-        # isel_icp = False
-
-        include_header = True
-
-        try:
-            if self.special_group:
-                self.app.inform.emit('[WARNING_NOTCL] %s %s %s.' %
-                                     (_("This CNCJob object can't be processed because it is a"),
-                                      str(self.special_group),
-                                      _("CNCJob object")))
-                return 'fail'
-        except AttributeError:
-            pass
-
-        # if this dict is not empty then the object is a Geometry object
-        if self.cnc_tools:
-            first_key = next(iter(self.cnc_tools))
-            include_header = self.app.preprocessors[self.cnc_tools[first_key]['data']['ppname_g']].include_header
-
-        # if this dict is not empty then the object is an Excellon object
-        if self.exc_cnc_tools:
-            first_key = next(iter(self.exc_cnc_tools))
-            include_header = self.app.preprocessors[self.exc_cnc_tools[first_key]['data']['ppname_e']].include_header
-
-        # # detect if using Roland preprocessor
-        # try:
-        #     for key in self.cnc_tools:
-        #         if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20':
-        #             roland = True
-        #             break
-        # except Exception:
-        #     try:
-        #         for key in self.cnc_tools:
-        #             if self.cnc_tools[key]['data']['ppname_e'] == 'Roland_MDX_20':
-        #                 roland = True
-        #                 break
-        #     except Exception:
-        #         pass
-        #
-        # # detect if using HPGL preprocessor
-        # try:
-        #     for key in self.cnc_tools:
-        #         if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl':
-        #             hpgl = True
-        #             break
-        # except Exception:
-        #     try:
-        #         for key in self.cnc_tools:
-        #             if self.cnc_tools[key]['data']['ppname_e'] == 'hpgl':
-        #                 hpgl = True
-        #                 break
-        #     except Exception:
-        #         pass
-        #
-        # # detect if using ISEL_ICP_CNC preprocessor
-        # try:
-        #     for key in self.cnc_tools:
-        #         if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_g'].upper():
-        #             isel_icp = True
-        #             break
-        # except Exception:
-        #     try:
-        #         for key in self.cnc_tools:
-        #             if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_e'].upper():
-        #                 isel_icp = True
-        #                 break
-        #     except Exception:
-        #         pass
-
-        # do not add gcode_header when using the Roland preprocessor, add it for every other preprocessor
-        # if roland is False and hpgl is False and isel_icp is False:
-        #     gcode = self.gcode_header()
-
-        # do not add gcode_header when using the Roland, HPGL or ISEP_ICP_CNC preprocessor (or any other preprocessor
-        # that has the include_header attribute set as False, add it for every other preprocessor
-        # if include_header:
-        #     gcode = self.gcode_header()
-        # else:
-        #     gcode = ''
-
-        # # 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:
-        #         for key, value in self.cnc_tools[tooluid_key].items():
-        #             if key == 'gcode':
-        #                 gcode += value
-        #                 break
-        # else:
-        #     gcode += self.gcode
-
-        # if roland is True:
-        #     g = preamble + gcode + postamble
-        # elif hpgl is True:
-        #     g = self.gcode_header() + preamble + gcode + postamble
-        # else:
-        #     # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
-        #     g_idx = gcode.rfind('G20')
-        #
-        #     # if it did not find 'G20' then search for 'G21'
-        #     if g_idx == -1:
-        #         g_idx = gcode.rfind('G21')
-        #
-        #     # if it did not find 'G20' and it did not find 'G21' then there is an error and return
-        #     # but only when the preprocessor is not ISEL_ICP who is allowed not to have the G20/G21 command
-        #     if g_idx == -1 and isel_icp is False:
-        #         self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
-        #         return
-        #
-        #     footer = self.app.defaults['cncjob_footer']
-        #     end_gcode = self.gcode_footer() if footer is True else ''
-        #     g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + end_gcode
-
-        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:
-                    for key, value in self.cnc_tools[tooluid_key].items():
-                        if key == 'gcode':
-                            gcode += value
-                            break
-            else:
-                gcode += self.gcode
-
-            g = g + gcode + 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
-            # g_idx = gcode.rfind('G20')
-            #
-            # # if it did not find 'G20' then search for 'G21'
-            # if g_idx == -1:
-            #     g_idx = gcode.rfind('G21')
-            #
-            # # if it did not find 'G20' and it did not find 'G21' then there is an error and return
-            # if g_idx == -1:
-            #     self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
-            #     return
-
-            # 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:
-                    for key, value in self.cnc_tools[tooluid_key].items():
-                        if key == 'gcode':
-                            gcode += value
-                            break
-            else:
-                gcode += self.gcode
-
-            end_gcode = self.gcode_footer() if self.app.defaults['cncjob_footer'] is True else ''
-
-            # detect if using a HPGL preprocessor
-            hpgl = False
-            if self.cnc_tools:
-                for key in self.cnc_tools:
-                    if 'ppname_g' in self.cnc_tools[key]['data']:
-                        if 'hpgl' in self.cnc_tools[key]['data']['ppname_g']:
-                            hpgl = True
-                            break
-            elif self.exc_cnc_tools:
-                for key in self.cnc_tools:
-                    if 'ppname_e' in self.cnc_tools[key]['data']:
-                        if 'hpgl' in self.cnc_tools[key]['data']['ppname_e']:
-                            hpgl = True
-                            break
-
-            if hpgl:
-                processed_gcode = ''
-                pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$")
-                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
-                    else:
-                        processed_gcode += gline + '\n'
-
-                gcode = processed_gcode
-                g = self.gcode_header() + '\n' + preamble + '\n' + gcode + postamble + end_gcode
-            else:
-                try:
-                    g_idx = gcode.index('G94')
-                    g = self.gcode_header() + gcode[:g_idx + 3] + '\n\n' + preamble + '\n' + \
-                        gcode[(g_idx + 3):] + postamble + end_gcode
-                except ValueError:
-                    self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                         _("G-code does not have a G94 code and we will not include the code in the "
-                                           "'Prepend to GCode' text box"))
-                    g = self.gcode_header() + '\n' + gcode + postamble + 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:
-            # match = self.re_toolchange.search(g)
-            if 'M6' in g:
-                m6_code = self.parse_custom_toolchange_code(self.ui.toolchange_text.get_value())
-                if m6_code is None or m6_code == '':
-                    self.app.inform.emit(
-                        '[ERROR_NOTCL] %s' % _("Cancelled. The Toolchange Custom code is enabled but it's empty.")
-                    )
-                    return 'fail'
-
-                g = g.replace('M6', m6_code)
-                self.app.inform.emit('[success] %s' % _("Toolchange G-code was replaced by a custom code."))
-
-        lines = StringIO(g)
-
-        # Write
-        if filename is not None:
-            try:
-                force_windows_line_endings = self.app.defaults['cncjob_line_ending']
-                if force_windows_line_endings and sys.platform != 'win32':
-                    with open(filename, 'w', newline='\r\n') as f:
-                        for line in lines:
-                            f.write(line)
-                else:
-                    with open(filename, 'w') as f:
-                        for line in lines:
-                            f.write(line)
-            except FileNotFoundError:
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory"))
-                return
-            except PermissionError:
-                self.app.inform.emit(
-                    '[WARNING] %s' % _("Permission denied, saving not possible.\n"
-                                       "Most likely another app is holding the file open and not accessible.")
-                )
-                return 'fail'
-        elif to_file is False:
-            # Just for adding it to the recent files list.
-            if self.app.defaults["global_open_style"] is False:
-                self.app.file_opened.emit("cncjob", filename)
-            self.app.file_saved.emit("cncjob", filename)
-
-            self.app.inform.emit('[success] %s: %s' % (_("Saved to"), filename))
-        else:
-            return lines
-
-    def on_toolchange_custom_clicked(self, signal):
-        """
-        Handler for clicking toolchange custom.
-
-        :param signal:
-        :return:
-        """
-
-        try:
-            if 'toolchange_custom' not in str(self.options['ppname_e']).lower():
-                if self.ui.toolchange_cb.get_value():
-                    self.ui.toolchange_cb.set_value(False)
-                    self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                         _("The used preprocessor file has to have in it's name: 'toolchange_custom'"))
-        except KeyError:
-            try:
-                for key in self.cnc_tools:
-                    ppg = self.cnc_tools[key]['data']['ppname_g']
-                    if 'toolchange_custom' not in str(ppg).lower():
-                        print(ppg)
-                        if self.ui.toolchange_cb.get_value():
-                            self.ui.toolchange_cb.set_value(False)
-                            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                                 _("The used preprocessor file has to have in it's name: "
-                                                   "'toolchange_custom'"))
-            except KeyError:
-                self.app.inform.emit('[ERROR] %s' % _("There is no preprocessor file."))
-
-    def get_gcode(self, preamble='', postamble=''):
-        """
-        We need this to be able to get_gcode separately for shell command export_gcode
-
-        :param preamble:    Extra GCode added to the beginning of the GCode
-        :param postamble:   Extra GCode added at the end of the GCode
-        :return:            The modified GCode
-        """
-        return preamble + '\n' + self.gcode + "\n" + postamble
-
-    def get_svg(self):
-        # we need this to be able get_svg separately for shell command export_svg
-        pass
-
-    def on_plot_cb_click(self, *args):
-        """
-        Handler for clicking on the Plot checkbox.
-
-        :param args:
-        :return:
-        """
-        if self.muted_ui:
-            return
-        kind = self.ui.cncplot_method_combo.get_value()
-        self.plot(kind=kind)
-        self.read_form_item('plot')
-
-        self.ui_disconnect()
-        cb_flag = self.ui.plot_cb.isChecked()
-        for row in range(self.ui.cnc_tools_table.rowCount()):
-            table_cb = self.ui.cnc_tools_table.cellWidget(row, 6)
-            if cb_flag:
-                table_cb.setChecked(True)
-            else:
-                table_cb.setChecked(False)
-        self.ui_connect()
-
-    def on_plot_cb_click_table(self):
-        """
-        Handler for clicking the plot checkboxes added into a Table on each row. Purpose: toggle visibility for the
-        tool/aperture found on that row.
-        :return:
-        """
-
-        # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked)
-        self.ui_disconnect()
-        # cw = self.sender()
-        # cw_index = self.ui.cnc_tools_table.indexAt(cw.pos())
-        # cw_row = cw_index.row()
-
-        kind = self.ui.cncplot_method_combo.get_value()
-
-        self.shapes.clear(update=True)
-
-        for tooluid_key in self.cnc_tools:
-            tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
-            gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
-            # tool_uid = int(self.ui.cnc_tools_table.item(cw_row, 3).text())
-
-            for r in range(self.ui.cnc_tools_table.rowCount()):
-                if int(self.ui.cnc_tools_table.item(r, 5).text()) == int(tooluid_key):
-                    if self.ui.cnc_tools_table.cellWidget(r, 6).isChecked():
-                        self.plot2(tooldia=tooldia, obj=self, visible=True, gcode_parsed=gcode_parsed, kind=kind)
-
-        self.shapes.redraw()
-
-        # make sure that the general plot is disabled if one of the row plot's are disabled and
-        # if all the row plot's are enabled also enable the general plot checkbox
-        cb_cnt = 0
-        total_row = self.ui.cnc_tools_table.rowCount()
-        for row in range(total_row):
-            if self.ui.cnc_tools_table.cellWidget(row, 6).isChecked():
-                cb_cnt += 1
-            else:
-                cb_cnt -= 1
-        if cb_cnt < total_row:
-            self.ui.plot_cb.setChecked(False)
-        else:
-            self.ui.plot_cb.setChecked(True)
-        self.ui_connect()
-
-    def plot(self, visible=None, kind='all'):
-        """
-        # Does all the required setup and returns False
-        # if the 'ptint' option is set to False.
-
-        :param visible: Boolean to decide if the object will be plotted as visible or disabled on canvas
-        :param kind:    String. Can be "all" or "travel" or "cut". For CNCJob plotting
-        :return:        None
-        """
-        if not FlatCAMObj.plot(self):
-            return
-
-        visible = visible if visible else self.options['plot']
-
-        if self.app.is_legacy is False:
-            if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
-                self.text_col.enabled = True
-            else:
-                self.text_col.enabled = False
-            self.annotation.redraw()
-
-        try:
-            if self.multitool is False:  # single tool usage
-                try:
-                    dia_plot = float(self.options["tooldia"])
-                except ValueError:
-                    # we may have a tuple with only one element and a comma
-                    dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0]
-                self.plot2(dia_plot, obj=self, visible=visible, kind=kind)
-            else:
-                # multiple tools usage
-                if self.cnc_tools:
-                    for tooluid_key in self.cnc_tools:
-                        tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
-                        gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
-                        self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
-
-                # TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed
-                if self.exc_cnc_tools:
-                    for tooldia_key in self.exc_cnc_tools:
-                        tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
-                        # gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed']
-                        gcode_parsed = self.gcode_parsed
-                        self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
-
-            self.shapes.redraw()
-        except (ObjectDeleted, AttributeError):
-            self.shapes.clear(update=True)
-            if self.app.is_legacy is False:
-                self.annotation.clear(update=True)
-
-    def on_annotation_change(self):
-        """
-        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()
-        else:
-            kind = self.ui.cncplot_method_combo.get_value()
-            self.plot(kind=kind)
-
-    def convert_units(self, units):
-        """
-        Units conversion used by the CNCJob objects.
-
-        :param units:   Can be "MM" or "IN"
-        :return:
-        """
-
-        log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()")
-
-        factor = CNCjob.convert_units(self, units)
-        self.options["tooldia"] = float(self.options["tooldia"]) * factor
-
-        param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
-                      'endz', 'toolchangez']
-
-        temp_tools_dict = {}
-        tool_dia_copy = {}
-        data_copy = {}
-
-        for tooluid_key, tooluid_value in self.cnc_tools.items():
-            for dia_key, dia_value in tooluid_value.items():
-                if dia_key == 'tooldia':
-                    dia_value *= factor
-                    dia_value = float('%.*f' % (self.decimals, dia_value))
-                    tool_dia_copy[dia_key] = dia_value
-                if dia_key == 'offset':
-                    tool_dia_copy[dia_key] = dia_value
-                if dia_key == 'offset_value':
-                    dia_value *= factor
-                    tool_dia_copy[dia_key] = dia_value
-
-                if dia_key == 'type':
-                    tool_dia_copy[dia_key] = dia_value
-                if dia_key == 'tool_type':
-                    tool_dia_copy[dia_key] = dia_value
-                if dia_key == 'data':
-                    for data_key, data_value in dia_value.items():
-                        # convert the form fields that are convertible
-                        for param in param_list:
-                            if data_key == param and data_value is not None:
-                                data_copy[data_key] = data_value * factor
-                        # copy the other dict entries that are not convertible
-                        if data_key not in param_list:
-                            data_copy[data_key] = data_value
-                    tool_dia_copy[dia_key] = deepcopy(data_copy)
-                    data_copy.clear()
-
-                if dia_key == 'gcode':
-                    tool_dia_copy[dia_key] = dia_value
-                if dia_key == 'gcode_parsed':
-                    tool_dia_copy[dia_key] = dia_value
-                if dia_key == 'solid_geometry':
-                    tool_dia_copy[dia_key] = dia_value
-
-                # if dia_key == 'solid_geometry':
-                #     tool_dia_copy[dia_key] = affinity.scale(dia_value, xfact=factor, origin=(0, 0))
-                # if dia_key == 'gcode_parsed':
-                #     for g in dia_value:
-                #         g['geom'] = affinity.scale(g['geom'], factor, factor, origin=(0, 0))
-                #
-                #     tool_dia_copy['gcode_parsed'] = deepcopy(dia_value)
-                #     tool_dia_copy['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_value])
-
-            temp_tools_dict.update({
-                tooluid_key: deepcopy(tool_dia_copy)
-            })
-            tool_dia_copy.clear()
-
-        self.cnc_tools.clear()
-        self.cnc_tools = deepcopy(temp_tools_dict)
-
-
-class FlatCAMScript(FlatCAMObj):
-    """
-    Represents a TCL script object.
-    """
-    optionChanged = QtCore.pyqtSignal(str)
-    ui_type = ScriptObjectUI
-
-    def __init__(self, name):
-        self.decimals = self.app.decimals
-
-        FlatCAMApp.App.log.debug("Creating a FlatCAMScript object...")
-        FlatCAMObj.__init__(self, name)
-
-        self.kind = "script"
-
-        self.options.update({
-            "plot": True,
-            "type": 'Script',
-            "source_file": '',
-        })
-
-        self.units = ''
-
-        self.ser_attrs = ['options', 'kind', 'source_file']
-        self.source_file = ''
-        self.script_code = ''
-
-        self.units_found = self.app.defaults['units']
-
-        # self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
-        self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
-
-    def set_ui(self, ui):
-        """
-        Sets the Object UI in Selected Tab for the FlatCAM Script type of object.
-        :param ui:
-        :return:
-        """
-        FlatCAMObj.set_ui(self, ui)
-        FlatCAMApp.App.log.debug("FlatCAMScript.set_ui()")
-
-        assert isinstance(self.ui, ScriptObjectUI), \
-            "Expected a ScriptObjectUI, got %s" % type(self.ui)
-
-        self.units = self.app.defaults['units'].upper()
-        self.units_found = self.app.defaults['units']
-
-        # Fill form fields only on object create
-        self.to_form()
-
-        # Show/Hide Advanced Options
-        if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText(_(
-                '<span style="color:green;"><b>Basic</b></span>'
-            ))
-        else:
-            self.ui.level.setText(_(
-                '<span style="color:red;"><b>Advanced</b></span>'
-            ))
-
-        # tab_here = False
-        # # try to not add too many times a tab that it is already installed
-        # for idx in range(self.app.ui.plot_tab_area.count()):
-        #     if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
-        #         tab_here = True
-        #         break
-        #
-        # # add the tab if it is not already added
-        # if tab_here is False:
-        #     self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
-        #     self.script_editor_tab.setObjectName(self.options['name'])
-
-        self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
-        self.script_editor_tab.setObjectName(self.options['name'])
-
-        # first clear previous text in text editor (if any)
-        # self.script_editor_tab.code_editor.clear()
-        # self.script_editor_tab.code_editor.setReadOnly(False)
-
-        self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter'])
-        self.on_autocomplete_changed(state=self.app.defaults['script_autocompleter'])
-
-        self.script_editor_tab.buttonRun.show()
-
-        # Switch plot_area to CNCJob tab
-        self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
-
-        flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
-        self.script_editor_tab.buttonOpen.clicked.disconnect()
-        self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt))
-        self.script_editor_tab.buttonSave.clicked.disconnect()
-        self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt))
-
-        self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code)
-        self.script_editor_tab.handleTextChanged()
-
-        self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
-
-        self.ser_attrs = ['options', 'kind', 'source_file']
-
-        # ---------------------------------------------------- #
-        # ----------- LOAD THE TEXT SOURCE FILE -------------- #
-        # ---------------------------------------------------- #
-        self.app.proc_container.view.set_busy(_("Loading..."))
-        self.script_editor_tab.t_frame.hide()
-
-        try:
-            self.script_editor_tab.code_editor.setPlainText(self.source_file)
-            # for line in self.source_file.splitlines():
-            #     QtWidgets.QApplication.processEvents()
-            #     self.script_editor_tab.code_editor.append(line)
-        except Exception as e:
-            log.debug("FlatCAMScript.set_ui() --> %s" % str(e))
-
-        self.script_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.End)
-        self.script_editor_tab.t_frame.show()
-
-        self.app.proc_container.view.set_idle()
-        self.build_ui()
-
-    def build_ui(self):
-        FlatCAMObj.build_ui(self)
-
-    def handle_run_code(self):
-        # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
-        # tries to print on a hidden widget, therefore show the dock if hidden
-        if self.app.ui.shell_dock.isHidden():
-            self.app.ui.shell_dock.show()
-
-        self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText())
-
-        old_line = ''
-        for tcl_command_line in self.script_code.splitlines():
-            # do not process lines starting with '#' = comment and empty lines
-            if not tcl_command_line.startswith('#') and tcl_command_line != '':
-                # id FlatCAM is run in Windows then replace all the slashes with
-                # the UNIX style slash that TCL understands
-                if sys.platform == 'win32':
-                    if "open" in tcl_command_line:
-                        tcl_command_line = tcl_command_line.replace('\\', '/')
-
-                if old_line != '':
-                    new_command = old_line + tcl_command_line + '\n'
-                else:
-                    new_command = tcl_command_line
-
-                # execute the actual Tcl command
-                try:
-                    self.app.shell.open_processing()  # Disables input box.
-
-                    result = self.app.tcl.eval(str(new_command))
-                    if result != 'None':
-                        self.app.shell.append_output(result + '\n')
-
-                    old_line = ''
-                except tk.TclError:
-                    old_line = old_line + tcl_command_line + '\n'
-                except Exception as e:
-                    log.debug("FlatCAMScript.handleRunCode() --> %s" % str(e))
-
-        if old_line != '':
-            # it means that the script finished with an error
-            result = self.app.tcl.eval("set errorInfo")
-            log.error("Exec command Exception: %s" % (result + '\n'))
-            self.app.shell.append_error('ERROR: ' + result + '\n')
-
-        self.app.shell.close_processing()
-
-    def on_autocomplete_changed(self, state):
-        if state:
-            self.script_editor_tab.code_editor.completer_enable = True
-        else:
-            self.script_editor_tab.code_editor.completer_enable = False
-
-    def to_dict(self):
-        """
-        Returns a representation of the object as a dictionary.
-        Attributes to include are listed in ``self.ser_attrs``.
-
-        :return: A dictionary-encoded copy of the object.
-        :rtype: dict
-        """
-        d = {}
-        for attr in self.ser_attrs:
-            d[attr] = getattr(self, attr)
-        return d
-
-    def from_dict(self, d):
-        """
-        Sets object's attributes from a dictionary.
-        Attributes to include are listed in ``self.ser_attrs``.
-        This method will look only for only and all the
-        attributes in ``self.ser_attrs``. They must all
-        be present. Use only for deserializing saved
-        objects.
-
-        :param d: Dictionary of attributes to set in the object.
-        :type d: dict
-        :return: None
-        """
-        for attr in self.ser_attrs:
-            setattr(self, attr, d[attr])
-
-
-class FlatCAMDocument(FlatCAMObj):
-    """
-    Represents a Document object.
-    """
-    optionChanged = QtCore.pyqtSignal(str)
-    ui_type = DocumentObjectUI
-
-    def __init__(self, name):
-        self.decimals = self.app.decimals
-
-        FlatCAMApp.App.log.debug("Creating a Document object...")
-        FlatCAMObj.__init__(self, name)
-
-        self.kind = "document"
-        self.units = ''
-
-        self.ser_attrs = ['options', 'kind', 'source_file']
-        self.source_file = ''
-        self.doc_code = ''
-
-        self.font_name = None
-        self.font_italic = None
-        self.font_bold = None
-        self.font_underline = None
-
-        self.document_editor_tab = None
-
-        self._read_only = False
-        self.units_found = self.app.defaults['units']
-
-    def set_ui(self, ui):
-        FlatCAMObj.set_ui(self, ui)
-        FlatCAMApp.App.log.debug("FlatCAMDocument.set_ui()")
-
-        assert isinstance(self.ui, DocumentObjectUI), \
-            "Expected a DocumentObjectUI, got %s" % type(self.ui)
-
-        self.units = self.app.defaults['units'].upper()
-        self.units_found = self.app.defaults['units']
-
-        # Fill form fields only on object create
-        self.to_form()
-
-        # Show/Hide Advanced Options
-        if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText(_(
-                '<span style="color:green;"><b>Basic</b></span>'
-            ))
-        else:
-            self.ui.level.setText(_(
-                '<span style="color:red;"><b>Advanced</b></span>'
-            ))
-
-        self.document_editor_tab = TextEditor(app=self.app)
-        stylesheet = """
-                        QTextEdit {selection-background-color:%s;
-                                   selection-color:white;
-                        }
-                     """ % self.app.defaults["document_sel_color"]
-
-        self.document_editor_tab.code_editor.setStyleSheet(stylesheet)
-
-        # first clear previous text in text editor (if any)
-        self.document_editor_tab.code_editor.clear()
-        self.document_editor_tab.code_editor.setReadOnly(self._read_only)
-
-        self.document_editor_tab.buttonRun.hide()
-
-        self.ui.autocomplete_cb.set_value(self.app.defaults['document_autocompleter'])
-        self.on_autocomplete_changed(state=self.app.defaults['document_autocompleter'])
-        self.on_tab_size_change(val=self.app.defaults['document_tab_size'])
-
-        flt = "FlatCAM Docs (*.FlatDoc);;All Files (*.*)"
-
-        # ######################################################################
-        # ######################## SIGNALS #####################################
-        # ######################################################################
-        self.document_editor_tab.buttonOpen.clicked.disconnect()
-        self.document_editor_tab.buttonOpen.clicked.connect(lambda: self.document_editor_tab.handleOpen(filt=flt))
-        self.document_editor_tab.buttonSave.clicked.disconnect()
-        self.document_editor_tab.buttonSave.clicked.connect(lambda: self.document_editor_tab.handleSaveGCode(filt=flt))
-
-        self.document_editor_tab.code_editor.textChanged.connect(self.on_text_changed)
-
-        self.ui.font_type_cb.currentFontChanged.connect(self.font_family)
-        self.ui.font_size_cb.activated.connect(self.font_size)
-        self.ui.font_bold_tb.clicked.connect(self.on_bold_button)
-        self.ui.font_italic_tb.clicked.connect(self.on_italic_button)
-        self.ui.font_under_tb.clicked.connect(self.on_underline_button)
-
-        self.ui.font_color_entry.editingFinished.connect(self.on_font_color_entry)
-        self.ui.font_color_button.clicked.connect(self.on_font_color_button)
-        self.ui.sel_color_entry.editingFinished.connect(self.on_selection_color_entry)
-        self.ui.sel_color_button.clicked.connect(self.on_selection_color_button)
-
-        self.ui.al_left_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignLeft))
-        self.ui.al_center_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignCenter))
-        self.ui.al_right_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignRight))
-        self.ui.al_justify_tb.clicked.connect(
-            lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignJustify)
-        )
-
-        self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
-        self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change)
-        # #######################################################################
-
-        self.ui.font_color_entry.set_value(self.app.defaults['document_font_color'])
-        self.ui.font_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['document_font_color']))
-
-        self.ui.sel_color_entry.set_value(self.app.defaults['document_sel_color'])
-        self.ui.sel_color_button.setStyleSheet(
-            "background-color:%s" % self.app.defaults['document_sel_color'])
-
-        self.ui.font_size_cb.setCurrentIndex(int(self.app.defaults['document_font_size']))
-
-        self.document_editor_tab.handleTextChanged()
-        self.ser_attrs = ['options', 'kind', 'source_file']
-
-        if Qt.mightBeRichText(self.source_file):
-            self.document_editor_tab.code_editor.setHtml(self.source_file)
-        else:
-            for line in self.source_file.splitlines():
-                self.document_editor_tab.code_editor.append(line)
-
-        self.build_ui()
-
-    @property
-    def read_only(self):
-        return self._read_only
-
-    @read_only.setter
-    def read_only(self, val):
-        if val:
-            self._read_only = True
-        else:
-            self._read_only = False
-
-    def build_ui(self):
-        FlatCAMObj.build_ui(self)
-        tab_here = False
-
-        # try to not add too many times a tab that it is already installed
-        for idx in range(self.app.ui.plot_tab_area.count()):
-            if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
-                tab_here = True
-                break
-
-        # add the tab if it is not already added
-        if tab_here is False:
-            self.app.ui.plot_tab_area.addTab(self.document_editor_tab, '%s' % _("Document Editor"))
-            self.document_editor_tab.setObjectName(self.options['name'])
-
-        # Switch plot_area to CNCJob tab
-        self.app.ui.plot_tab_area.setCurrentWidget(self.document_editor_tab)
-
-    def on_autocomplete_changed(self, state):
-        if state:
-            self.document_editor_tab.code_editor.completer_enable = True
-        else:
-            self.document_editor_tab.code_editor.completer_enable = False
-
-    def on_tab_size_change(self, val=None):
-        try:
-            self.ui.tab_size_spinner.returnPressed.disconnect(self.on_tab_size_change)
-        except TypeError:
-            pass
-
-        if val:
-            self.ui.tab_size_spinner.set_value(val)
-
-        tab_balue = int(self.ui.tab_size_spinner.get_value())
-        self.document_editor_tab.code_editor.setTabStopWidth(tab_balue)
-        self.app.defaults['document_tab_size'] = tab_balue
-
-        self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change)
-
-    def on_text_changed(self):
-        self.source_file = self.document_editor_tab.code_editor.toHtml()
-        # print(self.source_file)
-
-    def font_family(self, font):
-        # self.document_editor_tab.code_editor.selectAll()
-        font.setPointSize(float(self.ui.font_size_cb.get_value()))
-        self.document_editor_tab.code_editor.setCurrentFont(font)
-        self.font_name = self.ui.font_type_cb.currentFont().family()
-
-    def font_size(self):
-        # self.document_editor_tab.code_editor.selectAll()
-        self.document_editor_tab.code_editor.setFontPointSize(float(self.ui.font_size_cb.get_value()))
-
-    def on_bold_button(self):
-        if self.ui.font_bold_tb.isChecked():
-            self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Bold)
-            self.font_bold = True
-        else:
-            self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Normal)
-            self.font_bold = False
-
-    def on_italic_button(self):
-        if self.ui.font_italic_tb.isChecked():
-            self.document_editor_tab.code_editor.setFontItalic(True)
-            self.font_italic = True
-        else:
-            self.document_editor_tab.code_editor.setFontItalic(False)
-            self.font_italic = False
-
-    def on_underline_button(self):
-        if self.ui.font_under_tb.isChecked():
-            self.document_editor_tab.code_editor.setFontUnderline(True)
-            self.font_underline = True
-        else:
-            self.document_editor_tab.code_editor.setFontUnderline(False)
-            self.font_underline = False
-
-    # Setting font colors handlers
-    def on_font_color_entry(self):
-        self.app.defaults['document_font_color'] = self.ui.font_color_entry.get_value()
-        self.ui.font_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_font_color']))
-
-    def on_font_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['document_font_color'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        font_color = c_dialog.getColor(initial=current_color)
-
-        if font_color.isValid() is False:
-            return
-
-        self.document_editor_tab.code_editor.setTextColor(font_color)
-        self.ui.font_color_button.setStyleSheet("background-color:%s" % str(font_color.name()))
-
-        new_val = str(font_color.name())
-        self.ui.font_color_entry.set_value(new_val)
-        self.app.defaults['document_font_color'] = new_val
-
-    # Setting selection colors handlers
-    def on_selection_color_entry(self):
-        self.app.defaults['document_sel_color'] = self.ui.sel_color_entry.get_value()
-        self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_sel_color']))
-
-    def on_selection_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['document_sel_color'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        sel_color = c_dialog.getColor(initial=current_color)
-
-        if sel_color.isValid() is False:
-            return
-
-        p = QtGui.QPalette()
-        p.setColor(QtGui.QPalette.Highlight, sel_color)
-        p.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor('white'))
-
-        self.document_editor_tab.code_editor.setPalette(p)
-
-        self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(sel_color.name()))
-
-        new_val = str(sel_color.name())
-        self.ui.sel_color_entry.set_value(new_val)
-        self.app.defaults['document_sel_color'] = new_val
-
-    def to_dict(self):
-        """
-        Returns a representation of the object as a dictionary.
-        Attributes to include are listed in ``self.ser_attrs``.
-
-        :return: A dictionary-encoded copy of the object.
-        :rtype: dict
-        """
-        d = {}
-        for attr in self.ser_attrs:
-            d[attr] = getattr(self, attr)
-        return d
-
-    def from_dict(self, d):
-        """
-        Sets object's attributes from a dictionary.
-        Attributes to include are listed in ``self.ser_attrs``.
-        This method will look only for only and all the
-        attributes in ``self.ser_attrs``. They must all
-        be present. Use only for deserializing saved
-        objects.
-
-        :param d: Dictionary of attributes to set in the object.
-        :type d: dict
-        :return: None
-        """
-        for attr in self.ser_attrs:
-            setattr(self, attr, d[attr])
-
-# end of file

+ 4 - 2
FlatCAMPostProc.py

@@ -12,8 +12,10 @@ from abc import ABCMeta, abstractmethod
 import math
 
 # module-root dictionary of preprocessors
-import FlatCAMApp
 
+import logging
+
+log = logging.getLogger('base')
 preprocessors = {}
 
 
@@ -23,7 +25,7 @@ class ABCPostProcRegister(ABCMeta):
         newclass = super(ABCPostProcRegister, cls).__new__(cls, clsname, bases, attrs)
         if object not in bases:
             if newclass.__name__ in preprocessors:
-                FlatCAMApp.App.log.warning('Preprocessor %s has been overriden' % newclass.__name__)
+                log.warning('Preprocessor %s has been overriden' % newclass.__name__)
             preprocessors[newclass.__name__] = newclass()  # here is your register function
         return newclass
 

+ 1 - 0
FlatCAMTranslation.py

@@ -29,6 +29,7 @@ languages_dict = {
     'en': 'English',
     'es': 'Spanish',
     'fr': 'French',
+    'hu': 'Hungarian',
     'it': 'Italian',
     'ro': 'Romanian',
     'ru': 'Russian',

+ 42 - 30
ObjectCollection.py

@@ -16,10 +16,15 @@ from PyQt5.QtCore import Qt, QSettings
 from PyQt5.QtGui import QColor
 # from PyQt5.QtCore import QModelIndex
 
-from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon, FlatCAMCNCjob, FlatCAMDocument, FlatCAMScript, \
-    FlatCAMObj
+from flatcamObjects.FlatCAMObj import FlatCAMObj
+from flatcamObjects.FlatCAMCNCJob import CNCJobObject
+from flatcamObjects.FlatCAMDocument import DocumentObject
+from flatcamObjects.FlatCAMExcellon import ExcellonObject
+from flatcamObjects.FlatCAMGeometry import GeometryObject
+from flatcamObjects.FlatCAMGerber import GerberObject
+from flatcamObjects.FlatCAMScript import ScriptObject
+
 import inspect  # TODO: Remove
-import FlatCAMApp
 
 import re
 import logging
@@ -234,12 +239,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
     ]
 
     classdict = {
-        "gerber": FlatCAMGerber,
-        "excellon": FlatCAMExcellon,
-        "cncjob": FlatCAMCNCjob,
-        "geometry": FlatCAMGeometry,
-        "script": FlatCAMScript,
-        "document": FlatCAMDocument
+        "gerber": GerberObject,
+        "excellon": ExcellonObject,
+        "cncjob": CNCJobObject,
+        "geometry": GeometryObject,
+        "script": ScriptObject,
+        "document": DocumentObject
     }
 
     icon_files = {
@@ -332,7 +337,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         self.update_list_signal.connect(self.on_update_list_signal)
 
     def promise(self, obj_name):
-        FlatCAMApp.App.log.debug("Object %s has been promised." % obj_name)
+        log.debug("Object %s has been promised." % obj_name)
         self.promises.add(obj_name)
 
     def has_promises(self):
@@ -349,7 +354,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         return len(self.plot_promises) > 0
 
     def on_mouse_down(self, event):
-        FlatCAMApp.App.log.debug("Mouse button pressed on list")
+        log.debug("Mouse button pressed on list")
 
     def on_menu_request(self, pos):
 
@@ -373,17 +378,17 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             self.app.ui.menuprojectcolor.setEnabled(False)
 
             for obj in self.get_selected():
-                if type(obj) == FlatCAMGerber or type(obj) == FlatCAMExcellon:
+                if type(obj) == GerberObject or type(obj) == ExcellonObject:
                     self.app.ui.menuprojectcolor.setEnabled(True)
 
-                if type(obj) != FlatCAMGeometry:
+                if type(obj) != GeometryObject:
                     self.app.ui.menuprojectgeneratecnc.setVisible(False)
-                if type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMGerber:
+                if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject:
                     self.app.ui.menuprojectedit.setVisible(False)
-                if type(obj) != FlatCAMGerber and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMCNCjob:
+                if type(obj) != GerberObject and type(obj) != ExcellonObject and type(obj) != CNCJobObject:
                     self.app.ui.menuprojectviewsource.setVisible(False)
-                if type(obj) != FlatCAMGerber and type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and \
-                        type(obj) != FlatCAMCNCjob:
+                if type(obj) != GerberObject and type(obj) != GeometryObject and type(obj) != ExcellonObject and \
+                        type(obj) != CNCJobObject:
                     # meaning for Scripts and for Document type of FlatCAM object
                     self.app.ui.menuprojectenable.setVisible(False)
                     self.app.ui.menuprojectdisable.setVisible(False)
@@ -532,21 +537,21 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         # return QtWidgets.QAbstractItemModel.flags(self, index)
 
     def append(self, obj, active=False, to_index=None):
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
+        log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
 
         name = obj.options["name"]
 
         # Check promises and clear if exists
         if name in self.promises:
             self.promises.remove(name)
-            # FlatCAMApp.App.log.debug("Promised object %s became available." % name)
-            # FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises))
+            # log.debug("Promised object %s became available." % name)
+            # log.debug("%d promised objects remaining." % len(self.promises))
 
         # Prevent same name
         while name in self.get_names():
             # ## Create a new name
             # Ends with number?
-            FlatCAMApp.App.log.debug("new_object(): Object name (%s) exists, changing." % name)
+            log.debug("new_object(): Object name (%s) exists, changing." % name)
             match = re.search(r'(.*[^\d])?(\d+)$', name)
             if match:  # Yes: Increment the number!
                 base = match.group(1) or ''
@@ -596,7 +601,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :rtype: list
         """
 
-        # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
+        # log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
         return [x.options['name'] for x in self.get_list()]
 
     def get_bounds(self):
@@ -606,7 +611,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :return: [xmin, ymin, xmax, ymax]
         :rtype: list
         """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")
+        log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")
 
         # TODO: Move the operation out of here.
 
@@ -624,7 +629,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 xmax = max([xmax, gxmax])
                 ymax = max([ymax, gymax])
             except Exception as e:
-                FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry. %s" % str(e))
+                log.warning("DEV WARNING: Tried to get bounds of empty geometry. %s" % str(e))
 
         return [xmin, ymin, xmax, ymax]
 
@@ -638,7 +643,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :return: The requested object or None if no such object.
         :rtype: FlatCAMObj or None
         """
-        # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
+        # log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
 
         if isCaseSensitive is None or isCaseSensitive is True:
             for obj in self.get_list():
@@ -760,7 +765,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         self.app.all_objects_list = self.get_list()
 
     def delete_all(self):
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
+        log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
 
         self.app.object_status_changed.emit(None, 'delete_all', '')
 
@@ -897,8 +902,15 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             self.set_inactive(name)
 
     def on_list_selection_change(self, current, previous):
-        # FlatCAMApp.App.log.debug("on_list_selection_change()")
-        # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
+        """
+
+        :param current:     Current selected item
+        :param previous:    Previously selected item
+        :return:
+        """
+
+        # log.debug("on_list_selection_change()")
+        # log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
 
         try:
             obj = current.indexes()[0].internalPointer().obj
@@ -942,12 +954,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 )
         except IndexError:
             self.item_selected.emit('none')
-            # FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
+            # log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
             self.app.inform.emit('')
             try:
                 self.app.ui.selected_scroll_area.takeWidget()
             except Exception as e:
-                FlatCAMApp.App.log.debug("Nothing to remove. %s" % str(e))
+                log.debug("Nothing to remove. %s" % str(e))
 
             self.app.setup_component_editor()
             return

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 231 - 204
camlib.py


+ 38 - 36
flatcamEditors/FlatCAMExcEditor.py

@@ -12,7 +12,6 @@ from camlib import distance, arc, FlatCAMRTreeStorage
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
 from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
 from flatcamParsers.ParseExcellon import Excellon
-import FlatCAMApp
 
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
 import shapely.affinity as affinity
@@ -179,7 +178,7 @@ class FCDrillArray(FCShapeTool):
 
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
 
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_drill_array.png'))
@@ -1516,7 +1515,7 @@ class FlatCAMExcEditor(QtCore.QObject):
     draw_shape_idx = -1
 
     def __init__(self, app):
-        assert isinstance(app, FlatCAMApp.App), "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
+        # assert isinstance(app, FlatCAMApp.App), "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
 
         super(FlatCAMExcEditor, self).__init__()
 
@@ -2230,8 +2229,8 @@ class FlatCAMExcEditor(QtCore.QObject):
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
 
-        def entry2option(option, entry):
-            self.options[option] = float(entry.text())
+        # def entry2option(option, entry):
+        #     self.options[option] = float(entry.text())
 
         # Event signals disconnect id holders
         self.mp = None
@@ -2388,7 +2387,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
             try:
                 # Find no of slots for the current tool
-                for slot in self.slots:
+                for slot in self.slot_points_edit:
                     if slot['tool'] == tool_no:
                         slot_cnt += 1
 
@@ -2661,15 +2660,13 @@ class FlatCAMExcEditor(QtCore.QObject):
         # self.tools_table_exc.selectionModel().currentChanged.disconnect()
 
         self.is_modified = True
-        new_dia = None
+        # new_dia = None
 
-        if self.tools_table_exc.currentItem() is not None:
-            try:
-                new_dia = float(self.tools_table_exc.currentItem().text())
-            except ValueError as e:
-                log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e))
-                self.tools_table_exc.setCurrentItem(None)
-                return
+        try:
+            new_dia = float(self.tools_table_exc.currentItem().text())
+        except ValueError as e:
+            log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e))
+            return
 
         row_of_item_changed = self.tools_table_exc.currentRow()
         # rows start with 0, tools start with 1 so we adjust the value by 1
@@ -3042,7 +3039,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         Imports the geometry from the given FlatCAM Excellon object
         into the editor.
 
-        :param exc_obj: FlatCAMExcellon object
+        :param exc_obj: ExcellonObject object
         :return: None
         """
 
@@ -3118,7 +3115,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         """
         Create a new Excellon object that contain the edited content of the source Excellon object
 
-        :param exc_obj: FlatCAMExcellon
+        :param exc_obj: ExcellonObject
         :return: None
         """
 
@@ -3297,7 +3294,8 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         return self.edited_obj_name
 
-    def update_options(self, obj):
+    @staticmethod
+    def update_options(obj):
         try:
             if not obj.options:
                 obj.options = {}
@@ -3316,10 +3314,14 @@ class FlatCAMExcEditor(QtCore.QObject):
         """
         Creates a new Excellon object for the edited Excellon. Thread-safe.
 
-        :param outname: Name of the resulting object. None causes the
-            name to be that of the file.
-        :type outname: str
-        :return: None
+        :param outname:     Name of the resulting object. None causes the
+                            name to be that of the file.
+        :type outname:      str
+
+        :param n_drills:    The new Drills storage
+        :param n_slots:     The new Slots storage
+        :param n_tools:     The new Tools storage
+        :return:            None
         """
 
         self.app.log.debug("Update the Excellon object with edited content. Source is %s" %
@@ -3429,12 +3431,12 @@ class FlatCAMExcEditor(QtCore.QObject):
 
             self.replot()
 
-    def toolbar_tool_toggle(self, key):
-        self.options[key] = self.sender().isChecked()
-        if self.options[key] is True:
-            return 1
-        else:
-            return 0
+    # def toolbar_tool_toggle(self, key):
+    #     self.options[key] = self.sender().isChecked()
+    #     if self.options[key] is True:
+    #         return 1
+    #     else:
+    #         return 0
 
     def on_canvas_click(self, event):
         """
@@ -3446,12 +3448,12 @@ class FlatCAMExcEditor(QtCore.QObject):
         """
         if self.app.is_legacy is False:
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
-            right_button = 2
+            # event_is_dragging = event.is_dragging
+            # right_button = 2
         else:
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+            # event_is_dragging = self.app.plotcanvas.is_dragging
+            # right_button = 3
 
         self.pos = self.canvas.translate_coords(event_pos)
 
@@ -3575,8 +3577,8 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         if isinstance(shape, DrawToolUtilityShape):
             self.utility.append(shape)
-        else:
-            self.storage.insert(shape)  # TODO: Check performance
+        # else:
+        #     self.storage.insert(shape)
 
     def on_exc_click_release(self, event):
         """
@@ -3591,11 +3593,11 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         if self.app.is_legacy is False:
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
+            # event_is_dragging = event.is_dragging
             right_button = 2
         else:
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
+            # event_is_dragging = self.app.plotcanvas.is_dragging
             right_button = 3
 
         pos_canvas = self.canvas.translate_coords(event_pos)
@@ -4027,7 +4029,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                     del self.slot_points_edit[storage][0]
 
         if del_shape in self.selected:
-            self.selected.remove(del_shape)  # TODO: Check performance
+            self.selected.remove(del_shape)
 
     def delete_utility_geometry(self):
         for_deletion = [util_shape for util_shape in self.utility]

+ 7 - 7
flatcamEditors/FlatCAMGeoEditor.py

@@ -20,7 +20,6 @@ from flatcamGUI.ObjectUI import RadioSet
 from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
     FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog, FCTree
 from flatcamParsers.ParseFont import *
-import FlatCAMApp
 
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
 from shapely.ops import cascaded_union, unary_union, linemerge
@@ -88,8 +87,8 @@ class BufferSelectionTool(FlatCAMTool):
         self.buffer_corner_lbl.setToolTip(
             _("There are 3 types of corners:\n"
               " - 'Round': the corner is rounded for exterior buffer.\n"
-              " - 'Square:' the corner is met in a sharp angle for exterior buffer.\n"
-              " - 'Beveled:' the corner is a line that directly connects the features meeting in the corner")
+              " - 'Square': the corner is met in a sharp angle for exterior buffer.\n"
+              " - 'Beveled': the corner is a line that directly connects the features meeting in the corner")
         )
         self.buffer_corner_cb = FCComboBox()
         self.buffer_corner_cb.addItem(_("Round"))
@@ -3299,8 +3298,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
     draw_shape_idx = -1
 
     def __init__(self, app, disabled=False):
-        assert isinstance(app, FlatCAMApp.App), \
-            "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
+        # assert isinstance(app, FlatCAMApp.App), \
+        #     "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
 
         super(FlatCAMGeoEditor, self).__init__()
 
@@ -4011,6 +4010,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :return:        Boolean. Status of the checkbox that toggled the Editor Tool
         """
         cb_widget = self.sender()
+        assert isinstance(cb_widget, QtWidgets.QAction), "Expected a QAction got %s" % type(cb_widget)
         self.options[key] = cb_widget.isChecked()
 
         return 1 if self.options[key] is True else 0
@@ -4035,7 +4035,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         Imports the geometry from the given FlatCAM Geometry object
         into the editor.
 
-        :param fcgeometry:      FlatCAMGeometry
+        :param fcgeometry:      GeometryObject
         :param multigeo_tool:   A tool for the case of the edited geometry being of type 'multigeo'
         :return:                None
         """
@@ -4750,7 +4750,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         Transfers the geometry tool shape buffer to the selected geometry
         object. The geometry already in the object are removed.
 
-        :param fcgeometry: FlatCAMGeometry
+        :param fcgeometry: GeometryObject
         :return: None
         """
         if self.multigeo_tool:

+ 34 - 34
flatcamEditors/FlatCAMGrbEditor.py

@@ -21,7 +21,6 @@ from camlib import distance, arc, three_point_circle
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
     EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
 from FlatCAMTool import FlatCAMTool
-import FlatCAMApp
 
 import numpy as np
 from numpy.linalg import norm as numpy_norm
@@ -182,6 +181,7 @@ class FCShapeTool(DrawTool):
 
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
+        self.name = None
 
     def make(self):
         pass
@@ -199,7 +199,7 @@ class FCPad(FCShapeTool):
 
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_circle.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
@@ -1415,7 +1415,7 @@ class FCDisc(FCShapeTool):
 
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_disc.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
@@ -2422,8 +2422,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
     mp_finished = QtCore.pyqtSignal(list)
 
     def __init__(self, app):
-        assert isinstance(app, FlatCAMApp.App), \
-            "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
+        # assert isinstance(app, FlatCAMApp.App), \
+        #     "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
 
         super(FlatCAMGrbEditor, self).__init__()
 
@@ -2621,8 +2621,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.buffer_corner_lbl.setToolTip(
             _("There are 3 types of corners:\n"
               " - 'Round': the corner is rounded.\n"
-              " - 'Square:' the corner is met in a sharp angle.\n"
-              " - 'Beveled:' the corner is a line that directly connects the features meeting in the corner")
+              " - 'Square': the corner is met in a sharp angle.\n"
+              " - 'Beveled': the corner is a line that directly connects the features meeting in the corner")
         )
         self.buffer_corner_cb = FCComboBox()
         self.buffer_corner_cb.addItem(_("Round"))
@@ -3479,7 +3479,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 current_table_dia_edited = float(self.apertures_table.currentItem().text())
             except ValueError as e:
                 log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e))
-                self.apertures_table.setCurrentItem(None)
+                # self.apertures_table.setCurrentItem(None)
                 return
 
         row_of_item_changed = self.apertures_table.currentRow()
@@ -3833,7 +3833,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         Imports the geometry found in self.apertures from the given FlatCAM Gerber object
         into the editor.
 
-        :param orig_grb_obj: FlatCAMExcellon
+        :param orig_grb_obj: ExcellonObject
         :return: None
         """
 
@@ -3956,10 +3956,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
                     global_clear_geo = []
 
                     # create one big geometry made out of all 'negative' (clear) polygons
-                    for apid in app_obj.gerber_obj.apertures:
+                    for aper_id in app_obj.gerber_obj.apertures:
                         # first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo
-                        if 'geometry' in app_obj.gerber_obj.apertures[apid]:
-                            for elem in app_obj.gerber_obj.apertures[apid]['geometry']:
+                        if 'geometry' in app_obj.gerber_obj.apertures[aper_id]:
+                            for elem in app_obj.gerber_obj.apertures[aper_id]['geometry']:
                                 if 'clear' in elem:
                                     global_clear_geo.append(elem['clear'])
                     log.warning("Found %d clear polygons." % len(global_clear_geo))
@@ -3967,7 +3967,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                     if global_clear_geo:
                         global_clear_geo = MultiPolygon(global_clear_geo)
                         if isinstance(global_clear_geo, Polygon):
-                            global_clear_geo = list(global_clear_geo)
+                            global_clear_geo = [global_clear_geo]
 
                     # we subtract the big "negative" (clear) geometry from each solid polygon but only the part of
                     # clear geometry that fits inside the solid. otherwise we may loose the solid
@@ -3979,8 +3979,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
                             #         solid_geo = elem['solid']
                             #         for clear_geo in global_clear_geo:
                             #             # Make sure that the clear_geo is within the solid_geo otherwise we loose
-                            #             # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to
-                            #             # delete it
+                            #             # the solid_geometry. We want for clear_geometry just to cut
+                            #             # into solid_geometry not to delete it
                             #             if clear_geo.within(solid_geo):
                             #                 solid_geo = solid_geo.difference(clear_geo)
                             #         try:
@@ -4307,14 +4307,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
             self.plot_all()
 
-    def toolbar_tool_toggle(self, key):
-        """
-
-        :param key: key to update in self.options dictionary
-        :return:
-        """
-        self.options[key] = self.sender().isChecked()
-        return self.options[key]
+    # def toolbar_tool_toggle(self, key):
+    #     """
+    #
+    #     :param key: key to update in self.options dictionary
+    #     :return:
+    #     """
+    #     self.options[key] = self.sender().isChecked()
+    #     return self.options[key]
 
     def on_grb_shape_complete(self, storage=None, specific_shape=None, no_plot=False):
         """
@@ -4389,12 +4389,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
         """
         if self.app.is_legacy is False:
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
-            right_button = 2
+            # event_is_dragging = event.is_dragging
+            # right_button = 2
         else:
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+            # event_is_dragging = self.app.plotcanvas.is_dragging
+            # right_button = 3
 
         self.pos = self.canvas.translate_coords(event_pos)
 
@@ -4457,11 +4457,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.modifiers = QtWidgets.QApplication.keyboardModifiers()
         if self.app.is_legacy is False:
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
+            # event_is_dragging = event.is_dragging
             right_button = 2
         else:
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
+            # event_is_dragging = self.app.plotcanvas.is_dragging
             right_button = 3
 
         pos_canvas = self.canvas.translate_coords(event_pos)
@@ -4747,10 +4747,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.
 
-        :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such)
-        :param color: Shape color
-        :param linewidth: Width of lines in # of pixels.
-        :return: List of plotted elements.
+        :param geometry:    Geometry to be plotted (Any Shapely.geom kind or list of such)
+        :param color:       Shape color
+        :param linewidth:   Width of lines in # of pixels.
+        :return:            List of plotted elements.
         """
 
         if geometry is None:
@@ -5597,7 +5597,7 @@ class TransformEditorTool(FlatCAMTool):
             self.flip_ref_entry.set_value((0, 0))
 
     def template(self):
-        if not self.fcdraw.selected:
+        if not self.draw_app.selected:
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
             return
 

+ 2 - 2
flatcamGUI/FlatCAMGUI.py

@@ -2876,7 +2876,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
                 # Open Excellon file
                 if key == QtCore.Qt.Key_E:
-                    self.app.on_fileopenexcellon()
+                    self.app.on_fileopenexcellon(signal=None)
 
                 # Open Gerber file
                 if key == QtCore.Qt.Key_G:
@@ -2884,7 +2884,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                     if 'editor' in widget_name.lower():
                         self.app.goto_text_line()
                     else:
-                        self.app.on_fileopengerber()
+                        self.app.on_fileopengerber(signal=None)
 
                 # Distance Tool
                 if key == QtCore.Qt.Key_M:

+ 2 - 1
flatcamGUI/ObjectUI.py

@@ -1241,6 +1241,7 @@ class ExcellonObjectUI(ObjectUI):
         self.pdepth_entry.set_precision(self.decimals)
         self.pdepth_entry.set_range(-9999.9999, 9999.9999)
         self.pdepth_entry.setSingleStep(0.1)
+        self.pdepth_entry.setObjectName("e_depth_probe")
 
         self.grid5.addWidget(self.pdepth_label, 13, 0)
         self.grid5.addWidget(self.pdepth_entry, 13, 1)
@@ -1258,7 +1259,7 @@ class ExcellonObjectUI(ObjectUI):
         self.feedrate_probe_entry.set_precision(self.decimals)
         self.feedrate_probe_entry.set_range(0.0, 9999.9999)
         self.feedrate_probe_entry.setSingleStep(0.1)
-        self.feedrate_probe_entry.setObjectName(_("e_fr_probe"))
+        self.feedrate_probe_entry.setObjectName("e_fr_probe")
 
         self.grid5.addWidget(self.feedrate_probe_label, 14, 0)
         self.grid5.addWidget(self.feedrate_probe_entry, 14, 1)

+ 6 - 8
flatcamGUI/PlotCanvasLegacy.py

@@ -16,8 +16,6 @@ from descartes.patch import PolygonPatch
 
 from shapely.geometry import Polygon, LineString, LinearRing
 
-import FlatCAMApp
-
 from copy import deepcopy
 import logging
 
@@ -496,7 +494,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         :param event:
         :return:
         """
-        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
+        log.debug('on_key_down(): ' + str(event.key))
         self.key = event.key
 
     def on_key_up(self, event):
@@ -531,7 +529,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         try:
             self.figure.clf()
         except KeyError:
-            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
+            log.warning("KeyError in MPL figure.clf()")
 
         # Re-build
         self.figure.add_axes(self.axes)
@@ -582,7 +580,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         try:
             r = width / height
         except ZeroDivisionError:
-            FlatCAMApp.App.log.error("Height is %f" % height)
+            log.error("Height is %f" % height)
             return
         canvas_w, canvas_h = self.canvas.get_width_height()
         canvas_r = float(canvas_w) / canvas_h
@@ -1190,10 +1188,10 @@ class ShapeCollectionLegacy:
                                                  linewidth=local_shapes[element]['linewidth'])
                             self.axes.add_patch(patch)
                         except AssertionError:
-                            FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
-                            FlatCAMApp.App.log.warning(str(element))
+                            log.warning("A geometry component was not a polygon:")
+                            log.warning(str(element))
                         except Exception as e:
-                            FlatCAMApp.App.log.debug(
+                            log.debug(
                                 "PlotCanvasLegacy.ShepeCollectionLegacy.redraw() gerber 'solid' --> %s" % str(e))
                     else:
                         try:

+ 1 - 1
flatcamGUI/PreferencesUI.py

@@ -8420,7 +8420,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI):
               "- Excellon Object-> the Excellon object drills center will serve as reference.\n"
               "- Fixed Diameter -> will try to use the pads center as reference adding fixed diameter holes.\n"
               "- Fixed Annular Ring -> will try to keep a set annular ring.\n"
-              "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.\n")
+              "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.")
         )
         grid_lay.addWidget(self.hole_size_label, 9, 0)
         grid_lay.addWidget(self.hole_size_radio, 9, 1)

+ 1220 - 0
flatcamObjects/FlatCAMCNCJob.py

@@ -0,0 +1,1220 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# ##########################################################
+
+# ##########################################################
+# File modified by: Marius Stanciu                         #
+# ##########################################################
+
+from copy import deepcopy
+from io import StringIO
+from datetime import datetime
+
+from flatcamEditors.FlatCAMTextEditor import TextEditor
+from flatcamObjects.FlatCAMObj import *
+
+from camlib import CNCjob
+
+import os
+import sys
+import math
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class CNCJobObject(FlatCAMObj, CNCjob):
+    """
+    Represents G-Code.
+    """
+    optionChanged = QtCore.pyqtSignal(str)
+    ui_type = CNCObjectUI
+
+    def __init__(self, name, units="in", kind="generic", z_move=0.1,
+                 feedrate=3.0, feedrate_rapid=3.0, z_cut=-0.002, tooldia=0.0,
+                 spindlespeed=None):
+
+        log.debug("Creating CNCJob object...")
+
+        self.decimals = self.app.decimals
+
+        CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
+                        feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia,
+                        spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"]))
+
+        FlatCAMObj.__init__(self, name)
+
+        self.kind = "cncjob"
+
+        self.options.update({
+            "plot": True,
+            "tooldia": 0.03937,  # 0.4mm in inches
+            "append": "",
+            "prepend": "",
+            "dwell": False,
+            "dwelltime": 1,
+            "type": 'Geometry',
+            "toolchange_macro": '',
+            "toolchange_macro_enable": False
+        })
+
+        '''
+            This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the 
+            diameter of the tools and the value is another dict that will hold the data under the following form:
+               {tooldia:   {
+                           'tooluid': 1,
+                           'offset': 'Path',
+                           'type_item': 'Rough',
+                           'tool_type': 'C1',
+                           'data': {} # a dict to hold the parameters
+                           'gcode': "" # a string with the actual GCODE
+                           'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry 
+                           (cut or move)
+                           'solid_geometry': []
+                           },
+                           ...
+               }
+            It is populated in the GeometryObject.mtool_gen_cncjob()
+            BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ...
+        '''
+        self.cnc_tools = {}
+
+        '''
+           This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the 
+           diameter of the tools and the value is another dict that will hold the data under the following form:
+              {tooldia:   {
+                          'tool': int,
+                          'nr_drills': int,
+                          'nr_slots': int,
+                          'offset': float,
+                          'data': {} # a dict to hold the parameters
+                          'gcode': "" # a string with the actual GCODE
+                          'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry (cut or move)
+                          'solid_geometry': []
+                          },
+                          ...
+              }
+           It is populated in the ExcellonObject.on_create_cncjob_click() but actually 
+           it's done in camlib.CNCJob.generate_from_excellon_by_tool()
+           BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ...
+       '''
+        self.exc_cnc_tools = {}
+
+        # flag to store if the CNCJob is part of a special group of CNCJob objects that can't be processed by the
+        # default engine of FlatCAM. They generated by some of tools and are special cases of CNCJob objects.
+        self.special_group = None
+
+        # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool
+        # (like the one in the TCL Command), False
+        self.multitool = False
+
+        # determine if the GCode was generated out of a Excellon object or a Geometry object
+        self.origin_kind = None
+
+        # used for parsing the GCode lines to adjust the GCode when the GCode is offseted or scaled
+        gcodex_re_string = r'(?=.*(X[-\+]?\d*\.\d*))'
+        self.g_x_re = re.compile(gcodex_re_string)
+        gcodey_re_string = r'(?=.*(Y[-\+]?\d*\.\d*))'
+        self.g_y_re = re.compile(gcodey_re_string)
+        gcodez_re_string = r'(?=.*(Z[-\+]?\d*\.\d*))'
+        self.g_z_re = re.compile(gcodez_re_string)
+
+        gcodef_re_string = r'(?=.*(F[-\+]?\d*\.\d*))'
+        self.g_f_re = re.compile(gcodef_re_string)
+        gcodet_re_string = r'(?=.*(\=\s*[-\+]?\d*\.\d*))'
+        self.g_t_re = re.compile(gcodet_re_string)
+
+        gcodenr_re_string = r'([+-]?\d*\.\d+)'
+        self.g_nr_re = re.compile(gcodenr_re_string)
+
+        # Attributes to be included in serialization
+        # Always append to it because it carries contents
+        # from predecessors.
+        self.ser_attrs += ['options', 'kind', 'origin_kind', 'cnc_tools', 'exc_cnc_tools', 'multitool']
+
+        if self.app.is_legacy is False:
+            self.text_col = self.app.plotcanvas.new_text_collection()
+            self.text_col.enabled = True
+            self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
+
+        self.gcode_editor_tab = None
+
+        self.units_found = self.app.defaults['units']
+
+    def build_ui(self):
+        self.ui_disconnect()
+
+        FlatCAMObj.build_ui(self)
+        self.units = self.app.defaults['units'].upper()
+
+        # if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it
+        self.ui.cnc_tools_table.hide()
+        if self.cnc_tools:
+            self.ui.cnc_tools_table.show()
+            self.build_cnc_tools_table()
+
+        self.ui.exc_cnc_tools_table.hide()
+        if self.exc_cnc_tools:
+            self.ui.exc_cnc_tools_table.show()
+            self.build_excellon_cnc_tools()
+        #
+        self.ui_connect()
+
+    def build_cnc_tools_table(self):
+        tool_idx = 0
+
+        n = len(self.cnc_tools)
+        self.ui.cnc_tools_table.setRowCount(n)
+
+        for dia_key, dia_value in self.cnc_tools.items():
+
+            tool_idx += 1
+            row_no = tool_idx - 1
+
+            t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
+            # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+            self.ui.cnc_tools_table.setItem(row_no, 0, t_id)  # Tool name/id
+
+            # Make sure that the tool diameter when in MM is with no more than 2 decimals.
+            # There are no tool bits in MM with more than 2 decimals diameter.
+            # For INCH the decimals should be no more than 4. There are no tools under 10mils.
+
+            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['tooldia'])))
+
+            offset_txt = list(str(dia_value['offset']))
+            offset_txt[0] = offset_txt[0].upper()
+            offset_item = QtWidgets.QTableWidgetItem(''.join(offset_txt))
+            type_item = QtWidgets.QTableWidgetItem(str(dia_value['type']))
+            tool_type_item = QtWidgets.QTableWidgetItem(str(dia_value['tool_type']))
+
+            t_id.setFlags(QtCore.Qt.ItemIsEnabled)
+            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
+            offset_item.setFlags(QtCore.Qt.ItemIsEnabled)
+            type_item.setFlags(QtCore.Qt.ItemIsEnabled)
+            tool_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+            # hack so the checkbox stay centered in the table cell
+            # used this:
+            # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row
+            # plot_item = QtWidgets.QWidget()
+            # checkbox = FCCheckBox()
+            # checkbox.setCheckState(QtCore.Qt.Checked)
+            # qhboxlayout = QtWidgets.QHBoxLayout(plot_item)
+            # qhboxlayout.addWidget(checkbox)
+            # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
+            # qhboxlayout.setContentsMargins(0, 0, 0, 0)
+            plot_item = FCCheckBox()
+            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
+            tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_key))
+            if self.ui.plot_cb.isChecked():
+                plot_item.setChecked(True)
+
+            self.ui.cnc_tools_table.setItem(row_no, 1, dia_item)  # Diameter
+            self.ui.cnc_tools_table.setItem(row_no, 2, offset_item)  # Offset
+            self.ui.cnc_tools_table.setItem(row_no, 3, type_item)  # Toolpath Type
+            self.ui.cnc_tools_table.setItem(row_no, 4, tool_type_item)  # Tool Type
+
+            # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
+            self.ui.cnc_tools_table.setItem(row_no, 5, tool_uid_item)  # Tool unique ID)
+            self.ui.cnc_tools_table.setCellWidget(row_no, 6, plot_item)
+
+        # make the diameter column editable
+        # for row in range(tool_idx):
+        #     self.ui.cnc_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable |
+        #                                                   QtCore.Qt.ItemIsEnabled)
+
+        for row in range(tool_idx):
+            self.ui.cnc_tools_table.item(row, 0).setFlags(
+                self.ui.cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable)
+
+        self.ui.cnc_tools_table.resizeColumnsToContents()
+        self.ui.cnc_tools_table.resizeRowsToContents()
+
+        vertical_header = self.ui.cnc_tools_table.verticalHeader()
+        # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
+        vertical_header.hide()
+        self.ui.cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        horizontal_header = self.ui.cnc_tools_table.horizontalHeader()
+        horizontal_header.setMinimumSectionSize(10)
+        horizontal_header.setDefaultSectionSize(70)
+        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(0, 20)
+        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
+        horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(4, 40)
+        horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(4, 17)
+        # horizontal_header.setStretchLastSection(True)
+        self.ui.cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        self.ui.cnc_tools_table.setColumnWidth(0, 20)
+        self.ui.cnc_tools_table.setColumnWidth(4, 40)
+        self.ui.cnc_tools_table.setColumnWidth(6, 17)
+
+        # self.ui.geo_tools_table.setSortingEnabled(True)
+
+        self.ui.cnc_tools_table.setMinimumHeight(self.ui.cnc_tools_table.getHeight())
+        self.ui.cnc_tools_table.setMaximumHeight(self.ui.cnc_tools_table.getHeight())
+
+    def build_excellon_cnc_tools(self):
+        tool_idx = 0
+
+        n = len(self.exc_cnc_tools)
+        self.ui.exc_cnc_tools_table.setRowCount(n)
+
+        for tooldia_key, dia_value in self.exc_cnc_tools.items():
+
+            tool_idx += 1
+            row_no = tool_idx - 1
+
+            t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
+            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_z']) + self.z_cut))
+
+            t_id.setFlags(QtCore.Qt.ItemIsEnabled)
+            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
+            nr_drills_item.setFlags(QtCore.Qt.ItemIsEnabled)
+            nr_slots_item.setFlags(QtCore.Qt.ItemIsEnabled)
+            cutz_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+            # hack so the checkbox stay centered in the table cell
+            # used this:
+            # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row
+            # plot_item = QtWidgets.QWidget()
+            # checkbox = FCCheckBox()
+            # checkbox.setCheckState(QtCore.Qt.Checked)
+            # qhboxlayout = QtWidgets.QHBoxLayout(plot_item)
+            # qhboxlayout.addWidget(checkbox)
+            # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
+            # qhboxlayout.setContentsMargins(0, 0, 0, 0)
+
+            plot_item = FCCheckBox()
+            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
+            tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_value['tool']))
+            if self.ui.plot_cb.isChecked():
+                plot_item.setChecked(True)
+
+            # TODO until the feature of individual plot for an Excellon tool is implemented
+            plot_item.setDisabled(True)
+
+            self.ui.exc_cnc_tools_table.setItem(row_no, 0, t_id)  # Tool name/id
+            self.ui.exc_cnc_tools_table.setItem(row_no, 1, dia_item)  # Diameter
+            self.ui.exc_cnc_tools_table.setItem(row_no, 2, nr_drills_item)  # Nr of drills
+            self.ui.exc_cnc_tools_table.setItem(row_no, 3, nr_slots_item)  # Nr of slots
+
+            # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
+            self.ui.exc_cnc_tools_table.setItem(row_no, 4, tool_uid_item)  # Tool unique ID)
+            self.ui.exc_cnc_tools_table.setItem(row_no, 5, cutz_item)
+            self.ui.exc_cnc_tools_table.setCellWidget(row_no, 6, plot_item)
+
+        for row in range(tool_idx):
+            self.ui.exc_cnc_tools_table.item(row, 0).setFlags(
+                self.ui.exc_cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable)
+
+        self.ui.exc_cnc_tools_table.resizeColumnsToContents()
+        self.ui.exc_cnc_tools_table.resizeRowsToContents()
+
+        vertical_header = self.ui.exc_cnc_tools_table.verticalHeader()
+        vertical_header.hide()
+        self.ui.exc_cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        horizontal_header = self.ui.exc_cnc_tools_table.horizontalHeader()
+        horizontal_header.setMinimumSectionSize(10)
+        horizontal_header.setDefaultSectionSize(70)
+        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(0, 20)
+        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
+        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
+        horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.ResizeToContents)
+
+        horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
+
+        # horizontal_header.setStretchLastSection(True)
+        self.ui.exc_cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        self.ui.exc_cnc_tools_table.setColumnWidth(0, 20)
+        self.ui.exc_cnc_tools_table.setColumnWidth(6, 17)
+
+        self.ui.exc_cnc_tools_table.setMinimumHeight(self.ui.exc_cnc_tools_table.getHeight())
+        self.ui.exc_cnc_tools_table.setMaximumHeight(self.ui.exc_cnc_tools_table.getHeight())
+
+    def set_ui(self, ui):
+        FlatCAMObj.set_ui(self, ui)
+
+        log.debug("FlatCAMCNCJob.set_ui()")
+
+        assert isinstance(self.ui, CNCObjectUI), \
+            "Expected a CNCObjectUI, got %s" % type(self.ui)
+
+        self.units = self.app.defaults['units'].upper()
+        self.units_found = self.app.defaults['units']
+
+        # this signal has to be connected to it's slot before the defaults are populated
+        # the decision done in the slot has to override the default value set bellow
+        self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked)
+
+        self.form_fields.update({
+            "plot": self.ui.plot_cb,
+            "tooldia": self.ui.tooldia_entry,
+            "append": self.ui.append_text,
+            "prepend": self.ui.prepend_text,
+            "toolchange_macro": self.ui.toolchange_text,
+            "toolchange_macro_enable": self.ui.toolchange_cb
+        })
+
+        # Fill form fields only on object create
+        self.to_form()
+
+        # this means that the object that created this CNCJob was an Excellon or Geometry
+        try:
+            if self.travel_distance:
+                self.ui.t_distance_label.show()
+                self.ui.t_distance_entry.setVisible(True)
+                self.ui.t_distance_entry.setDisabled(True)
+                self.ui.t_distance_entry.set_value('%.*f' % (self.decimals, float(self.travel_distance)))
+                self.ui.units_label.setText(str(self.units).lower())
+                self.ui.units_label.setDisabled(True)
+
+                self.ui.t_time_label.show()
+                self.ui.t_time_entry.setVisible(True)
+                self.ui.t_time_entry.setDisabled(True)
+                # if time is more than 1 then we have minutes, else we have seconds
+                if self.routing_time > 1:
+                    self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(self.routing_time))))
+                    self.ui.units_time_label.setText('min')
+                else:
+                    time_r = self.routing_time * 60
+                    self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(time_r))))
+                    self.ui.units_time_label.setText('sec')
+                self.ui.units_time_label.setDisabled(True)
+        except AttributeError:
+            pass
+
+        if self.multitool is False:
+            self.ui.tooldia_entry.show()
+            self.ui.updateplot_button.show()
+        else:
+            self.ui.tooldia_entry.hide()
+            self.ui.updateplot_button.hide()
+
+        # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob
+        self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"])
+
+        try:
+            self.ui.annotation_cb.stateChanged.disconnect(self.on_annotation_change)
+        except (TypeError, AttributeError):
+            pass
+        self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change)
+
+        # set if to display text annotations
+        self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
+
+        # 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.cnc_frame.hide()
+        else:
+            self.ui.level.setText(_(
+                '<span style="color:red;"><b>Advanced</b></span>'
+            ))
+            self.ui.cnc_frame.show()
+
+        self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
+        self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
+        self.ui.modify_gcode_button.clicked.connect(self.on_edit_code_click)
+
+        self.ui.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters)
+
+        self.ui.cncplot_method_combo.activated_custom.connect(self.on_plot_kind_change)
+
+    def on_cnc_custom_parameters(self, signal_text):
+        if signal_text == 'Parameters':
+            return
+        else:
+            self.ui.toolchange_text.insertPlainText('%%%s%%' % signal_text)
+
+    def ui_connect(self):
+        for row in range(self.ui.cnc_tools_table.rowCount()):
+            self.ui.cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table)
+        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
+
+    def ui_disconnect(self):
+        for row in range(self.ui.cnc_tools_table.rowCount()):
+            self.ui.cnc_tools_table.cellWidget(row, 6).clicked.disconnect(self.on_plot_cb_click_table)
+        try:
+            self.ui.plot_cb.stateChanged.disconnect(self.on_plot_cb_click)
+        except (TypeError, AttributeError):
+            pass
+
+    def on_updateplot_button_click(self, *args):
+        """
+        Callback for the "Updata Plot" button. Reads the form for updates
+        and plots the object.
+        """
+        self.read_form()
+        self.on_plot_kind_change()
+
+    def on_plot_kind_change(self):
+        kind = self.ui.cncplot_method_combo.get_value()
+
+        def worker_task():
+            with self.app.proc_container.new(_("Plotting...")):
+                self.plot(kind=kind)
+
+        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
+
+    def on_exportgcode_button_click(self, *args):
+        """
+        Handler activated by a button clicked when exporting GCode.
+
+        :param args:
+        :return:
+        """
+        self.app.report_usage("cncjob_on_exportgcode_button")
+
+        self.read_form()
+        name = self.app.collection.get_active().options['name']
+        save_gcode = False
+
+        if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name:
+            _filter_ = "RML1 Files .rol (*.rol);;All Files (*.*)"
+        elif 'hpgl' in self.pp_geometry_name:
+            _filter_ = "HPGL Files .plt (*.plt);;All Files (*.*)"
+        else:
+            save_gcode = True
+            _filter_ = self.app.defaults['cncjob_save_filters']
+
+        try:
+            dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export Machine Code ..."),
+                directory=dir_file_to_save,
+                filter=_filter_
+            )
+        except TypeError:
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_)
+
+        filename = str(filename)
+
+        if filename == '':
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Machine Code cancelled ..."))
+            return
+        else:
+            if save_gcode is True:
+                used_extension = filename.rpartition('.')[2]
+                self.update_filters(last_ext=used_extension, filter_string='cncjob_save_filters')
+
+        new_name = os.path.split(str(filename))[1].rpartition('.')[0]
+        self.ui.name_entry.set_value(new_name)
+        self.on_name_activate(silent=True)
+
+        preamble = str(self.ui.prepend_text.get_value())
+        postamble = str(self.ui.append_text.get_value())
+
+        gc = self.export_gcode(filename, preamble=preamble, postamble=postamble)
+        if gc == 'fail':
+            return
+
+        if self.app.defaults["global_open_style"] is False:
+            self.app.file_opened.emit("gcode", filename)
+        self.app.file_saved.emit("gcode", filename)
+        self.app.inform.emit('[success] %s: %s' % (_("Machine Code file saved to"), filename))
+
+    def on_edit_code_click(self, *args):
+        """
+        Handler activated by a button clicked when editing GCode.
+
+        :param args:
+        :return:
+        """
+
+        self.app.proc_container.view.set_busy(_("Loading..."))
+
+        preamble = str(self.ui.prepend_text.get_value())
+        postamble = str(self.ui.append_text.get_value())
+
+        gco = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
+        if gco == 'fail':
+            return
+        else:
+            self.app.gcode_edited = gco
+
+        self.gcode_editor_tab = TextEditor(app=self.app, plain_text=True)
+
+        # add the tab if it was closed
+        self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Code Editor"))
+        self.gcode_editor_tab.setObjectName('code_editor_tab')
+
+        # delete the absolute and relative position and messages in the infobar
+        self.app.ui.position_label.setText("")
+        self.app.ui.rel_position_label.setText("")
+
+        # first clear previous text in text editor (if any)
+        self.gcode_editor_tab.code_editor.clear()
+        self.gcode_editor_tab.code_editor.setReadOnly(False)
+
+        self.gcode_editor_tab.code_editor.completer_enable = False
+        self.gcode_editor_tab.buttonRun.hide()
+
+        # Switch plot_area to CNCJob tab
+        self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab)
+
+        self.gcode_editor_tab.t_frame.hide()
+        # then append the text from GCode to the text editor
+        try:
+            self.gcode_editor_tab.code_editor.setPlainText(self.app.gcode_edited.getvalue())
+            # for line in self.app.gcode_edited:
+            #     QtWidgets.QApplication.processEvents()
+            #
+            #     proc_line = str(line).strip('\n')
+            #     self.gcode_editor_tab.code_editor.append(proc_line)
+        except Exception as e:
+            log.debug('FlatCAMCNNJob.on_edit_code_click() -->%s' % str(e))
+            self.app.inform.emit('[ERROR] %s %s' % ('FlatCAMCNNJob.on_edit_code_click() -->', str(e)))
+            return
+
+        self.gcode_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start)
+
+        self.gcode_editor_tab.handleTextChanged()
+        self.gcode_editor_tab.t_frame.show()
+        self.app.proc_container.view.set_idle()
+
+        self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor'))
+
+    def gcode_header(self, comment_start_symbol=None, comment_stop_symbol=None):
+        """
+        Will create a header to be added to all GCode files generated by FlatCAM
+
+        :param comment_start_symbol:    A symbol to be used as the first symbol in a comment
+        :param comment_stop_symbol:     A symbol to be used as the last symbol in a comment
+        :return:                        A string with a GCode header
+        """
+
+        log.debug("FlatCAMCNCJob.gcode_header()")
+        time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
+        marlin = False
+        hpgl = False
+        probe_pp = False
+
+        start_comment = comment_start_symbol if comment_start_symbol is not None else '('
+        stop_comment = comment_stop_symbol if comment_stop_symbol is not None else ')'
+
+        try:
+            for key in self.cnc_tools:
+                ppg = self.cnc_tools[key]['data']['ppname_g']
+                if 'marlin' in ppg.lower() or 'repetier' in ppg.lower():
+                    marlin = True
+                    break
+                if ppg == 'hpgl':
+                    hpgl = True
+                    break
+                if "toolchange_probe" in ppg.lower():
+                    probe_pp = True
+                    break
+        except KeyError:
+            # log.debug("FlatCAMCNCJob.gcode_header() error: --> %s" % str(e))
+            pass
+
+        try:
+            if 'marlin' in self.options['ppname_e'].lower() or 'repetier' in self.options['ppname_e'].lower():
+                marlin = True
+        except KeyError:
+            # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
+            pass
+
+        try:
+            if "toolchange_probe" in self.options['ppname_e'].lower():
+                probe_pp = True
+        except KeyError:
+            # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
+            pass
+
+        if marlin is True:
+            gcode = ';Marlin(Repetier) G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date:    %s\n' % \
+                    (str(self.app.version), str(self.app.version_date)) + '\n'
+
+            gcode += ';Name: ' + str(self.options['name']) + '\n'
+            gcode += ';Type: ' + "G-code from " + str(self.options['type']) + '\n'
+
+            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
+
+            gcode += ';Units: ' + self.units.upper() + '\n' + "\n"
+            gcode += ';Created on ' + time_str + '\n' + '\n'
+        elif hpgl is True:
+            gcode = 'CO "HPGL CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date:    %s' % \
+                    (str(self.app.version), str(self.app.version_date)) + '";\n'
+
+            gcode += 'CO "Name: ' + str(self.options['name']) + '";\n'
+            gcode += 'CO "Type: ' + "HPGL code from " + str(self.options['type']) + '";\n'
+
+            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
+
+            gcode += 'CO "Units: ' + self.units.upper() + '";\n'
+            gcode += 'CO "Created on ' + time_str + '";\n'
+        elif probe_pp is True:
+            gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
+                    (str(self.app.version), str(self.app.version_date)) + '\n'
+
+            gcode += '(This GCode tool change is done by using a Probe.)\n' \
+                     '(Make sure that before you start the job you first do a rough zero for Z axis.)\n' \
+                     '(This means that you need to zero the CNC axis and then jog to the toolchange X, Y location,)\n' \
+                     '(mount the probe and adjust the Z so more or less the probe tip touch the plate. ' \
+                     'Then zero the Z axis.)\n' + '\n'
+
+            gcode += '(Name: ' + str(self.options['name']) + ')\n'
+            gcode += '(Type: ' + "G-code from " + str(self.options['type']) + ')\n'
+
+            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
+
+            gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
+            gcode += '(Created on ' + time_str + ')\n' + '\n'
+        else:
+            gcode = '%sG-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s%s\n' % \
+                    (start_comment, str(self.app.version), str(self.app.version_date), stop_comment) + '\n'
+
+            gcode += '%sName: ' % start_comment + str(self.options['name']) + '%s\n' % stop_comment
+            gcode += '%sType: ' % start_comment + "G-code from " + str(self.options['type']) + '%s\n' % stop_comment
+
+            # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
+
+            gcode += '%sUnits: ' % start_comment + self.units.upper() + '%s\n' % stop_comment + "\n"
+            gcode += '%sCreated on ' % start_comment + time_str + '%s\n' % stop_comment + '\n'
+
+        return gcode
+
+    @staticmethod
+    def gcode_footer(end_command=None):
+        """
+        Will add the M02 to the end of GCode, if requested.
+
+        :param end_command: 'M02' or 'M30' - String
+        :return:
+        """
+        if end_command:
+            return end_command
+        else:
+            return 'M02'
+
+    def export_gcode(self, filename=None, preamble='', postamble='', to_file=False):
+        """
+        This will save the GCode from the Gcode object to a file on the OS filesystem
+
+        :param filename:    filename for the GCode file
+        :param preamble:    a custom Gcode block to be added at the beginning of the Gcode file
+        :param postamble:   a custom Gcode block to be added at the end of the Gcode file
+        :param to_file:     if False then no actual file is saved but the app will know that a file was created
+        :return:            None
+        """
+        # gcode = ''
+        # roland = False
+        # hpgl = False
+        # isel_icp = False
+
+        include_header = True
+
+        try:
+            if self.special_group:
+                self.app.inform.emit('[WARNING_NOTCL] %s %s %s.' %
+                                     (_("This CNCJob object can't be processed because it is a"),
+                                      str(self.special_group),
+                                      _("CNCJob object")))
+                return 'fail'
+        except AttributeError:
+            pass
+
+        # if this dict is not empty then the object is a Geometry object
+        if self.cnc_tools:
+            first_key = next(iter(self.cnc_tools))
+            include_header = self.app.preprocessors[self.cnc_tools[first_key]['data']['ppname_g']].include_header
+
+        # if this dict is not empty then the object is an Excellon object
+        if self.exc_cnc_tools:
+            first_key = next(iter(self.exc_cnc_tools))
+            include_header = self.app.preprocessors[self.exc_cnc_tools[first_key]['data']['ppname_e']].include_header
+
+        # # detect if using Roland preprocessor
+        # try:
+        #     for key in self.cnc_tools:
+        #         if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20':
+        #             roland = True
+        #             break
+        # except Exception:
+        #     try:
+        #         for key in self.cnc_tools:
+        #             if self.cnc_tools[key]['data']['ppname_e'] == 'Roland_MDX_20':
+        #                 roland = True
+        #                 break
+        #     except Exception:
+        #         pass
+        #
+        # # detect if using HPGL preprocessor
+        # try:
+        #     for key in self.cnc_tools:
+        #         if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl':
+        #             hpgl = True
+        #             break
+        # except Exception:
+        #     try:
+        #         for key in self.cnc_tools:
+        #             if self.cnc_tools[key]['data']['ppname_e'] == 'hpgl':
+        #                 hpgl = True
+        #                 break
+        #     except Exception:
+        #         pass
+        #
+        # # detect if using ISEL_ICP_CNC preprocessor
+        # try:
+        #     for key in self.cnc_tools:
+        #         if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_g'].upper():
+        #             isel_icp = True
+        #             break
+        # except Exception:
+        #     try:
+        #         for key in self.cnc_tools:
+        #             if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_e'].upper():
+        #                 isel_icp = True
+        #                 break
+        #     except Exception:
+        #         pass
+
+        # do not add gcode_header when using the Roland preprocessor, add it for every other preprocessor
+        # if roland is False and hpgl is False and isel_icp is False:
+        #     gcode = self.gcode_header()
+
+        # do not add gcode_header when using the Roland, HPGL or ISEP_ICP_CNC preprocessor (or any other preprocessor
+        # that has the include_header attribute set as False, add it for every other preprocessor
+        # if include_header:
+        #     gcode = self.gcode_header()
+        # else:
+        #     gcode = ''
+
+        # # 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:
+        #         for key, value in self.cnc_tools[tooluid_key].items():
+        #             if key == 'gcode':
+        #                 gcode += value
+        #                 break
+        # else:
+        #     gcode += self.gcode
+
+        # if roland is True:
+        #     g = preamble + gcode + postamble
+        # elif hpgl is True:
+        #     g = self.gcode_header() + preamble + gcode + postamble
+        # else:
+        #     # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
+        #     g_idx = gcode.rfind('G20')
+        #
+        #     # if it did not find 'G20' then search for 'G21'
+        #     if g_idx == -1:
+        #         g_idx = gcode.rfind('G21')
+        #
+        #     # if it did not find 'G20' and it did not find 'G21' then there is an error and return
+        #     # but only when the preprocessor is not ISEL_ICP who is allowed not to have the G20/G21 command
+        #     if g_idx == -1 and isel_icp is False:
+        #         self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
+        #         return
+        #
+        #     footer = self.app.defaults['cncjob_footer']
+        #     end_gcode = self.gcode_footer() if footer is True else ''
+        #     g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + end_gcode
+
+        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:
+                    for key, value in self.cnc_tools[tooluid_key].items():
+                        if key == 'gcode':
+                            gcode += value
+                            break
+            else:
+                gcode += self.gcode
+
+            g = g + gcode + 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
+            # g_idx = gcode.rfind('G20')
+            #
+            # # if it did not find 'G20' then search for 'G21'
+            # if g_idx == -1:
+            #     g_idx = gcode.rfind('G21')
+            #
+            # # if it did not find 'G20' and it did not find 'G21' then there is an error and return
+            # if g_idx == -1:
+            #     self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
+            #     return
+
+            # 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:
+                    for key, value in self.cnc_tools[tooluid_key].items():
+                        if key == 'gcode':
+                            gcode += value
+                            break
+            else:
+                gcode += self.gcode
+
+            end_gcode = self.gcode_footer() if self.app.defaults['cncjob_footer'] is True else ''
+
+            # detect if using a HPGL preprocessor
+            hpgl = False
+            if self.cnc_tools:
+                for key in self.cnc_tools:
+                    if 'ppname_g' in self.cnc_tools[key]['data']:
+                        if 'hpgl' in self.cnc_tools[key]['data']['ppname_g']:
+                            hpgl = True
+                            break
+            elif self.exc_cnc_tools:
+                for key in self.cnc_tools:
+                    if 'ppname_e' in self.cnc_tools[key]['data']:
+                        if 'hpgl' in self.cnc_tools[key]['data']['ppname_e']:
+                            hpgl = True
+                            break
+
+            if hpgl:
+                processed_gcode = ''
+                pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$")
+                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
+                    else:
+                        processed_gcode += gline + '\n'
+
+                gcode = processed_gcode
+                g = self.gcode_header() + '\n' + preamble + '\n' + gcode + postamble + end_gcode
+            else:
+                try:
+                    g_idx = gcode.index('G94')
+                    g = self.gcode_header() + gcode[:g_idx + 3] + '\n\n' + preamble + '\n' + \
+                        gcode[(g_idx + 3):] + postamble + end_gcode
+                except ValueError:
+                    self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                         _("G-code does not have a G94 code and we will not include the code in the "
+                                           "'Prepend to GCode' text box"))
+                    g = self.gcode_header() + '\n' + gcode + postamble + 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:
+            # match = self.re_toolchange.search(g)
+            if 'M6' in g:
+                m6_code = self.parse_custom_toolchange_code(self.ui.toolchange_text.get_value())
+                if m6_code is None or m6_code == '':
+                    self.app.inform.emit(
+                        '[ERROR_NOTCL] %s' % _("Cancelled. The Toolchange Custom code is enabled but it's empty.")
+                    )
+                    return 'fail'
+
+                g = g.replace('M6', m6_code)
+                self.app.inform.emit('[success] %s' % _("Toolchange G-code was replaced by a custom code."))
+
+        lines = StringIO(g)
+
+        # Write
+        if filename is not None:
+            try:
+                force_windows_line_endings = self.app.defaults['cncjob_line_ending']
+                if force_windows_line_endings and sys.platform != 'win32':
+                    with open(filename, 'w', newline='\r\n') as f:
+                        for line in lines:
+                            f.write(line)
+                else:
+                    with open(filename, 'w') as f:
+                        for line in lines:
+                            f.write(line)
+            except FileNotFoundError:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory"))
+                return
+            except PermissionError:
+                self.app.inform.emit(
+                    '[WARNING] %s' % _("Permission denied, saving not possible.\n"
+                                       "Most likely another app is holding the file open and not accessible.")
+                )
+                return 'fail'
+        elif to_file is False:
+            # Just for adding it to the recent files list.
+            if self.app.defaults["global_open_style"] is False:
+                self.app.file_opened.emit("cncjob", filename)
+            self.app.file_saved.emit("cncjob", filename)
+
+            self.app.inform.emit('[success] %s: %s' % (_("Saved to"), filename))
+        else:
+            return lines
+
+    def on_toolchange_custom_clicked(self, signal):
+        """
+        Handler for clicking toolchange custom.
+
+        :param signal:
+        :return:
+        """
+
+        try:
+            if 'toolchange_custom' not in str(self.options['ppname_e']).lower():
+                if self.ui.toolchange_cb.get_value():
+                    self.ui.toolchange_cb.set_value(False)
+                    self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                         _("The used preprocessor file has to have in it's name: 'toolchange_custom'"))
+        except KeyError:
+            try:
+                for key in self.cnc_tools:
+                    ppg = self.cnc_tools[key]['data']['ppname_g']
+                    if 'toolchange_custom' not in str(ppg).lower():
+                        print(ppg)
+                        if self.ui.toolchange_cb.get_value():
+                            self.ui.toolchange_cb.set_value(False)
+                            self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                                 _("The used preprocessor file has to have in it's name: "
+                                                   "'toolchange_custom'"))
+            except KeyError:
+                self.app.inform.emit('[ERROR] %s' % _("There is no preprocessor file."))
+
+    def get_gcode(self, preamble='', postamble=''):
+        """
+        We need this to be able to get_gcode separately for shell command export_gcode
+
+        :param preamble:    Extra GCode added to the beginning of the GCode
+        :param postamble:   Extra GCode added at the end of the GCode
+        :return:            The modified GCode
+        """
+        return preamble + '\n' + self.gcode + "\n" + postamble
+
+    def get_svg(self):
+        # we need this to be able get_svg separately for shell command export_svg
+        pass
+
+    def on_plot_cb_click(self, *args):
+        """
+        Handler for clicking on the Plot checkbox.
+
+        :param args:
+        :return:
+        """
+        if self.muted_ui:
+            return
+        kind = self.ui.cncplot_method_combo.get_value()
+        self.plot(kind=kind)
+        self.read_form_item('plot')
+
+        self.ui_disconnect()
+        cb_flag = self.ui.plot_cb.isChecked()
+        for row in range(self.ui.cnc_tools_table.rowCount()):
+            table_cb = self.ui.cnc_tools_table.cellWidget(row, 6)
+            if cb_flag:
+                table_cb.setChecked(True)
+            else:
+                table_cb.setChecked(False)
+        self.ui_connect()
+
+    def on_plot_cb_click_table(self):
+        """
+        Handler for clicking the plot checkboxes added into a Table on each row. Purpose: toggle visibility for the
+        tool/aperture found on that row.
+        :return:
+        """
+
+        # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked)
+        self.ui_disconnect()
+        # cw = self.sender()
+        # cw_index = self.ui.cnc_tools_table.indexAt(cw.pos())
+        # cw_row = cw_index.row()
+
+        kind = self.ui.cncplot_method_combo.get_value()
+
+        self.shapes.clear(update=True)
+
+        for tooluid_key in self.cnc_tools:
+            tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
+            gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
+            # tool_uid = int(self.ui.cnc_tools_table.item(cw_row, 3).text())
+
+            for r in range(self.ui.cnc_tools_table.rowCount()):
+                if int(self.ui.cnc_tools_table.item(r, 5).text()) == int(tooluid_key):
+                    if self.ui.cnc_tools_table.cellWidget(r, 6).isChecked():
+                        self.plot2(tooldia=tooldia, obj=self, visible=True, gcode_parsed=gcode_parsed, kind=kind)
+
+        self.shapes.redraw()
+
+        # make sure that the general plot is disabled if one of the row plot's are disabled and
+        # if all the row plot's are enabled also enable the general plot checkbox
+        cb_cnt = 0
+        total_row = self.ui.cnc_tools_table.rowCount()
+        for row in range(total_row):
+            if self.ui.cnc_tools_table.cellWidget(row, 6).isChecked():
+                cb_cnt += 1
+            else:
+                cb_cnt -= 1
+        if cb_cnt < total_row:
+            self.ui.plot_cb.setChecked(False)
+        else:
+            self.ui.plot_cb.setChecked(True)
+        self.ui_connect()
+
+    def plot(self, visible=None, kind='all'):
+        """
+        # Does all the required setup and returns False
+        # if the 'ptint' option is set to False.
+
+        :param visible: Boolean to decide if the object will be plotted as visible or disabled on canvas
+        :param kind:    String. Can be "all" or "travel" or "cut". For CNCJob plotting
+        :return:        None
+        """
+        if not FlatCAMObj.plot(self):
+            return
+
+        visible = visible if visible else self.options['plot']
+
+        if self.app.is_legacy is False:
+            if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
+                self.text_col.enabled = True
+            else:
+                self.text_col.enabled = False
+            self.annotation.redraw()
+
+        try:
+            if self.multitool is False:  # single tool usage
+                try:
+                    dia_plot = float(self.options["tooldia"])
+                except ValueError:
+                    # we may have a tuple with only one element and a comma
+                    dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0]
+                self.plot2(dia_plot, obj=self, visible=visible, kind=kind)
+            else:
+                # multiple tools usage
+                if self.cnc_tools:
+                    for tooluid_key in self.cnc_tools:
+                        tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
+                        gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
+                        self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
+
+                # TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed
+                if self.exc_cnc_tools:
+                    for tooldia_key in self.exc_cnc_tools:
+                        tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
+                        # gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed']
+                        gcode_parsed = self.gcode_parsed
+                        self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
+
+            self.shapes.redraw()
+        except (ObjectDeleted, AttributeError):
+            self.shapes.clear(update=True)
+            if self.app.is_legacy is False:
+                self.annotation.clear(update=True)
+
+    def on_annotation_change(self):
+        """
+        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()
+        else:
+            kind = self.ui.cncplot_method_combo.get_value()
+            self.plot(kind=kind)
+
+    def convert_units(self, units):
+        """
+        Units conversion used by the CNCJob objects.
+
+        :param units:   Can be "MM" or "IN"
+        :return:
+        """
+
+        log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()")
+
+        factor = CNCjob.convert_units(self, units)
+        self.options["tooldia"] = float(self.options["tooldia"]) * factor
+
+        param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
+                      'endz', 'toolchangez']
+
+        temp_tools_dict = {}
+        tool_dia_copy = {}
+        data_copy = {}
+
+        for tooluid_key, tooluid_value in self.cnc_tools.items():
+            for dia_key, dia_value in tooluid_value.items():
+                if dia_key == 'tooldia':
+                    dia_value *= factor
+                    dia_value = float('%.*f' % (self.decimals, dia_value))
+                    tool_dia_copy[dia_key] = dia_value
+                if dia_key == 'offset':
+                    tool_dia_copy[dia_key] = dia_value
+                if dia_key == 'offset_value':
+                    dia_value *= factor
+                    tool_dia_copy[dia_key] = dia_value
+
+                if dia_key == 'type':
+                    tool_dia_copy[dia_key] = dia_value
+                if dia_key == 'tool_type':
+                    tool_dia_copy[dia_key] = dia_value
+                if dia_key == 'data':
+                    for data_key, data_value in dia_value.items():
+                        # convert the form fields that are convertible
+                        for param in param_list:
+                            if data_key == param and data_value is not None:
+                                data_copy[data_key] = data_value * factor
+                        # copy the other dict entries that are not convertible
+                        if data_key not in param_list:
+                            data_copy[data_key] = data_value
+                    tool_dia_copy[dia_key] = deepcopy(data_copy)
+                    data_copy.clear()
+
+                if dia_key == 'gcode':
+                    tool_dia_copy[dia_key] = dia_value
+                if dia_key == 'gcode_parsed':
+                    tool_dia_copy[dia_key] = dia_value
+                if dia_key == 'solid_geometry':
+                    tool_dia_copy[dia_key] = dia_value
+
+                # if dia_key == 'solid_geometry':
+                #     tool_dia_copy[dia_key] = affinity.scale(dia_value, xfact=factor, origin=(0, 0))
+                # if dia_key == 'gcode_parsed':
+                #     for g in dia_value:
+                #         g['geom'] = affinity.scale(g['geom'], factor, factor, origin=(0, 0))
+                #
+                #     tool_dia_copy['gcode_parsed'] = deepcopy(dia_value)
+                #     tool_dia_copy['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_value])
+
+            temp_tools_dict.update({
+                tooluid_key: deepcopy(tool_dia_copy)
+            })
+            tool_dia_copy.clear()
+
+        self.cnc_tools.clear()
+        self.cnc_tools = deepcopy(temp_tools_dict)

+ 314 - 0
flatcamObjects/FlatCAMDocument.py

@@ -0,0 +1,314 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# ##########################################################
+
+# ##########################################################
+# File modified by: Marius Stanciu                         #
+# ##########################################################
+
+from flatcamEditors.FlatCAMTextEditor import TextEditor
+from flatcamObjects.FlatCAMObj import *
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class DocumentObject(FlatCAMObj):
+    """
+    Represents a Document object.
+    """
+    optionChanged = QtCore.pyqtSignal(str)
+    ui_type = DocumentObjectUI
+
+    def __init__(self, name):
+        self.decimals = self.app.decimals
+
+        log.debug("Creating a Document object...")
+        FlatCAMObj.__init__(self, name)
+
+        self.kind = "document"
+        self.units = ''
+
+        self.ser_attrs = ['options', 'kind', 'source_file']
+        self.source_file = ''
+        self.doc_code = ''
+
+        self.font_name = None
+        self.font_italic = None
+        self.font_bold = None
+        self.font_underline = None
+
+        self.document_editor_tab = None
+
+        self._read_only = False
+        self.units_found = self.app.defaults['units']
+
+    def set_ui(self, ui):
+        FlatCAMObj.set_ui(self, ui)
+        log.debug("DocumentObject.set_ui()")
+
+        assert isinstance(self.ui, DocumentObjectUI), \
+            "Expected a DocumentObjectUI, got %s" % type(self.ui)
+
+        self.units = self.app.defaults['units'].upper()
+        self.units_found = self.app.defaults['units']
+
+        # Fill form fields only on object create
+        self.to_form()
+
+        # Show/Hide Advanced Options
+        if self.app.defaults["global_app_level"] == 'b':
+            self.ui.level.setText(_(
+                '<span style="color:green;"><b>Basic</b></span>'
+            ))
+        else:
+            self.ui.level.setText(_(
+                '<span style="color:red;"><b>Advanced</b></span>'
+            ))
+
+        self.document_editor_tab = TextEditor(app=self.app)
+        stylesheet = """
+                        QTextEdit {selection-background-color:%s;
+                                   selection-color:white;
+                        }
+                     """ % self.app.defaults["document_sel_color"]
+
+        self.document_editor_tab.code_editor.setStyleSheet(stylesheet)
+
+        # first clear previous text in text editor (if any)
+        self.document_editor_tab.code_editor.clear()
+        self.document_editor_tab.code_editor.setReadOnly(self._read_only)
+
+        self.document_editor_tab.buttonRun.hide()
+
+        self.ui.autocomplete_cb.set_value(self.app.defaults['document_autocompleter'])
+        self.on_autocomplete_changed(state=self.app.defaults['document_autocompleter'])
+        self.on_tab_size_change(val=self.app.defaults['document_tab_size'])
+
+        flt = "FlatCAM Docs (*.FlatDoc);;All Files (*.*)"
+
+        # ######################################################################
+        # ######################## SIGNALS #####################################
+        # ######################################################################
+        self.document_editor_tab.buttonOpen.clicked.disconnect()
+        self.document_editor_tab.buttonOpen.clicked.connect(lambda: self.document_editor_tab.handleOpen(filt=flt))
+        self.document_editor_tab.buttonSave.clicked.disconnect()
+        self.document_editor_tab.buttonSave.clicked.connect(lambda: self.document_editor_tab.handleSaveGCode(filt=flt))
+
+        self.document_editor_tab.code_editor.textChanged.connect(self.on_text_changed)
+
+        self.ui.font_type_cb.currentFontChanged.connect(self.font_family)
+        self.ui.font_size_cb.activated.connect(self.font_size)
+        self.ui.font_bold_tb.clicked.connect(self.on_bold_button)
+        self.ui.font_italic_tb.clicked.connect(self.on_italic_button)
+        self.ui.font_under_tb.clicked.connect(self.on_underline_button)
+
+        self.ui.font_color_entry.editingFinished.connect(self.on_font_color_entry)
+        self.ui.font_color_button.clicked.connect(self.on_font_color_button)
+        self.ui.sel_color_entry.editingFinished.connect(self.on_selection_color_entry)
+        self.ui.sel_color_button.clicked.connect(self.on_selection_color_button)
+
+        self.ui.al_left_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignLeft))
+        self.ui.al_center_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignCenter))
+        self.ui.al_right_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignRight))
+        self.ui.al_justify_tb.clicked.connect(
+            lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignJustify)
+        )
+
+        self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
+        self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change)
+        # #######################################################################
+
+        self.ui.font_color_entry.set_value(self.app.defaults['document_font_color'])
+        self.ui.font_color_button.setStyleSheet(
+            "background-color:%s" % str(self.app.defaults['document_font_color']))
+
+        self.ui.sel_color_entry.set_value(self.app.defaults['document_sel_color'])
+        self.ui.sel_color_button.setStyleSheet(
+            "background-color:%s" % self.app.defaults['document_sel_color'])
+
+        self.ui.font_size_cb.setCurrentIndex(int(self.app.defaults['document_font_size']))
+
+        self.document_editor_tab.handleTextChanged()
+        self.ser_attrs = ['options', 'kind', 'source_file']
+
+        if Qt.mightBeRichText(self.source_file):
+            self.document_editor_tab.code_editor.setHtml(self.source_file)
+        else:
+            for line in self.source_file.splitlines():
+                self.document_editor_tab.code_editor.append(line)
+
+        self.build_ui()
+
+    @property
+    def read_only(self):
+        return self._read_only
+
+    @read_only.setter
+    def read_only(self, val):
+        if val:
+            self._read_only = True
+        else:
+            self._read_only = False
+
+    def build_ui(self):
+        FlatCAMObj.build_ui(self)
+        tab_here = False
+
+        # try to not add too many times a tab that it is already installed
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
+                tab_here = True
+                break
+
+        # add the tab if it is not already added
+        if tab_here is False:
+            self.app.ui.plot_tab_area.addTab(self.document_editor_tab, '%s' % _("Document Editor"))
+            self.document_editor_tab.setObjectName(self.options['name'])
+
+        # Switch plot_area to CNCJob tab
+        self.app.ui.plot_tab_area.setCurrentWidget(self.document_editor_tab)
+
+    def on_autocomplete_changed(self, state):
+        if state:
+            self.document_editor_tab.code_editor.completer_enable = True
+        else:
+            self.document_editor_tab.code_editor.completer_enable = False
+
+    def on_tab_size_change(self, val=None):
+        try:
+            self.ui.tab_size_spinner.returnPressed.disconnect(self.on_tab_size_change)
+        except TypeError:
+            pass
+
+        if val:
+            self.ui.tab_size_spinner.set_value(val)
+
+        tab_balue = int(self.ui.tab_size_spinner.get_value())
+        self.document_editor_tab.code_editor.setTabStopWidth(tab_balue)
+        self.app.defaults['document_tab_size'] = tab_balue
+
+        self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change)
+
+    def on_text_changed(self):
+        self.source_file = self.document_editor_tab.code_editor.toHtml()
+        # print(self.source_file)
+
+    def font_family(self, font):
+        # self.document_editor_tab.code_editor.selectAll()
+        font.setPointSize(float(self.ui.font_size_cb.get_value()))
+        self.document_editor_tab.code_editor.setCurrentFont(font)
+        self.font_name = self.ui.font_type_cb.currentFont().family()
+
+    def font_size(self):
+        # self.document_editor_tab.code_editor.selectAll()
+        self.document_editor_tab.code_editor.setFontPointSize(float(self.ui.font_size_cb.get_value()))
+
+    def on_bold_button(self):
+        if self.ui.font_bold_tb.isChecked():
+            self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Bold)
+            self.font_bold = True
+        else:
+            self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Normal)
+            self.font_bold = False
+
+    def on_italic_button(self):
+        if self.ui.font_italic_tb.isChecked():
+            self.document_editor_tab.code_editor.setFontItalic(True)
+            self.font_italic = True
+        else:
+            self.document_editor_tab.code_editor.setFontItalic(False)
+            self.font_italic = False
+
+    def on_underline_button(self):
+        if self.ui.font_under_tb.isChecked():
+            self.document_editor_tab.code_editor.setFontUnderline(True)
+            self.font_underline = True
+        else:
+            self.document_editor_tab.code_editor.setFontUnderline(False)
+            self.font_underline = False
+
+    # Setting font colors handlers
+    def on_font_color_entry(self):
+        self.app.defaults['document_font_color'] = self.ui.font_color_entry.get_value()
+        self.ui.font_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_font_color']))
+
+    def on_font_color_button(self):
+        current_color = QtGui.QColor(self.app.defaults['document_font_color'])
+
+        c_dialog = QtWidgets.QColorDialog()
+        font_color = c_dialog.getColor(initial=current_color)
+
+        if font_color.isValid() is False:
+            return
+
+        self.document_editor_tab.code_editor.setTextColor(font_color)
+        self.ui.font_color_button.setStyleSheet("background-color:%s" % str(font_color.name()))
+
+        new_val = str(font_color.name())
+        self.ui.font_color_entry.set_value(new_val)
+        self.app.defaults['document_font_color'] = new_val
+
+    # Setting selection colors handlers
+    def on_selection_color_entry(self):
+        self.app.defaults['document_sel_color'] = self.ui.sel_color_entry.get_value()
+        self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_sel_color']))
+
+    def on_selection_color_button(self):
+        current_color = QtGui.QColor(self.app.defaults['document_sel_color'])
+
+        c_dialog = QtWidgets.QColorDialog()
+        sel_color = c_dialog.getColor(initial=current_color)
+
+        if sel_color.isValid() is False:
+            return
+
+        p = QtGui.QPalette()
+        p.setColor(QtGui.QPalette.Highlight, sel_color)
+        p.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor('white'))
+
+        self.document_editor_tab.code_editor.setPalette(p)
+
+        self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(sel_color.name()))
+
+        new_val = str(sel_color.name())
+        self.ui.sel_color_entry.set_value(new_val)
+        self.app.defaults['document_sel_color'] = new_val
+
+    def to_dict(self):
+        """
+        Returns a representation of the object as a dictionary.
+        Attributes to include are listed in ``self.ser_attrs``.
+
+        :return: A dictionary-encoded copy of the object.
+        :rtype: dict
+        """
+        d = {}
+        for attr in self.ser_attrs:
+            d[attr] = getattr(self, attr)
+        return d
+
+    def from_dict(self, d):
+        """
+        Sets object's attributes from a dictionary.
+        Attributes to include are listed in ``self.ser_attrs``.
+        This method will look only for only and all the
+        attributes in ``self.ser_attrs``. They must all
+        be present. Use only for deserializing saved
+        objects.
+
+        :param d: Dictionary of attributes to set in the object.
+        :type d: dict
+        :return: None
+        """
+        for attr in self.ser_attrs:
+            setattr(self, attr, d[attr])

+ 1622 - 0
flatcamObjects/FlatCAMExcellon.py

@@ -0,0 +1,1622 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# ##########################################################
+
+# ##########################################################
+# File modified by: Marius Stanciu                         #
+# ##########################################################
+
+
+from shapely.geometry import Point, LineString
+
+from copy import deepcopy
+
+from flatcamParsers.ParseExcellon import Excellon
+from flatcamObjects.FlatCAMObj import *
+
+import itertools
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class ExcellonObject(FlatCAMObj, Excellon):
+    """
+    Represents Excellon/Drill code.
+    """
+
+    ui_type = ExcellonObjectUI
+    optionChanged = QtCore.pyqtSignal(str)
+
+    def __init__(self, name):
+        self.decimals = self.app.decimals
+
+        self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
+
+        Excellon.__init__(self, geo_steps_per_circle=self.circle_steps)
+        FlatCAMObj.__init__(self, name)
+
+        self.kind = "excellon"
+
+        self.options.update({
+            "plot": True,
+            "solid": False,
+
+            "operation": "drill",
+            "milling_type": "drills",
+
+            "milling_dia": 0.04,
+
+            "cutz": -0.1,
+            "multidepth": False,
+            "depthperpass": 0.7,
+            "travelz": 0.1,
+            "feedrate": self.app.defaults["geometry_feedrate"],
+            "feedrate_z": 5.0,
+            "feedrate_rapid": 5.0,
+            "tooldia": 0.1,
+            "slot_tooldia": 0.1,
+            "toolchange": False,
+            "toolchangez": 1.0,
+            "toolchangexy": "0.0, 0.0",
+            "extracut": self.app.defaults["geometry_extracut"],
+            "extracut_length": self.app.defaults["geometry_extracut_length"],
+            "endz": 2.0,
+            "endxy": '',
+
+            "startz": None,
+            "offset": 0.0,
+            "spindlespeed": 0,
+            "dwell": True,
+            "dwelltime": 1000,
+            "ppname_e": 'default',
+            "ppname_g": self.app.defaults["geometry_ppname_g"],
+            "z_pdepth": -0.02,
+            "feedrate_probe": 3.0,
+            "optimization_type": "B",
+        })
+
+        # TODO: Document this.
+        self.tool_cbs = {}
+
+        # dict that holds the object names and the option name
+        # the key is the object name (defines in ObjectUI) for each UI element that is a parameter
+        # particular for a tool and the value is the actual name of the option that the UI element is changing
+        self.name2option = {}
+
+        # default set of data to be added to each tool in self.tools as self.tools[tool]['data'] = self.default_data
+        self.default_data = {}
+
+        # fill in self.default_data values from self.options
+        for opt_key, opt_val in self.app.options.items():
+            if opt_key.find('excellon_') == 0:
+                self.default_data[opt_key] = deepcopy(opt_val)
+        for opt_key, opt_val in self.app.options.items():
+            if opt_key.find('geometry_') == 0:
+                self.default_data[opt_key] = deepcopy(opt_val)
+
+        # variable to store the total amount of drills per job
+        self.tot_drill_cnt = 0
+        self.tool_row = 0
+
+        # variable to store the total amount of slots per job
+        self.tot_slot_cnt = 0
+        self.tool_row_slots = 0
+
+        # variable to store the distance travelled
+        self.travel_distance = 0.0
+
+        # store the source file here
+        self.source_file = ""
+
+        self.multigeo = False
+        self.units_found = self.app.defaults['units']
+
+        self.fill_color = self.app.defaults['excellon_plot_fill']
+        self.outline_color = self.app.defaults['excellon_plot_line']
+        self.alpha_level = 'bf'
+
+        # Attributes to be included in serialization
+        # Always append to it because it carries contents
+        # from predecessors.
+        self.ser_attrs += ['options', 'kind']
+
+    def merge(self, exc_list, exc_final):
+        """
+        Merge Excellon objects found in exc_list parameter into exc_final object.
+        Options are always copied from source .
+
+        Tools are disregarded, what is taken in consideration is the unique drill diameters found as values in the
+        exc_list tools dict's. In the reconstruction section for each unique tool diameter it will be created a
+        tool_name to be used in the final Excellon object, exc_final.
+
+        If only one object is in exc_list parameter then this function will copy that object in the exc_final
+
+        :param exc_list: List or one object of ExcellonObject Objects to join.
+        :param exc_final: Destination ExcellonObject object.
+        :return: None
+        """
+
+        try:
+            decimals_exc = self.decimals
+        except AttributeError:
+            decimals_exc = 4
+
+        # flag to signal that we need to reorder the tools dictionary and drills and slots lists
+        flag_order = False
+
+        try:
+            flattened_list = list(itertools.chain(*exc_list))
+        except TypeError:
+            flattened_list = exc_list
+
+        # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
+        # values will be list of Shapely Points; for drills
+        custom_dict_drills = {}
+
+        # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
+        # values will be list of Shapely Points; for slots
+        custom_dict_slots = {}
+
+        for exc in flattened_list:
+            # copy options of the current excellon obj to the final excellon obj
+            for option in exc.options:
+                if option != 'name':
+                    try:
+                        exc_final.options[option] = exc.options[option]
+                    except Exception:
+                        exc.app.log.warning("Failed to copy option.", option)
+
+            for drill in exc.drills:
+                exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[drill['tool']]['C']))
+
+                if exc_tool_dia not in custom_dict_drills:
+                    custom_dict_drills[exc_tool_dia] = [drill['point']]
+                else:
+                    custom_dict_drills[exc_tool_dia].append(drill['point'])
+
+            for slot in exc.slots:
+                exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[slot['tool']]['C']))
+
+                if exc_tool_dia not in custom_dict_slots:
+                    custom_dict_slots[exc_tool_dia] = [[slot['start'], slot['stop']]]
+                else:
+                    custom_dict_slots[exc_tool_dia].append([slot['start'], slot['stop']])
+
+            # add the zeros and units to the exc_final object
+            exc_final.zeros = exc.zeros
+            exc_final.units = exc.units
+
+        # ##########################################
+        # Here we add data to the exc_final object #
+        # ##########################################
+
+        # variable to make tool_name for the tools
+        current_tool = 0
+        # The tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of
+        # drills
+        for tool_dia in custom_dict_drills:
+            # we create a tool name for each key in the drill_dia dict (the key is a unique drill diameter)
+            current_tool += 1
+
+            tool_name = str(current_tool)
+            spec = {"C": float(tool_dia)}
+            exc_final.tools[tool_name] = spec
+
+            # rebuild the drills list of dict's that belong to the exc_final object
+            for point in custom_dict_drills[tool_dia]:
+                exc_final.drills.append(
+                    {
+                        "point": point,
+                        "tool": str(current_tool)
+                    }
+                )
+
+        # The tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop])
+        # of two Shapely Points in case of slots
+        for tool_dia in custom_dict_slots:
+            # we create a tool name for each key in the slot_dia dict (the key is a unique slot diameter)
+            # but only if there are no drills
+            if not exc_final.tools:
+                current_tool += 1
+                tool_name = str(current_tool)
+                spec = {"C": float(tool_dia)}
+                exc_final.tools[tool_name] = spec
+            else:
+                dia_list = []
+                for v in exc_final.tools.values():
+                    dia_list.append(float(v["C"]))
+
+                if tool_dia not in dia_list:
+                    flag_order = True
+
+                    current_tool = len(dia_list) + 1
+                    tool_name = str(current_tool)
+                    spec = {"C": float(tool_dia)}
+                    exc_final.tools[tool_name] = spec
+
+                else:
+                    for k, v in exc_final.tools.items():
+                        if v["C"] == tool_dia:
+                            current_tool = int(k)
+                            break
+
+            # rebuild the slots list of dict's that belong to the exc_final object
+            for point in custom_dict_slots[tool_dia]:
+                exc_final.slots.append(
+                    {
+                        "start": point[0],
+                        "stop": point[1],
+                        "tool": str(current_tool)
+                    }
+                )
+
+        # flag_order == True means that there was an slot diameter not in the tools and we also have drills
+        # and the new tool was added to self.tools therefore we need to reorder the tools and drills and slots
+        current_tool = 0
+        if flag_order is True:
+            dia_list = []
+            temp_drills = []
+            temp_slots = []
+            temp_tools = {}
+            for v in exc_final.tools.values():
+                dia_list.append(float(v["C"]))
+            dia_list.sort()
+            for ordered_dia in dia_list:
+                current_tool += 1
+                tool_name_temp = str(current_tool)
+                spec_temp = {"C": float(ordered_dia)}
+                temp_tools[tool_name_temp] = spec_temp
+
+                for drill in exc_final.drills:
+                    exc_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[drill['tool']]['C']))
+                    if exc_tool_dia == ordered_dia:
+                        temp_drills.append(
+                            {
+                                "point": drill["point"],
+                                "tool": str(current_tool)
+                            }
+                        )
+
+                for slot in exc_final.slots:
+                    slot_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[slot['tool']]['C']))
+                    if slot_tool_dia == ordered_dia:
+                        temp_slots.append(
+                            {
+                                "start": slot["start"],
+                                "stop": slot["stop"],
+                                "tool": str(current_tool)
+                            }
+                        )
+
+            # delete the exc_final tools, drills and slots
+            exc_final.tools = {}
+            exc_final.drills[:] = []
+            exc_final.slots[:] = []
+
+            # update the exc_final tools, drills and slots with the ordered values
+            exc_final.tools = temp_tools
+            exc_final.drills[:] = temp_drills
+            exc_final.slots[:] = temp_slots
+
+        # create the geometry for the exc_final object
+        exc_final.create_geometry()
+
+    def build_ui(self):
+        FlatCAMObj.build_ui(self)
+
+        self.units = self.app.defaults['units'].upper()
+
+        for row in range(self.ui.tools_table.rowCount()):
+            try:
+                # if connected, disconnect the signal from the slot on item_changed as it creates issues
+                offset_spin_widget = self.ui.tools_table.cellWidget(row, 4)
+                offset_spin_widget.valueChanged.disconnect()
+            except (TypeError, AttributeError):
+                pass
+
+        n = len(self.tools)
+        # we have (n+2) rows because there are 'n' tools, each a row, plus the last 2 rows for totals.
+        self.ui.tools_table.setRowCount(n + 2)
+
+        self.tot_drill_cnt = 0
+        self.tot_slot_cnt = 0
+
+        self.tool_row = 0
+
+        sort = []
+        for k, v in list(self.tools.items()):
+            sort.append((k, v.get('C')))
+        sorted_tools = sorted(sort, key=lambda t1: t1[1])
+        tools = [i[0] for i in sorted_tools]
+
+        new_options = {}
+        for opt in self.options:
+            new_options[opt] = self.options[opt]
+
+        for tool_no in tools:
+
+            # add the data dictionary for each tool with the default values
+            self.tools[tool_no]['data'] = deepcopy(new_options)
+            # self.tools[tool_no]['data']["tooldia"] = self.tools[tool_no]["C"]
+            # self.tools[tool_no]['data']["slot_tooldia"] = self.tools[tool_no]["C"]
+
+            drill_cnt = 0  # variable to store the nr of drills per tool
+            slot_cnt = 0  # variable to store the nr of slots per tool
+
+            # Find no of drills for the current tool
+            for drill in self.drills:
+                if drill['tool'] == tool_no:
+                    drill_cnt += 1
+
+            self.tot_drill_cnt += drill_cnt
+
+            # Find no of slots for the current tool
+            for slot in self.slots:
+                if slot['tool'] == tool_no:
+                    slot_cnt += 1
+
+            self.tot_slot_cnt += slot_cnt
+
+            exc_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool_no))
+            exc_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+
+            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.tools[tool_no]['C']))
+            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+            drill_count_item = QtWidgets.QTableWidgetItem('%d' % drill_cnt)
+            drill_count_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+            # if the slot number is zero is better to not clutter the GUI with zero's so we print a space
+            slot_count_str = '%d' % slot_cnt if slot_cnt > 0 else ''
+            slot_count_item = QtWidgets.QTableWidgetItem(slot_count_str)
+            slot_count_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+            plot_item = FCCheckBox()
+            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
+            if self.ui.plot_cb.isChecked():
+                plot_item.setChecked(True)
+
+            self.ui.tools_table.setItem(self.tool_row, 0, exc_id_item)  # Tool name/id
+            self.ui.tools_table.setItem(self.tool_row, 1, dia_item)  # Diameter
+            self.ui.tools_table.setItem(self.tool_row, 2, drill_count_item)  # Number of drills per tool
+            self.ui.tools_table.setItem(self.tool_row, 3, slot_count_item)  # Number of drills per tool
+            empty_plot_item = QtWidgets.QTableWidgetItem('')
+            empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+            self.ui.tools_table.setItem(self.tool_row, 5, empty_plot_item)
+            self.ui.tools_table.setCellWidget(self.tool_row, 5, plot_item)
+
+            self.tool_row += 1
+
+        # add a last row with the Total number of drills
+        empty_1 = QtWidgets.QTableWidgetItem('')
+        empty_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        empty_1_1 = QtWidgets.QTableWidgetItem('')
+        empty_1_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        empty_1_2 = QtWidgets.QTableWidgetItem('')
+        empty_1_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        empty_1_3 = QtWidgets.QTableWidgetItem('')
+        empty_1_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+
+        label_tot_drill_count = QtWidgets.QTableWidgetItem(_('Total Drills'))
+        tot_drill_count = QtWidgets.QTableWidgetItem('%d' % self.tot_drill_cnt)
+        label_tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled)
+        tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled)
+
+        self.ui.tools_table.setItem(self.tool_row, 0, empty_1)
+        self.ui.tools_table.setItem(self.tool_row, 1, label_tot_drill_count)
+        self.ui.tools_table.setItem(self.tool_row, 2, tot_drill_count)  # Total number of drills
+        self.ui.tools_table.setItem(self.tool_row, 3, empty_1_1)
+        self.ui.tools_table.setItem(self.tool_row, 5, empty_1_3)
+
+        font = QtGui.QFont()
+        font.setBold(True)
+        font.setWeight(75)
+
+        for k in [1, 2]:
+            self.ui.tools_table.item(self.tool_row, k).setForeground(QtGui.QColor(127, 0, 255))
+            self.ui.tools_table.item(self.tool_row, k).setFont(font)
+
+        self.tool_row += 1
+
+        # add a last row with the Total number of slots
+        empty_2 = QtWidgets.QTableWidgetItem('')
+        empty_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        empty_2_1 = QtWidgets.QTableWidgetItem('')
+        empty_2_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        empty_2_2 = QtWidgets.QTableWidgetItem('')
+        empty_2_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        empty_2_3 = QtWidgets.QTableWidgetItem('')
+        empty_2_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+
+        label_tot_slot_count = QtWidgets.QTableWidgetItem(_('Total Slots'))
+        tot_slot_count = QtWidgets.QTableWidgetItem('%d' % self.tot_slot_cnt)
+        label_tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled)
+        tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled)
+
+        self.ui.tools_table.setItem(self.tool_row, 0, empty_2)
+        self.ui.tools_table.setItem(self.tool_row, 1, label_tot_slot_count)
+        self.ui.tools_table.setItem(self.tool_row, 2, empty_2_1)
+        self.ui.tools_table.setItem(self.tool_row, 3, tot_slot_count)  # Total number of slots
+        self.ui.tools_table.setItem(self.tool_row, 5, empty_2_3)
+
+        for kl in [1, 2, 3]:
+            self.ui.tools_table.item(self.tool_row, kl).setFont(font)
+            self.ui.tools_table.item(self.tool_row, kl).setForeground(QtGui.QColor(0, 70, 255))
+
+        # sort the tool diameter column
+        # self.ui.tools_table.sortItems(1)
+
+        # all the tools are selected by default
+        self.ui.tools_table.selectColumn(0)
+
+        self.ui.tools_table.resizeColumnsToContents()
+        self.ui.tools_table.resizeRowsToContents()
+
+        vertical_header = self.ui.tools_table.verticalHeader()
+        # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
+        vertical_header.hide()
+        self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        horizontal_header = self.ui.tools_table.horizontalHeader()
+        horizontal_header.setMinimumSectionSize(10)
+        horizontal_header.setDefaultSectionSize(70)
+        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(0, 20)
+
+        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+
+        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
+        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
+        horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(5, 17)
+        self.ui.tools_table.setColumnWidth(5, 17)
+
+        # horizontal_header.setStretchLastSection(True)
+        # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents)
+
+        # horizontal_header.setStretchLastSection(True)
+        self.ui.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        self.ui.tools_table.setSortingEnabled(False)
+
+        self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight())
+        self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight())
+
+        if not self.drills:
+            self.ui.tooldia_entry.hide()
+            self.ui.generate_milling_button.hide()
+        else:
+            self.ui.tooldia_entry.show()
+            self.ui.generate_milling_button.show()
+
+        if not self.slots:
+            self.ui.slot_tooldia_entry.hide()
+            self.ui.generate_milling_slots_button.hide()
+        else:
+            self.ui.slot_tooldia_entry.show()
+            self.ui.generate_milling_slots_button.show()
+
+        # set the text on tool_data_label after loading the object
+        sel_items = self.ui.tools_table.selectedItems()
+        sel_rows = [it.row() for it in sel_items]
+        if len(sel_rows) > 1:
+            self.ui.tool_data_label.setText(
+                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
+            )
+
+        self.ui_connect()
+
+    def set_ui(self, ui):
+        """
+        Configures the user interface for this object.
+        Connects options to form fields.
+
+        :param ui: User interface object.
+        :type ui: ExcellonObjectUI
+        :return: None
+        """
+        FlatCAMObj.set_ui(self, ui)
+
+        log.debug("ExcellonObject.set_ui()")
+
+        self.units = self.app.defaults['units'].upper()
+
+        self.form_fields.update({
+            "plot": self.ui.plot_cb,
+            "solid": self.ui.solid_cb,
+
+            "operation": self.ui.operation_radio,
+            "milling_type": self.ui.milling_type_radio,
+
+            "milling_dia": self.ui.mill_dia_entry,
+            "cutz": self.ui.cutz_entry,
+            "multidepth": self.ui.mpass_cb,
+            "depthperpass": self.ui.maxdepth_entry,
+            "travelz": self.ui.travelz_entry,
+            "feedrate_z": self.ui.feedrate_z_entry,
+            "feedrate": self.ui.xyfeedrate_entry,
+            "feedrate_rapid": self.ui.feedrate_rapid_entry,
+            "tooldia": self.ui.tooldia_entry,
+            "slot_tooldia": self.ui.slot_tooldia_entry,
+            "toolchange": self.ui.toolchange_cb,
+            "toolchangez": self.ui.toolchangez_entry,
+            "extracut": self.ui.extracut_cb,
+            "extracut_length": self.ui.e_cut_entry,
+
+            "spindlespeed": self.ui.spindlespeed_entry,
+            "dwell": self.ui.dwell_cb,
+            "dwelltime": self.ui.dwelltime_entry,
+
+            "startz": self.ui.estartz_entry,
+            "endz": self.ui.endz_entry,
+            "endxy": self.ui.endxy_entry,
+
+            "offset": self.ui.offset_entry,
+
+            "ppname_e": self.ui.pp_excellon_name_cb,
+            "ppname_g": self.ui.pp_geo_name_cb,
+            "z_pdepth": self.ui.pdepth_entry,
+            "feedrate_probe": self.ui.feedrate_probe_entry,
+            # "gcode_type": self.ui.excellon_gcode_type_radio
+        })
+
+        self.name2option = {
+            "e_operation": "operation",
+            "e_milling_type": "milling_type",
+            "e_milling_dia": "milling_dia",
+            "e_cutz": "cutz",
+            "e_multidepth": "multidepth",
+            "e_depthperpass": "depthperpass",
+
+            "e_travelz": "travelz",
+            "e_feedratexy": "feedrate",
+            "e_feedratez": "feedrate_z",
+            "e_fr_rapid": "feedrate_rapid",
+            "e_extracut": "extracut",
+            "e_extracut_length": "extracut_length",
+            "e_spindlespeed": "spindlespeed",
+            "e_dwell": "dwell",
+            "e_dwelltime": "dwelltime",
+            "e_offset": "offset",
+        }
+
+        # populate Excellon preprocessor combobox list
+        for name in list(self.app.preprocessors.keys()):
+            # the HPGL preprocessor is only for Geometry not for Excellon job therefore don't add it
+            if name == 'hpgl':
+                continue
+            self.ui.pp_excellon_name_cb.addItem(name)
+
+        # populate Geometry (milling) preprocessor combobox list
+        for name in list(self.app.preprocessors.keys()):
+            self.ui.pp_geo_name_cb.addItem(name)
+
+        # Fill form fields
+        self.to_form()
+
+        # update the changes in UI depending on the selected preprocessor in Preferences
+        # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the
+        # self.ui.pp_excellon_name_cb combobox
+        self.on_pp_changed()
+
+        # Show/Hide Advanced Options
+        if self.app.defaults["global_app_level"] == 'b':
+            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
+
+            self.ui.tools_table.setColumnHidden(4, True)
+            self.ui.tools_table.setColumnHidden(5, True)
+            self.ui.estartz_label.hide()
+            self.ui.estartz_entry.hide()
+            self.ui.feedrate_rapid_label.hide()
+            self.ui.feedrate_rapid_entry.hide()
+            self.ui.pdepth_label.hide()
+            self.ui.pdepth_entry.hide()
+            self.ui.feedrate_probe_label.hide()
+            self.ui.feedrate_probe_entry.hide()
+        else:
+            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
+
+        assert isinstance(self.ui, ExcellonObjectUI), \
+            "Expected a ExcellonObjectUI, got %s" % type(self.ui)
+        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
+        self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
+        self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
+        self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
+        self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click)
+
+        self.on_operation_type(val='drill')
+        self.ui.operation_radio.activated_custom.connect(self.on_operation_type)
+
+        self.ui.pp_excellon_name_cb.activated.connect(self.on_pp_changed)
+
+        self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
+
+        self.units_found = self.app.defaults['units']
+
+        # ########################################
+        # #######3 TEMP SETTINGS #################
+        # ########################################
+        self.ui.operation_radio.set_value("drill")
+        self.ui.operation_radio.setEnabled(False)
+
+    def ui_connect(self):
+
+        # selective plotting
+        for row in range(self.ui.tools_table.rowCount() - 2):
+            self.ui.tools_table.cellWidget(row, 5).clicked.connect(self.on_plot_cb_click_table)
+        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
+
+        # rows selected
+        self.ui.tools_table.clicked.connect(self.on_row_selection_change)
+        self.ui.tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change)
+
+        # value changed in the particular parameters of a tool
+        for key, option in self.name2option.items():
+            current_widget = self.form_fields[option]
+
+            if isinstance(current_widget, FCCheckBox):
+                current_widget.stateChanged.connect(self.form_to_storage)
+            if isinstance(current_widget, RadioSet):
+                current_widget.activated_custom.connect(self.form_to_storage)
+            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
+                current_widget.returnPressed.connect(self.form_to_storage)
+
+    def ui_disconnect(self):
+        # selective plotting
+        for row in range(self.ui.tools_table.rowCount()):
+            try:
+                self.ui.tools_table.cellWidget(row, 5).clicked.disconnect()
+            except (TypeError, AttributeError):
+                pass
+        try:
+            self.ui.plot_cb.stateChanged.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        # rows selected
+        try:
+            self.ui.tools_table.clicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+        try:
+            self.ui.tools_table.horizontalHeader().sectionClicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        # value changed in the particular parameters of a tool
+        for key, option in self.name2option.items():
+            current_widget = self.form_fields[option]
+
+            if isinstance(current_widget, FCCheckBox):
+                try:
+                    current_widget.stateChanged.disconnect(self.form_to_storage)
+                except (TypeError, ValueError):
+                    pass
+            if isinstance(current_widget, RadioSet):
+                try:
+                    current_widget.activated_custom.disconnect(self.form_to_storage)
+                except (TypeError, ValueError):
+                    pass
+            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
+                try:
+                    current_widget.returnPressed.disconnect(self.form_to_storage)
+                except (TypeError, ValueError):
+                    pass
+
+    def on_row_selection_change(self):
+        self.ui_disconnect()
+
+        sel_rows = []
+        sel_items = self.ui.tools_table.selectedItems()
+        for it in sel_items:
+            sel_rows.append(it.row())
+
+        if not sel_rows:
+            self.ui.tool_data_label.setText(
+                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("No Tool Selected"))
+            )
+            self.ui.generate_cnc_button.setDisabled(True)
+            self.ui.generate_milling_button.setDisabled(True)
+            self.ui.generate_milling_slots_button.setDisabled(True)
+            self.ui_connect()
+            return
+        else:
+            self.ui.generate_cnc_button.setDisabled(False)
+            self.ui.generate_milling_button.setDisabled(False)
+            self.ui.generate_milling_slots_button.setDisabled(False)
+
+        if len(sel_rows) == 1:
+            # update the QLabel that shows for which Tool we have the parameters in the UI form
+            tooluid = int(self.ui.tools_table.item(sel_rows[0], 0).text())
+            self.ui.tool_data_label.setText(
+                "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), tooluid)
+            )
+        else:
+            self.ui.tool_data_label.setText(
+                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
+            )
+
+        for c_row in sel_rows:
+            # populate the form with the data from the tool associated with the row parameter
+            try:
+                item = self.ui.tools_table.item(c_row, 0)
+                if type(item) is not None:
+                    tooluid = item.text()
+                    self.storage_to_form(self.tools[str(tooluid)]['data'])
+                else:
+                    self.ui_connect()
+                    return
+            except Exception as e:
+                log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e))
+                self.ui_connect()
+                return
+
+        self.ui_connect()
+
+    def storage_to_form(self, dict_storage):
+        for form_key in self.form_fields:
+            for storage_key in dict_storage:
+                if form_key == storage_key and form_key not in \
+                        ["toolchange", "toolchangez", "startz", "endz", "ppname_e", "ppname_g"]:
+                    try:
+                        self.form_fields[form_key].set_value(dict_storage[form_key])
+                    except Exception as e:
+                        log.debug("ExcellonObject.storage_to_form() --> %s" % str(e))
+                        pass
+
+    def form_to_storage(self):
+        if self.ui.tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            return
+
+        self.ui_disconnect()
+
+        widget_changed = self.sender()
+        wdg_objname = widget_changed.objectName()
+        option_changed = self.name2option[wdg_objname]
+
+        # row = self.ui.tools_table.currentRow()
+        rows = sorted(set(index.row() for index in self.ui.tools_table.selectedIndexes()))
+        for row in rows:
+            if row < 0:
+                row = 0
+            tooluid_item = int(self.ui.tools_table.item(row, 0).text())
+
+            for tooluid_key, tooluid_val in self.tools.items():
+                if int(tooluid_key) == tooluid_item:
+                    new_option_value = self.form_fields[option_changed].get_value()
+                    if option_changed in tooluid_val:
+                        tooluid_val[option_changed] = new_option_value
+                    if option_changed in tooluid_val['data']:
+                        tooluid_val['data'][option_changed] = new_option_value
+
+        self.ui_connect()
+
+    def on_operation_type(self, val):
+        if val == 'mill':
+            self.ui.mill_type_label.show()
+            self.ui.milling_type_radio.show()
+            self.ui.mill_dia_label.show()
+            self.ui.mill_dia_entry.show()
+            self.ui.frxylabel.show()
+            self.ui.xyfeedrate_entry.show()
+            self.ui.extracut_cb.show()
+            self.ui.e_cut_entry.show()
+
+            # if 'laser' not in self.ui.pp_excellon_name_cb.get_value().lower():
+            #     self.ui.mpass_cb.show()
+            #     self.ui.maxdepth_entry.show()
+        else:
+            self.ui.mill_type_label.hide()
+            self.ui.milling_type_radio.hide()
+            self.ui.mill_dia_label.hide()
+            self.ui.mill_dia_entry.hide()
+            # self.ui.mpass_cb.hide()
+            # self.ui.maxdepth_entry.hide()
+            self.ui.frxylabel.hide()
+            self.ui.xyfeedrate_entry.hide()
+            self.ui.extracut_cb.hide()
+            self.ui.e_cut_entry.hide()
+
+    def get_selected_tools_list(self):
+        """
+        Returns the keys to the self.tools dictionary corresponding
+        to the selections on the tool list in the GUI.
+
+        :return: List of tools.
+        :rtype: list
+        """
+
+        return [str(x.text()) for x in self.ui.tools_table.selectedItems()]
+
+    def get_selected_tools_table_items(self):
+        """
+        Returns a list of lists, each list in the list is made out of row elements
+
+        :return: List of table_tools items.
+        :rtype: list
+        """
+        table_tools_items = []
+        for x in self.ui.tools_table.selectedItems():
+            # from the columnCount we subtract a value of 1 which represent the last column (plot column)
+            # which does not have text
+            txt = ''
+            elem = []
+
+            for column in range(0, self.ui.tools_table.columnCount() - 1):
+                try:
+                    txt = self.ui.tools_table.item(x.row(), column).text()
+                except AttributeError:
+                    try:
+                        txt = self.ui.tools_table.cellWidget(x.row(), column).currentText()
+                    except AttributeError:
+                        pass
+                elem.append(txt)
+            table_tools_items.append(deepcopy(elem))
+            # table_tools_items.append([self.ui.tools_table.item(x.row(), column).text()
+            #                           for column in range(0, self.ui.tools_table.columnCount() - 1)])
+        for item in table_tools_items:
+            item[0] = str(item[0])
+        return table_tools_items
+
+    def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'):
+        """
+        Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code
+        :return: has_slots and Excellon_code
+        """
+
+        excellon_code = ''
+
+        # store here if the file has slots, return 1 if any slots, 0 if only drills
+        has_slots = 0
+
+        # drills processing
+        try:
+            if self.drills:
+                length = whole + fract
+                for tool in self.tools:
+                    excellon_code += 'T0%s\n' % str(tool) if int(tool) < 10 else 'T%s\n' % str(tool)
+
+                    for drill in self.drills:
+                        if form == 'dec' and tool == drill['tool']:
+                            drill_x = drill['point'].x * factor
+                            drill_y = drill['point'].y * factor
+                            excellon_code += "X{:.{dec}f}Y{:.{dec}f}\n".format(drill_x, drill_y, dec=fract)
+                        elif e_zeros == 'LZ' and tool == drill['tool']:
+                            drill_x = drill['point'].x * factor
+                            drill_y = drill['point'].y * factor
+
+                            exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract)
+                            exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract)
+
+                            # extract whole part and decimal part
+                            exc_x_formatted = exc_x_formatted.partition('.')
+                            exc_y_formatted = exc_y_formatted.partition('.')
+
+                            # left padd the 'whole' part with zeros
+                            x_whole = exc_x_formatted[0].rjust(whole, '0')
+                            y_whole = exc_y_formatted[0].rjust(whole, '0')
+
+                            # restore the coordinate padded in the left with 0 and added the decimal part
+                            # without the decinal dot
+                            exc_x_formatted = x_whole + exc_x_formatted[2]
+                            exc_y_formatted = y_whole + exc_y_formatted[2]
+
+                            excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted,
+                                                                         yform=exc_y_formatted)
+                        elif tool == drill['tool']:
+                            drill_x = drill['point'].x * factor
+                            drill_y = drill['point'].y * factor
+
+                            exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract).replace('.', '')
+                            exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract).replace('.', '')
+
+                            # pad with rear zeros
+                            exc_x_formatted.ljust(length, '0')
+                            exc_y_formatted.ljust(length, '0')
+
+                            excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted,
+                                                                         yform=exc_y_formatted)
+        except Exception as e:
+            log.debug(str(e))
+
+        # slots processing
+        try:
+            if self.slots:
+                has_slots = 1
+                for tool in self.tools:
+                    excellon_code += 'G05\n'
+
+                    if int(tool) < 10:
+                        excellon_code += 'T0' + str(tool) + '\n'
+                    else:
+                        excellon_code += 'T' + str(tool) + '\n'
+
+                    for slot in self.slots:
+                        if form == 'dec' and tool == slot['tool']:
+                            start_slot_x = slot['start'].x * factor
+                            start_slot_y = slot['start'].y * factor
+                            stop_slot_x = slot['stop'].x * factor
+                            stop_slot_y = slot['stop'].y * factor
+                            if slot_type == 'routing':
+                                excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM15\n".format(start_slot_x,
+                                                                                           start_slot_y,
+                                                                                           dec=fract)
+                                excellon_code += "G01X{:.{dec}f}Y{:.{dec}f}\nM16\n".format(stop_slot_x,
+                                                                                           stop_slot_y,
+                                                                                           dec=fract)
+                            elif slot_type == 'drilling':
+                                excellon_code += "X{:.{dec}f}Y{:.{dec}f}G85X{:.{dec}f}Y{:.{dec}f}\nG05\n".format(
+                                    start_slot_x, start_slot_y, stop_slot_x, stop_slot_y, dec=fract
+                                )
+
+                        elif e_zeros == 'LZ' and tool == slot['tool']:
+                            start_slot_x = slot['start'].x * factor
+                            start_slot_y = slot['start'].y * factor
+                            stop_slot_x = slot['stop'].x * factor
+                            stop_slot_y = slot['stop'].y * factor
+
+                            start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '')
+                            start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '')
+                            stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '')
+                            stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '')
+
+                            # extract whole part and decimal part
+                            start_slot_x_formatted = start_slot_x_formatted.partition('.')
+                            start_slot_y_formatted = start_slot_y_formatted.partition('.')
+                            stop_slot_x_formatted = stop_slot_x_formatted.partition('.')
+                            stop_slot_y_formatted = stop_slot_y_formatted.partition('.')
+
+                            # left padd the 'whole' part with zeros
+                            start_x_whole = start_slot_x_formatted[0].rjust(whole, '0')
+                            start_y_whole = start_slot_y_formatted[0].rjust(whole, '0')
+                            stop_x_whole = stop_slot_x_formatted[0].rjust(whole, '0')
+                            stop_y_whole = stop_slot_y_formatted[0].rjust(whole, '0')
+
+                            # restore the coordinate padded in the left with 0 and added the decimal part
+                            # without the decinal dot
+                            start_slot_x_formatted = start_x_whole + start_slot_x_formatted[2]
+                            start_slot_y_formatted = start_y_whole + start_slot_y_formatted[2]
+                            stop_slot_x_formatted = stop_x_whole + stop_slot_x_formatted[2]
+                            stop_slot_y_formatted = stop_y_whole + stop_slot_y_formatted[2]
+
+                            if slot_type == 'routing':
+                                excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
+                                                                                       ystart=start_slot_y_formatted)
+                                excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
+                                                                                     ystop=stop_slot_y_formatted)
+                            elif slot_type == 'drilling':
+                                excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format(
+                                    xstart=start_slot_x_formatted, ystart=start_slot_y_formatted,
+                                    xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted
+                                )
+                        elif tool == slot['tool']:
+                            start_slot_x = slot['start'].x * factor
+                            start_slot_y = slot['start'].y * factor
+                            stop_slot_x = slot['stop'].x * factor
+                            stop_slot_y = slot['stop'].y * factor
+                            length = whole + fract
+
+                            start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '')
+                            start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '')
+                            stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '')
+                            stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '')
+
+                            # pad with rear zeros
+                            start_slot_x_formatted.ljust(length, '0')
+                            start_slot_y_formatted.ljust(length, '0')
+                            stop_slot_x_formatted.ljust(length, '0')
+                            stop_slot_y_formatted.ljust(length, '0')
+
+                            if slot_type == 'routing':
+                                excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
+                                                                                       ystart=start_slot_y_formatted)
+                                excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
+                                                                                     ystop=stop_slot_y_formatted)
+                            elif slot_type == 'drilling':
+                                excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format(
+                                    xstart=start_slot_x_formatted, ystart=start_slot_y_formatted,
+                                    xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted
+                                )
+        except Exception as e:
+            log.debug(str(e))
+
+        if not self.drills and not self.slots:
+            log.debug("FlatCAMObj.ExcellonObject.export_excellon() --> Excellon Object is empty: no drills, no slots.")
+            return 'fail'
+
+        return has_slots, excellon_code
+
+    def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
+        """
+        Note: This method is a good template for generic operations as
+        it takes it's options from parameters or otherwise from the
+        object's options and returns a (success, msg) tuple as feedback
+        for shell operations.
+
+        :return: Success/failure condition tuple (bool, str).
+        :rtype: tuple
+        """
+
+        # Get the tools from the list. These are keys
+        # to self.tools
+        if tools is None:
+            tools = self.get_selected_tools_list()
+
+        if outname is None:
+            outname = self.options["name"] + "_mill"
+
+        if tooldia is None:
+            tooldia = float(self.options["tooldia"])
+
+        # Sort tools by diameter. items() -> [('name', diameter), ...]
+        # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
+
+        sort = []
+        for k, v in self.tools.items():
+            sort.append((k, v.get('C')))
+        sorted_tools = sorted(sort, key=lambda t1: t1[1])
+
+        if tools == "all":
+            tools = [i[0] for i in sorted_tools]  # List if ordered tool names.
+            log.debug("Tools 'all' and sorted are: %s" % str(tools))
+
+        if len(tools) == 0:
+            self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                 _("Please select one or more tools from the list and try again."))
+            return False, "Error: No tools."
+
+        for tool in tools:
+            if tooldia > self.tools[tool]["C"]:
+                self.app.inform.emit(
+                    '[ERROR_NOTCL] %s %s: %s' % (
+                        _("Milling tool for DRILLS is larger than hole size. Cancelled."),
+                        _("Tool"),
+                        str(tool)
+                    )
+                )
+                return False, "Error: Milling tool is larger than hole."
+
+        def geo_init(geo_obj, app_obj):
+            assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj)
+
+            # ## Add properties to the object
+
+            # get the tool_table items in a list of row items
+            tool_table_items = self.get_selected_tools_table_items()
+            # insert an information only element in the front
+            tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")])
+
+            geo_obj.options['Tools_in_use'] = tool_table_items
+            geo_obj.options['type'] = 'Excellon Geometry'
+            geo_obj.options["cnctooldia"] = str(tooldia)
+
+            geo_obj.solid_geometry = []
+
+            # 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 hole in self.drills:
+                if hole['tool'] in tools:
+                    buffer_value = self.tools[hole['tool']]["C"] / 2 - tooldia / 2
+                    if buffer_value == 0:
+                        geo_obj.solid_geometry.append(
+                            Point(hole['point']).buffer(0.0000001).exterior)
+                    else:
+                        geo_obj.solid_geometry.append(
+                            Point(hole['point']).buffer(buffer_value).exterior)
+        if use_thread:
+            def geo_thread(app_obj):
+                app_obj.new_object("geometry", outname, geo_init, plot=plot)
+
+            # Create a promise with the new name
+            self.app.collection.promise(outname)
+
+            # Send to worker
+            self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
+        else:
+            self.app.new_object("geometry", outname, geo_init, plot=plot)
+
+        return True, ""
+
+    def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False):
+        """
+        Note: This method is a good template for generic operations as
+        it takes it's options from parameters or otherwise from the
+        object's options and returns a (success, msg) tuple as feedback
+        for shell operations.
+
+        :return: Success/failure condition tuple (bool, str).
+        :rtype: tuple
+        """
+
+        # Get the tools from the list. These are keys
+        # to self.tools
+        if tools is None:
+            tools = self.get_selected_tools_list()
+
+        if outname is None:
+            outname = self.options["name"] + "_mill"
+
+        if tooldia is None:
+            tooldia = float(self.options["slot_tooldia"])
+
+        # Sort tools by diameter. items() -> [('name', diameter), ...]
+        # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
+
+        sort = []
+        for k, v in self.tools.items():
+            sort.append((k, v.get('C')))
+        sorted_tools = sorted(sort, key=lambda t1: t1[1])
+
+        if tools == "all":
+            tools = [i[0] for i in sorted_tools]  # List if ordered tool names.
+            log.debug("Tools 'all' and sorted are: %s" % str(tools))
+
+        if len(tools) == 0:
+            self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                 _("Please select one or more tools from the list and try again."))
+            return False, "Error: No tools."
+
+        for tool in tools:
+            # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
+            adj_toolstable_tooldia = float('%.*f' % (self.decimals, float(tooldia)))
+            adj_file_tooldia = float('%.*f' % (self.decimals, float(self.tools[tool]["C"])))
+            if adj_toolstable_tooldia > adj_file_tooldia + 0.0001:
+                self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                     _("Milling tool for SLOTS is larger than hole size. Cancelled."))
+                return False, "Error: Milling tool is larger than hole."
+
+        def geo_init(geo_obj, app_obj):
+            assert geo_obj.kind == 'geometry' "Initializer expected a GeometryObject, got %s" % type(geo_obj)
+
+            # ## Add properties to the object
+
+            # get the tool_table items in a list of row items
+            tool_table_items = self.get_selected_tools_table_items()
+            # insert an information only element in the front
+            tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")])
+
+            geo_obj.options['Tools_in_use'] = tool_table_items
+            geo_obj.options['type'] = 'Excellon Geometry'
+            geo_obj.options["cnctooldia"] = str(tooldia)
+
+            geo_obj.solid_geometry = []
+
+            # 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 slot in self.slots:
+                if slot['tool'] in tools:
+                    toolstable_tool = float('%.*f' % (self.decimals, float(tooldia)))
+                    file_tool = float('%.*f' % (self.decimals, float(self.tools[tool]["C"])))
+
+                    # 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)
+                    buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001
+                    if buffer_value == 0:
+                        start = slot['start']
+                        stop = slot['stop']
+
+                        lines_string = LineString([start, stop])
+                        poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior
+                        geo_obj.solid_geometry.append(poly)
+                    else:
+                        start = slot['start']
+                        stop = slot['stop']
+
+                        lines_string = LineString([start, stop])
+                        poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior
+                        geo_obj.solid_geometry.append(poly)
+
+        if use_thread:
+            def geo_thread(app_obj):
+                app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
+
+            # Create a promise with the new name
+            self.app.collection.promise(outname)
+
+            # Send to worker
+            self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
+        else:
+            self.app.new_object("geometry", outname + '_slot', geo_init, plot=plot)
+
+        return True, ""
+
+    def on_generate_milling_button_click(self, *args):
+        self.app.report_usage("excellon_on_create_milling_drills button")
+        self.read_form()
+
+        self.generate_milling_drills(use_thread=False)
+
+    def on_generate_milling_slots_button_click(self, *args):
+        self.app.report_usage("excellon_on_create_milling_slots_button")
+        self.read_form()
+
+        self.generate_milling_slots(use_thread=False)
+
+    def on_pp_changed(self):
+        current_pp = self.ui.pp_excellon_name_cb.get_value()
+
+        if "toolchange_probe" in current_pp.lower():
+            self.ui.pdepth_entry.setVisible(True)
+            self.ui.pdepth_label.show()
+
+            self.ui.feedrate_probe_entry.setVisible(True)
+            self.ui.feedrate_probe_label.show()
+        else:
+            self.ui.pdepth_entry.setVisible(False)
+            self.ui.pdepth_label.hide()
+
+            self.ui.feedrate_probe_entry.setVisible(False)
+            self.ui.feedrate_probe_label.hide()
+
+        if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
+            self.ui.feedrate_rapid_label.show()
+            self.ui.feedrate_rapid_entry.show()
+        else:
+            self.ui.feedrate_rapid_label.hide()
+            self.ui.feedrate_rapid_entry.hide()
+
+        if 'laser' in current_pp.lower():
+            self.ui.cutzlabel.hide()
+            self.ui.cutz_entry.hide()
+            try:
+                self.ui.mpass_cb.hide()
+                self.ui.maxdepth_entry.hide()
+            except AttributeError:
+                pass
+
+            if 'marlin' in current_pp.lower():
+                self.ui.travelzlabel.setText('%s:' % _("Focus Z"))
+                self.ui.endz_label.show()
+                self.ui.endz_entry.show()
+            else:
+                self.ui.travelzlabel.hide()
+                self.ui.travelz_entry.hide()
+
+                self.ui.endz_label.hide()
+                self.ui.endz_entry.hide()
+
+            try:
+                self.ui.frzlabel.hide()
+                self.ui.feedrate_z_entry.hide()
+            except AttributeError:
+                pass
+
+            self.ui.dwell_cb.hide()
+            self.ui.dwelltime_entry.hide()
+
+            self.ui.spindle_label.setText('%s:' % _("Laser Power"))
+
+            try:
+                self.ui.tool_offset_label.hide()
+                self.ui.offset_entry.hide()
+            except AttributeError:
+                pass
+        else:
+            self.ui.cutzlabel.show()
+            self.ui.cutz_entry.show()
+            try:
+                self.ui.mpass_cb.show()
+                self.ui.maxdepth_entry.show()
+            except AttributeError:
+                pass
+
+            self.ui.travelzlabel.setText('%s:' % _('Travel Z'))
+
+            self.ui.travelzlabel.show()
+            self.ui.travelz_entry.show()
+
+            self.ui.endz_label.show()
+            self.ui.endz_entry.show()
+
+            try:
+                self.ui.frzlabel.show()
+                self.ui.feedrate_z_entry.show()
+            except AttributeError:
+                pass
+            self.ui.dwell_cb.show()
+            self.ui.dwelltime_entry.show()
+
+            self.ui.spindle_label.setText('%s:' % _('Spindle speed'))
+
+            try:
+                self.ui.tool_offset_lbl.show()
+                self.ui.offset_entry.show()
+            except AttributeError:
+                pass
+
+    def on_create_cncjob_button_click(self, *args):
+        self.app.report_usage("excellon_on_create_cncjob_button")
+        self.read_form()
+
+        # Get the tools from the list
+        tools = self.get_selected_tools_list()
+
+        if len(tools) == 0:
+            # if there is a single tool in the table (remember that the last 2 rows are for totals and do not count in
+            # tool number) it means that there are 3 rows (1 tool and 2 totals).
+            # in this case regardless of the selection status of that tool, use it.
+            if self.ui.tools_table.rowCount() == 3:
+                tools.append(self.ui.tools_table.item(0, 0).text())
+            else:
+                self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                     _("Please select one or more tools from the list and try again."))
+                return
+
+        xmin = self.options['xmin']
+        ymin = self.options['ymin']
+        xmax = self.options['xmax']
+        ymax = self.options['ymax']
+
+        job_name = self.options["name"] + "_cnc"
+        pp_excellon_name = self.options["ppname_e"]
+
+        # Object initialization function for app.new_object()
+        def job_init(job_obj, app_obj):
+            assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
+
+            # get the tool_table items in a list of row items
+            tool_table_items = self.get_selected_tools_table_items()
+            # insert an information only element in the front
+            tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")])
+
+            # ## Add properties to the object
+
+            job_obj.origin_kind = 'excellon'
+
+            job_obj.options['Tools_in_use'] = tool_table_items
+            job_obj.options['type'] = 'Excellon'
+            job_obj.options['ppname_e'] = pp_excellon_name
+
+            job_obj.multidepth = self.options["multidepth"]
+            job_obj.z_depthpercut = self.options["depthperpass"]
+
+            job_obj.z_move = float(self.options["travelz"])
+            job_obj.feedrate = float(self.options["feedrate_z"])
+            job_obj.z_feedrate = float(self.options["feedrate_z"])
+            job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
+
+            job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] != 0 else None
+            job_obj.spindledir = self.app.defaults['excellon_spindledir']
+            job_obj.dwell = self.options["dwell"]
+            job_obj.dwelltime = float(self.options["dwelltime"])
+
+            job_obj.pp_excellon_name = pp_excellon_name
+
+            job_obj.toolchange_xy_type = "excellon"
+            job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
+            job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
+
+            job_obj.options['xmin'] = xmin
+            job_obj.options['ymin'] = ymin
+            job_obj.options['xmax'] = xmax
+            job_obj.options['ymax'] = ymax
+
+            job_obj.z_pdepth = float(self.options["z_pdepth"])
+            job_obj.feedrate_probe = float(self.options["feedrate_probe"])
+
+            job_obj.z_cut = float(self.options['cutz'])
+            job_obj.toolchange = self.options["toolchange"]
+            job_obj.xy_toolchange = self.app.defaults["excellon_toolchangexy"]
+            job_obj.z_toolchange = float(self.options["toolchangez"])
+            job_obj.startz = float(self.options["startz"]) if self.options["startz"] else None
+            job_obj.endz = float(self.options["endz"])
+            job_obj.xy_end = self.options["endxy"]
+            job_obj.excellon_optimization_type = self.app.defaults["excellon_optimization_type"]
+
+            tools_csv = ','.join(tools)
+            ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, use_ui=True)
+
+            if ret_val == 'fail':
+                return 'fail'
+
+            job_obj.gcode_parse()
+            job_obj.create_geometry()
+
+        # To be run in separate thread
+        def job_thread(app_obj):
+            with self.app.proc_container.new(_("Generating CNC Code")):
+                app_obj.new_object("cncjob", job_name, job_init)
+
+        # Create promise for the new name.
+        self.app.collection.promise(job_name)
+
+        # Send to worker
+        # self.app.worker.add_task(job_thread, [self.app])
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+
+    def convert_units(self, units):
+        log.debug("FlatCAMObj.ExcellonObject.convert_units()")
+
+        Excellon.convert_units(self, units)
+
+        # factor = Excellon.convert_units(self, units)
+        # self.options['drillz'] = float(self.options['drillz']) * factor
+        # self.options['travelz'] = float(self.options['travelz']) * factor
+        # self.options['feedrate'] = float(self.options['feedrate']) * factor
+        # self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
+        # self.options['toolchangez'] = float(self.options['toolchangez']) * factor
+        #
+        # if self.app.defaults["excellon_toolchangexy"] == '':
+        #     self.options['toolchangexy'] = "0.0, 0.0"
+        # else:
+        #     coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")]
+        #     if len(coords_xy) < 2:
+        #         self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be "
+        #                                               "in the format (x, y) \n"
+        #                                               "but now there is only one value, not two. "))
+        #         return 'fail'
+        #     coords_xy[0] *= factor
+        #     coords_xy[1] *= factor
+        #     self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
+        #
+        # if self.options['startz'] is not None:
+        #     self.options['startz'] = float(self.options['startz']) * factor
+        # self.options['endz'] = float(self.options['endz']) * factor
+
+    def on_solid_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('solid')
+        self.plot()
+
+    def on_plot_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.plot()
+        self.read_form_item('plot')
+
+        self.ui_disconnect()
+        cb_flag = self.ui.plot_cb.isChecked()
+        for row in range(self.ui.tools_table.rowCount() - 2):
+            table_cb = self.ui.tools_table.cellWidget(row, 5)
+            if cb_flag:
+                table_cb.setChecked(True)
+            else:
+                table_cb.setChecked(False)
+
+        self.ui_connect()
+
+    def on_plot_cb_click_table(self):
+        # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked)
+        self.ui_disconnect()
+        # cw = self.sender()
+        # cw_index = self.ui.tools_table.indexAt(cw.pos())
+        # cw_row = cw_index.row()
+        check_row = 0
+
+        self.shapes.clear(update=True)
+        for tool_key in self.tools:
+            solid_geometry = self.tools[tool_key]['solid_geometry']
+
+            # find the geo_tool_table row associated with the tool_key
+            for row in range(self.ui.tools_table.rowCount()):
+                tool_item = int(self.ui.tools_table.item(row, 0).text())
+                if tool_item == int(tool_key):
+                    check_row = row
+                    break
+            if self.ui.tools_table.cellWidget(check_row, 5).isChecked():
+                self.options['plot'] = True
+                # self.plot_element(element=solid_geometry, visible=True)
+                # Plot excellon (All polygons?)
+                if self.options["solid"]:
+                    for geo in solid_geometry:
+                        self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
+                                       visible=self.options['plot'],
+                                       layer=2)
+                else:
+                    for geo in solid_geometry:
+                        self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
+                        for ints in geo.interiors:
+                            self.add_shape(shape=ints, color='green', visible=self.options['plot'])
+        self.shapes.redraw()
+
+        # make sure that the general plot is disabled if one of the row plot's are disabled and
+        # if all the row plot's are enabled also enable the general plot checkbox
+        cb_cnt = 0
+        total_row = self.ui.tools_table.rowCount()
+        for row in range(total_row - 2):
+            if self.ui.tools_table.cellWidget(row, 5).isChecked():
+                cb_cnt += 1
+            else:
+                cb_cnt -= 1
+        if cb_cnt < total_row - 2:
+            self.ui.plot_cb.setChecked(False)
+        else:
+            self.ui.plot_cb.setChecked(True)
+        self.ui_connect()
+
+    def plot(self, visible=None, kind=None):
+
+        # Does all the required setup and returns False
+        # if the 'ptint' option is set to False.
+        if not FlatCAMObj.plot(self):
+            return
+
+        # try:
+        #     # Plot Excellon (All polygons?)
+        #     if self.options["solid"]:
+        #         for tool in self.tools:
+        #             for geo in self.tools[tool]['solid_geometry']:
+        #                 self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
+        #                                visible=self.options['plot'],
+        #                                layer=2)
+        #     else:
+        #         for tool in self.tools:
+        #             for geo in self.tools[tool]['solid_geometry']:
+        #                 self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot'])
+        #                 for ints in geo.interiors:
+        #                     self.add_shape(shape=ints, color='orange', visible=self.options['plot'])
+        #
+        #     self.shapes.redraw()
+        #     return
+        # except (ObjectDeleted, AttributeError, KeyError):
+        #     self.shapes.clear(update=True)
+
+        # this stays for compatibility reasons, in case we try to open old projects
+        try:
+            __ = iter(self.solid_geometry)
+        except TypeError:
+            self.solid_geometry = [self.solid_geometry]
+
+        visible = visible if visible else self.options['plot']
+
+        try:
+            # Plot Excellon (All polygons?)
+            if self.options["solid"]:
+                for geo in self.solid_geometry:
+                    self.add_shape(shape=geo,
+                                   color=self.outline_color,
+                                   face_color=self.fill_color,
+                                   visible=visible,
+                                   layer=2)
+            else:
+                for geo in self.solid_geometry:
+                    self.add_shape(shape=geo.exterior, color='red', visible=visible)
+                    for ints in geo.interiors:
+                        self.add_shape(shape=ints, color='orange', visible=visible)
+
+            self.shapes.redraw()
+        except (ObjectDeleted, AttributeError):
+            self.shapes.clear(update=True)
+
+    def on_apply_param_to_all_clicked(self):
+        if self.ui.tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            log.debug("ExcellonObject.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
+            return
+
+        self.ui_disconnect()
+
+        row = self.ui.tools_table.currentRow()
+        if row < 0:
+            row = 0
+
+        tooluid_item = int(self.ui.tools_table.item(row, 0).text())
+        temp_tool_data = {}
+
+        for tooluid_key, tooluid_val in self.tools.items():
+            if int(tooluid_key) == tooluid_item:
+                # this will hold the 'data' key of the self.tools[tool] dictionary that corresponds to
+                # the current row in the tool table
+                temp_tool_data = tooluid_val['data']
+                break
+
+        for tooluid_key, tooluid_val in self.tools.items():
+            tooluid_val['data'] = deepcopy(temp_tool_data)
+
+        self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools."))
+
+        self.ui_connect()

+ 2622 - 0
flatcamObjects/FlatCAMGeometry.py

@@ -0,0 +1,2622 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# ##########################################################
+
+# ##########################################################
+# File modified by: Marius Stanciu                         #
+# ##########################################################
+
+from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
+import shapely.affinity as affinity
+
+from camlib import Geometry
+
+from flatcamObjects.FlatCAMObj import *
+
+import ezdxf
+import math
+import numpy as np
+from copy import deepcopy
+import traceback
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class GeometryObject(FlatCAMObj, Geometry):
+    """
+    Geometric object not associated with a specific
+    format.
+    """
+    optionChanged = QtCore.pyqtSignal(str)
+    ui_type = GeometryObjectUI
+
+    def __init__(self, name):
+        self.decimals = self.app.decimals
+
+        self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
+
+        FlatCAMObj.__init__(self, name)
+        Geometry.__init__(self, geo_steps_per_circle=self.circle_steps)
+
+        self.kind = "geometry"
+
+        self.options.update({
+            "plot": True,
+            "cutz": -0.002,
+            "vtipdia": 0.1,
+            "vtipangle": 30,
+            "travelz": 0.1,
+            "feedrate": 5.0,
+            "feedrate_z": 5.0,
+            "feedrate_rapid": 5.0,
+            "spindlespeed": 0,
+            "dwell": True,
+            "dwelltime": 1000,
+            "multidepth": False,
+            "depthperpass": 0.002,
+            "extracut": False,
+            "extracut_length": 0.1,
+            "endz": 2.0,
+            "endxy": '',
+
+            "startz": None,
+            "toolchange": False,
+            "toolchangez": 1.0,
+            "toolchangexy": "0.0, 0.0",
+            "ppname_g": 'default',
+            "z_pdepth": -0.02,
+            "feedrate_probe": 3.0,
+        })
+
+        if "cnctooldia" not in self.options:
+            if type(self.app.defaults["geometry_cnctooldia"]) == float:
+                self.options["cnctooldia"] = self.app.defaults["geometry_cnctooldia"]
+            else:
+                try:
+                    tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
+                    tools_diameters = [eval(a) for a in tools_string if a != '']
+                    self.options["cnctooldia"] = tools_diameters[0] if tools_diameters else 0.0
+                except Exception as e:
+                    log.debug("FlatCAMObj.GeometryObject.init() --> %s" % str(e))
+
+        self.options["startz"] = self.app.defaults["geometry_startz"]
+
+        # this will hold the tool unique ID that is useful when having multiple tools with same diameter
+        self.tooluid = 0
+
+        '''
+            self.tools = {}
+            This is a dictionary. Each dict key is associated with a tool used in geo_tools_table. The key is the 
+            tool_id of the tools and the value is another dict that will hold the data under the following form:
+                {tooluid:   {
+                            'tooldia': 1,
+                            'offset': 'Path',
+                            'offset_value': 0.0
+                            'type': 'Rough',
+                            'tool_type': 'C1',
+                            'data': self.default_tool_data
+                            'solid_geometry': []
+                            }
+                }
+        '''
+        self.tools = {}
+
+        # this dict is to store those elements (tools) of self.tools that are selected in the self.geo_tools_table
+        # those elements are the ones used for generating GCode
+        self.sel_tools = {}
+
+        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"]
+
+        # flag to store if the V-Shape tool is selected in self.ui.geo_tools_table
+        self.v_tool_type = None
+
+        # flag to store if the Geometry is type 'multi-geometry' meaning that each tool has it's own geometry
+        # the default value is False
+        self.multigeo = False
+
+        # flag to store if the geometry is part of a special group of geometries that can't be processed by the default
+        # engine of FlatCAM. Most likely are generated by some of tools and are special cases of geometries.
+        self.special_group = None
+
+        self.old_pp_state = self.app.defaults["geometry_multidepth"]
+        self.old_toolchangeg_state = self.app.defaults["geometry_toolchange"]
+        self.units_found = self.app.defaults['units']
+
+        # this variable can be updated by the Object that generates the geometry
+        self.tool_type = 'C1'
+
+        # save here the old value for the Cut Z before it is changed by selecting a V-shape type tool in the tool table
+        self.old_cutz = self.app.defaults["geometry_cutz"]
+
+        self.fill_color = self.app.defaults['geometry_plot_line']
+        self.outline_color = self.app.defaults['geometry_plot_line']
+        self.alpha_level = 'FF'
+
+        self.param_fields = {}
+
+        # Attributes to be included in serialization
+        # Always append to it because it carries contents
+        # from predecessors.
+        self.ser_attrs += ['options', 'kind', 'tools', 'multigeo']
+
+    def build_ui(self):
+        self.ui_disconnect()
+        FlatCAMObj.build_ui(self)
+
+        self.units = self.app.defaults['units']
+
+        tool_idx = 0
+
+        n = len(self.tools)
+        self.ui.geo_tools_table.setRowCount(n)
+
+        for tooluid_key, tooluid_value in self.tools.items():
+            tool_idx += 1
+            row_no = tool_idx - 1
+
+            tool_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
+            tool_id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+            self.ui.geo_tools_table.setItem(row_no, 0, tool_id)  # Tool name/id
+
+            # Make sure that the tool diameter when in MM is with no more than 2 decimals.
+            # There are no tool bits in MM with more than 3 decimals diameter.
+            # For INCH the decimals should be no more than 3. There are no tools under 10mils.
+
+            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooluid_value['tooldia'])))
+
+            dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+            offset_item = FCComboBox()
+            for item in self.offset_item_options:
+                offset_item.addItem(item)
+            # offset_item.setStyleSheet('background-color: rgb(255,255,255)')
+            idx = offset_item.findText(tooluid_value['offset'])
+            offset_item.setCurrentIndex(idx)
+
+            type_item = FCComboBox()
+            for item in self.type_item_options:
+                type_item.addItem(item)
+            # type_item.setStyleSheet('background-color: rgb(255,255,255)')
+            idx = type_item.findText(tooluid_value['type'])
+            type_item.setCurrentIndex(idx)
+
+            tool_type_item = FCComboBox()
+            for item in self.tool_type_item_options:
+                tool_type_item.addItem(item)
+                # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
+            idx = tool_type_item.findText(tooluid_value['tool_type'])
+            tool_type_item.setCurrentIndex(idx)
+
+            tool_uid_item = QtWidgets.QTableWidgetItem(str(tooluid_key))
+
+            plot_item = FCCheckBox()
+            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
+            if self.ui.plot_cb.isChecked():
+                plot_item.setChecked(True)
+
+            self.ui.geo_tools_table.setItem(row_no, 1, dia_item)  # Diameter
+            self.ui.geo_tools_table.setCellWidget(row_no, 2, offset_item)
+            self.ui.geo_tools_table.setCellWidget(row_no, 3, type_item)
+            self.ui.geo_tools_table.setCellWidget(row_no, 4, tool_type_item)
+
+            # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY ###
+            self.ui.geo_tools_table.setItem(row_no, 5, tool_uid_item)  # Tool unique ID
+            self.ui.geo_tools_table.setCellWidget(row_no, 6, plot_item)
+
+            try:
+                self.ui.tool_offset_entry.set_value(tooluid_value['offset_value'])
+            except Exception as e:
+                log.debug("build_ui() --> Could not set the 'offset_value' key in self.tools. Error: %s" % str(e))
+
+        # make the diameter column editable
+        for row in range(tool_idx):
+            self.ui.geo_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable |
+                                                          QtCore.Qt.ItemIsEditable |
+                                                          QtCore.Qt.ItemIsEnabled)
+
+        # sort the tool diameter column
+        # self.ui.geo_tools_table.sortItems(1)
+        # all the tools are selected by default
+        # self.ui.geo_tools_table.selectColumn(0)
+
+        self.ui.geo_tools_table.resizeColumnsToContents()
+        self.ui.geo_tools_table.resizeRowsToContents()
+
+        vertical_header = self.ui.geo_tools_table.verticalHeader()
+        # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
+        vertical_header.hide()
+        self.ui.geo_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        horizontal_header = self.ui.geo_tools_table.horizontalHeader()
+        horizontal_header.setMinimumSectionSize(10)
+        horizontal_header.setDefaultSectionSize(70)
+        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(0, 20)
+        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+        # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents)
+        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
+        horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(4, 40)
+        horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(4, 17)
+        # horizontal_header.setStretchLastSection(True)
+        self.ui.geo_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        self.ui.geo_tools_table.setColumnWidth(0, 20)
+        self.ui.geo_tools_table.setColumnWidth(4, 40)
+        self.ui.geo_tools_table.setColumnWidth(6, 17)
+
+        # self.ui.geo_tools_table.setSortingEnabled(True)
+
+        self.ui.geo_tools_table.setMinimumHeight(self.ui.geo_tools_table.getHeight())
+        self.ui.geo_tools_table.setMaximumHeight(self.ui.geo_tools_table.getHeight())
+
+        # update UI for all rows - useful after units conversion but only if there is at least one row
+        row_cnt = self.ui.geo_tools_table.rowCount()
+        if row_cnt > 0:
+            for r in range(row_cnt):
+                self.update_ui(r)
+
+        # select only the first tool / row
+        selected_row = 0
+        try:
+            self.select_tools_table_row(selected_row, clearsel=True)
+            # update the Geometry UI
+            self.update_ui()
+        except Exception as e:
+            # when the tools table is empty there will be this error but once the table is populated it will go away
+            log.debug(str(e))
+
+        # disable the Plot column in Tool Table if the geometry is SingleGeo as it is not needed
+        # and can create some problems
+        if self.multigeo is False:
+            self.ui.geo_tools_table.setColumnHidden(6, True)
+        else:
+            self.ui.geo_tools_table.setColumnHidden(6, False)
+
+        self.set_tool_offset_visibility(selected_row)
+
+        # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
+        # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
+        self.ui.name_entry.set_value(self.options['name'])
+        self.ui_connect()
+
+        self.ui.e_cut_entry.setDisabled(False) if self.ui.extracut_cb.get_value() else \
+            self.ui.e_cut_entry.setDisabled(True)
+
+        # set the text on tool_data_label after loading the object
+        sel_rows = []
+        sel_items = self.ui.geo_tools_table.selectedItems()
+        for it in sel_items:
+            sel_rows.append(it.row())
+        if len(sel_rows) > 1:
+            self.ui.tool_data_label.setText(
+                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
+            )
+
+    def set_ui(self, ui):
+        FlatCAMObj.set_ui(self, ui)
+
+        log.debug("GeometryObject.set_ui()")
+
+        assert isinstance(self.ui, GeometryObjectUI), \
+            "Expected a GeometryObjectUI, got %s" % type(self.ui)
+
+        self.units = self.app.defaults['units'].upper()
+        self.units_found = self.app.defaults['units']
+
+        # populate preprocessor names in the combobox
+        for name in list(self.app.preprocessors.keys()):
+            self.ui.pp_geometry_name_cb.addItem(name)
+
+        self.form_fields.update({
+            "plot": self.ui.plot_cb,
+            "cutz": self.ui.cutz_entry,
+            "vtipdia": self.ui.tipdia_entry,
+            "vtipangle": self.ui.tipangle_entry,
+            "travelz": self.ui.travelz_entry,
+            "feedrate": self.ui.cncfeedrate_entry,
+            "feedrate_z": self.ui.feedrate_z_entry,
+            "feedrate_rapid": self.ui.feedrate_rapid_entry,
+            "spindlespeed": self.ui.cncspindlespeed_entry,
+            "dwell": self.ui.dwell_cb,
+            "dwelltime": self.ui.dwelltime_entry,
+            "multidepth": self.ui.mpass_cb,
+            "ppname_g": self.ui.pp_geometry_name_cb,
+            "z_pdepth": self.ui.pdepth_entry,
+            "feedrate_probe": self.ui.feedrate_probe_entry,
+            "depthperpass": self.ui.maxdepth_entry,
+            "extracut": self.ui.extracut_cb,
+            "extracut_length": self.ui.e_cut_entry,
+            "toolchange": self.ui.toolchangeg_cb,
+            "toolchangez": self.ui.toolchangez_entry,
+            "endz": self.ui.endz_entry,
+            "endxy": self.ui.endxy_entry,
+            "cnctooldia": self.ui.addtool_entry
+        })
+
+        self.param_fields.update({
+            "vtipdia": self.ui.tipdia_entry,
+            "vtipangle": self.ui.tipangle_entry,
+            "cutz": self.ui.cutz_entry,
+            "depthperpass": self.ui.maxdepth_entry,
+            "multidepth": self.ui.mpass_cb,
+            "travelz": self.ui.travelz_entry,
+            "feedrate": self.ui.cncfeedrate_entry,
+            "feedrate_z": self.ui.feedrate_z_entry,
+            "feedrate_rapid": self.ui.feedrate_rapid_entry,
+            "extracut": self.ui.extracut_cb,
+            "extracut_length": self.ui.e_cut_entry,
+            "spindlespeed": self.ui.cncspindlespeed_entry,
+            "dwelltime": self.ui.dwelltime_entry,
+            "dwell": self.ui.dwell_cb,
+            "pdepth": self.ui.pdepth_entry,
+            "pfeedrate": self.ui.feedrate_probe_entry,
+        })
+        # Fill form fields only on object create
+        self.to_form()
+
+        # update the changes in UI depending on the selected preprocessor in Preferences
+        # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the
+        # self.ui.pp_geometry_name_cb combobox
+        self.on_pp_changed()
+
+        self.ui.tipdialabel.hide()
+        self.ui.tipdia_entry.hide()
+        self.ui.tipanglelabel.hide()
+        self.ui.tipangle_entry.hide()
+        self.ui.cutz_entry.setDisabled(False)
+
+        # store here the default data for Geometry Data
+        self.default_data = {}
+        self.default_data.update({
+            "name": None,
+            "plot": None,
+            "cutz": None,
+            "vtipdia": None,
+            "vtipangle": None,
+            "travelz": None,
+            "feedrate": None,
+            "feedrate_z": None,
+            "feedrate_rapid": None,
+            "dwell": None,
+            "dwelltime": None,
+            "multidepth": None,
+            "ppname_g": None,
+            "depthperpass": None,
+            "extracut": None,
+            "extracut_length": None,
+            "toolchange": None,
+            "toolchangez": None,
+            "endz": None,
+            "endxy": '',
+            "spindlespeed": 0,
+            "toolchangexy": None,
+            "startz": None
+        })
+
+        # fill in self.default_data values from self.options
+        for def_key in self.default_data:
+            for opt_key, opt_val in self.options.items():
+                if def_key == opt_key:
+                    self.default_data[def_key] = deepcopy(opt_val)
+
+        if type(self.options["cnctooldia"]) == float:
+            tools_list = [self.options["cnctooldia"]]
+        else:
+            try:
+                temp_tools = self.options["cnctooldia"].split(",")
+                tools_list = [
+                    float(eval(dia)) for dia in temp_tools if dia != ''
+                ]
+            except Exception as e:
+                log.error("GeometryObject.set_ui() -> At least one tool diameter needed. "
+                          "Verify in Edit -> Preferences -> Geometry General -> Tool dia. %s" % str(e))
+                return
+
+        self.tooluid += 1
+
+        if not self.tools:
+            for toold in tools_list:
+                new_data = deepcopy(self.default_data)
+                self.tools.update({
+                    self.tooluid: {
+                        'tooldia': float('%.*f' % (self.decimals, float(toold))),
+                        'offset': 'Path',
+                        'offset_value': 0.0,
+                        'type': _('Rough'),
+                        'tool_type': self.tool_type,
+                        'data': new_data,
+                        'solid_geometry': self.solid_geometry
+                    }
+                })
+                self.tooluid += 1
+        else:
+            # if self.tools is not empty then it can safely be assumed that it comes from an opened project.
+            # Because of the serialization the self.tools list on project save, the dict keys (members of self.tools
+            # are each a dict) are turned into strings so we rebuild the self.tools elements so the keys are
+            # again float type; dict's don't like having keys changed when iterated through therefore the need for the
+            # following convoluted way of changing the keys from string to float type
+            temp_tools = {}
+            for tooluid_key in self.tools:
+                val = deepcopy(self.tools[tooluid_key])
+                new_key = deepcopy(int(tooluid_key))
+                temp_tools[new_key] = val
+
+            self.tools.clear()
+            self.tools = deepcopy(temp_tools)
+
+        self.ui.tool_offset_entry.hide()
+        self.ui.tool_offset_lbl.hide()
+
+        # used to store the state of the mpass_cb if the selected preprocessor for geometry is hpgl
+        self.old_pp_state = self.default_data['multidepth']
+        self.old_toolchangeg_state = self.default_data['toolchange']
+
+        if not isinstance(self.ui, GeometryObjectUI):
+            log.debug("Expected a GeometryObjectUI, got %s" % type(self.ui))
+            return
+
+        self.ui.geo_tools_table.setupContextMenu()
+        self.ui.geo_tools_table.addContextMenu(
+            _("Add from Tool 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"))
+
+        # Show/Hide Advanced Options
+        if self.app.defaults["global_app_level"] == 'b':
+            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
+
+            self.ui.geo_tools_table.setColumnHidden(2, True)
+            self.ui.geo_tools_table.setColumnHidden(3, True)
+            # 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.deltool_btn.hide()
+            # self.ui.endz_label.hide()
+            # self.ui.endz_entry.hide()
+            self.ui.fr_rapidlabel.hide()
+            self.ui.feedrate_rapid_entry.hide()
+            self.ui.extracut_cb.hide()
+            self.ui.e_cut_entry.hide()
+            self.ui.pdepth_label.hide()
+            self.ui.pdepth_entry.hide()
+            self.ui.feedrate_probe_label.hide()
+            self.ui.feedrate_probe_entry.hide()
+        else:
+            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
+
+        self.ui.e_cut_entry.setDisabled(False) if self.app.defaults['geometry_extracut'] else \
+            self.ui.e_cut_entry.setDisabled(True)
+        self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state))
+
+        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
+        self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
+        self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
+        self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
+        self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed)
+
+        self.ui.tipdia_entry.valueChanged.connect(self.update_cutz)
+        self.ui.tipangle_entry.valueChanged.connect(self.update_cutz)
+
+        self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
+        self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
+        self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed)
+
+    def on_cut_z_changed(self):
+        self.old_cutz = self.ui.cutz_entry.get_value()
+
+    def set_tool_offset_visibility(self, current_row):
+        if current_row is None:
+            return
+        try:
+            tool_offset = self.ui.geo_tools_table.cellWidget(current_row, 2)
+            if tool_offset is not None:
+                tool_offset_txt = tool_offset.currentText()
+                if tool_offset_txt == 'Custom':
+                    self.ui.tool_offset_entry.show()
+                    self.ui.tool_offset_lbl.show()
+                else:
+                    self.ui.tool_offset_entry.hide()
+                    self.ui.tool_offset_lbl.hide()
+        except Exception as e:
+            log.debug("set_tool_offset_visibility() --> " + str(e))
+            return
+
+    def on_offset_value_edited(self):
+        """
+        This will save the offset_value into self.tools storage whenever the offset value is edited
+        :return:
+        """
+
+        for current_row in self.ui.geo_tools_table.selectedItems():
+            # sometime the header get selected and it has row number -1
+            # we don't want to do anything with the header :)
+            if current_row.row() < 0:
+                continue
+            tool_uid = int(self.ui.geo_tools_table.item(current_row.row(), 5).text())
+            self.set_tool_offset_visibility(current_row.row())
+
+            for tooluid_key, tooluid_value in self.tools.items():
+                if int(tooluid_key) == tool_uid:
+                    try:
+                        tooluid_value['offset_value'] = float(self.ui.tool_offset_entry.get_value())
+                    except ValueError:
+                        # try to convert comma to decimal point. if it's still not working error message and return
+                        try:
+                            tooluid_value['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."))
+                            return
+
+    def ui_connect(self):
+        # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the
+        # changes in geometry UI
+        for i in self.param_fields:
+            current_widget = self.param_fields[i]
+            if isinstance(current_widget, FCCheckBox):
+                current_widget.stateChanged.connect(self.gui_form_to_storage)
+            elif isinstance(current_widget, FCComboBox):
+                current_widget.currentIndexChanged.connect(self.gui_form_to_storage)
+            elif isinstance(current_widget, FloatEntry) or isinstance(current_widget, LengthEntry) or \
+                    isinstance(current_widget, FCEntry) or isinstance(current_widget, IntEntry):
+                current_widget.editingFinished.connect(self.gui_form_to_storage)
+            elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner):
+                current_widget.returnPressed.connect(self.gui_form_to_storage)
+
+        for row in range(self.ui.geo_tools_table.rowCount()):
+            for col in [2, 3, 4]:
+                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.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
+        self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete())
+
+        # self.ui.geo_tools_table.currentItemChanged.connect(self.on_row_selection_change)
+        self.ui.geo_tools_table.clicked.connect(self.on_row_selection_change)
+        self.ui.geo_tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change)
+
+        self.ui.geo_tools_table.itemChanged.connect(self.on_tool_edit)
+        self.ui.tool_offset_entry.returnPressed.connect(self.on_offset_value_edited)
+
+        for row in range(self.ui.geo_tools_table.rowCount()):
+            self.ui.geo_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table)
+        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
+
+        # common parameters update
+        self.ui.pp_geometry_name_cb.currentIndexChanged.connect(self.update_common_param_in_storage)
+
+    def ui_disconnect(self):
+
+        # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the
+        # changes in geometry UI
+        for i in self.param_fields:
+            # current_widget = self.ui.grid3.itemAt(i).widget()
+            current_widget = self.param_fields[i]
+            if isinstance(current_widget, FCCheckBox):
+                try:
+                    current_widget.stateChanged.disconnect(self.gui_form_to_storage)
+                except (TypeError, AttributeError):
+                    pass
+            elif isinstance(current_widget, FCComboBox):
+                try:
+                    current_widget.currentIndexChanged.disconnect(self.gui_form_to_storage)
+                except (TypeError, AttributeError):
+                    pass
+            elif isinstance(current_widget, LengthEntry) or isinstance(current_widget, IntEntry) or \
+                    isinstance(current_widget, FCEntry) or isinstance(current_widget, FloatEntry):
+                try:
+                    current_widget.editingFinished.disconnect(self.gui_form_to_storage)
+                except (TypeError, AttributeError):
+                    pass
+            elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner):
+                try:
+                    current_widget.returnPressed.disconnect(self.gui_form_to_storage)
+                except TypeError:
+                    pass
+
+        for row in range(self.ui.geo_tools_table.rowCount()):
+            for col in [2, 3, 4]:
+                try:
+                    self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.disconnect()
+                except (TypeError, AttributeError):
+                    pass
+
+        try:
+            self.ui.addtool_btn.clicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.ui.copytool_btn.clicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.ui.deltool_btn.clicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.ui.geo_tools_table.clicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+        try:
+            self.ui.geo_tools_table.horizontalHeader().sectionClicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.ui.geo_tools_table.itemChanged.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.ui.tool_offset_entry.returnPressed.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+        for row in range(self.ui.geo_tools_table.rowCount()):
+            try:
+                self.ui.geo_tools_table.cellWidget(row, 6).clicked.disconnect()
+            except (TypeError, AttributeError):
+                pass
+
+        try:
+            self.ui.plot_cb.stateChanged.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
+    def on_row_selection_change(self):
+        self.update_ui()
+
+    def update_ui(self, row=None):
+        self.ui_disconnect()
+
+        if row is None:
+            sel_rows = []
+            sel_items = self.ui.geo_tools_table.selectedItems()
+            for it in sel_items:
+                sel_rows.append(it.row())
+        else:
+            sel_rows = row if type(row) == list else [row]
+
+        if not sel_rows:
+            sel_rows = [0]
+
+        for current_row in sel_rows:
+            self.set_tool_offset_visibility(current_row)
+
+            # populate the form with the data from the tool associated with the row parameter
+            try:
+                item = self.ui.geo_tools_table.item(current_row, 5)
+                if type(item) is not None:
+                    tooluid = int(item.text())
+                else:
+                    self.ui_connect()
+                    return
+            except Exception as e:
+                log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e))
+                self.ui_connect()
+                return
+
+            # update the QLabel that shows for which Tool we have the parameters in the UI form
+            if len(sel_rows) == 1:
+                self.ui.tool_data_label.setText(
+                    "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), tooluid)
+                )
+
+                # update the form with the V-Shape fields if V-Shape selected in the geo_tool_table
+                # also modify the Cut Z form entry to reflect the calculated Cut Z from values got from V-Shape Fields
+                try:
+                    item = self.ui.geo_tools_table.cellWidget(current_row, 4)
+                    if item is not None:
+                        tool_type_txt = item.currentText()
+                        self.ui_update_v_shape(tool_type_txt=tool_type_txt)
+                    else:
+                        self.ui_connect()
+                        return
+                except Exception as e:
+                    log.debug("Tool missing in ui_update_v_shape(). Add a tool in Geo Tool Table. %s" % str(e))
+                    return
+
+                try:
+                    # set the form with data from the newly selected tool
+                    for tooluid_key, tooluid_value in list(self.tools.items()):
+                        if int(tooluid_key) == tooluid:
+                            for key, value in list(tooluid_value.items()):
+                                if key == 'data':
+                                    form_value_storage = tooluid_value['data']
+                                    self.update_form(form_value_storage)
+                                if key == 'offset_value':
+                                    # update the offset value in the entry even if the entry is hidden
+                                    self.ui.tool_offset_entry.set_value(tooluid_value['offset_value'])
+
+                                if key == 'tool_type' and value == 'V':
+                                    self.update_cutz()
+                except Exception as e:
+                    log.debug("GeometryObject.update_ui() -> %s " % str(e))
+
+            else:
+                self.ui.tool_data_label.setText(
+                    "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
+                )
+
+        self.ui_connect()
+
+    def on_tool_add(self, dia=None):
+        self.ui_disconnect()
+
+        self.units = self.app.defaults['units'].upper()
+
+        if dia is not None:
+            tooldia = dia
+        else:
+            tooldia = float(self.ui.addtool_entry.get_value())
+
+        # construct a list of all 'tooluid' in the self.tools
+        # tool_uid_list = []
+        # for tooluid_key in self.tools:
+        #     tool_uid_list.append(int(tooluid_key))
+        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
+
+        tooldia = float('%.*f' % (self.decimals, tooldia))
+
+        # 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
+        if self.tools:
+            last_data = self.tools[max_uid]['data']
+            last_offset = self.tools[max_uid]['offset']
+            last_offset_value = self.tools[max_uid]['offset_value']
+            last_type = self.tools[max_uid]['type']
+            last_tool_type = self.tools[max_uid]['tool_type']
+            last_solid_geometry = self.tools[max_uid]['solid_geometry']
+
+            # if previous geometry was empty (it may happen for the first tool added)
+            # then copy the object.solid_geometry
+            if not last_solid_geometry:
+                last_solid_geometry = self.solid_geometry
+
+            self.tools.update({
+                self.tooluid: {
+                    'tooldia': tooldia,
+                    'offset': last_offset,
+                    'offset_value': last_offset_value,
+                    'type': last_type,
+                    'tool_type': last_tool_type,
+                    'data': deepcopy(last_data),
+                    'solid_geometry': deepcopy(last_solid_geometry)
+                }
+            })
+        else:
+            self.tools.update({
+                self.tooluid: {
+                    'tooldia': tooldia,
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': _('Rough'),
+                    'tool_type': 'C1',
+                    'data': deepcopy(self.default_data),
+                    'solid_geometry': self.solid_geometry
+                }
+            })
+
+        self.tools[self.tooluid]['data']['name'] = self.options['name']
+
+        self.ui.tool_offset_entry.hide()
+        self.ui.tool_offset_lbl.hide()
+
+        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
+        try:
+            self.ser_attrs.remove('tools')
+        except TypeError:
+            pass
+        self.ser_attrs.append('tools')
+
+        self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
+        self.ui_connect()
+        self.build_ui()
+
+        # if there is no 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)
+
+    def on_tool_add_from_db_clicked(self):
+        """
+        Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object
+        and display the Tools Database tab in the form needed for the Tool adding
+        :return: None
+        """
+
+        # if the Tools Database is already opened focus on it
+        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.setCurrentWidget(self.app.tools_db_tab)
+                break
+        self.app.on_tools_database()
+        self.app.tools_db_tab.ok_to_add = True
+        self.app.tools_db_tab.buttons_frame.hide()
+        self.app.tools_db_tab.add_tool_from_db.show()
+        self.app.tools_db_tab.cancel_tool_from_db.show()
+
+    def on_tool_from_db_inserted(self, tool):
+        """
+        Called from the Tools DB object through a App method when adding a tool from Tools Database
+        :param tool: a dict with the tool data
+        :return: None
+        """
+
+        self.ui_disconnect()
+        self.units = self.app.defaults['units'].upper()
+
+        tooldia = float(tool['tooldia'])
+
+        # construct a list of all 'tooluid' in the self.tools
+        tool_uid_list = []
+        for tooluid_key in self.tools:
+            tool_uid_item = int(tooluid_key)
+            tool_uid_list.append(tool_uid_item)
+
+        # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
+        if not tool_uid_list:
+            max_uid = 0
+        else:
+            max_uid = max(tool_uid_list)
+        self.tooluid = max_uid + 1
+
+        tooldia = float('%.*f' % (self.decimals, tooldia))
+
+        self.tools.update({
+            self.tooluid: {
+                'tooldia': tooldia,
+                'offset': tool['offset'],
+                'offset_value': float(tool['offset_value']),
+                'type': tool['type'],
+                'tool_type': tool['tool_type'],
+                'data': deepcopy(tool['data']),
+                'solid_geometry': self.solid_geometry
+            }
+        })
+
+        self.tools[self.tooluid]['data']['name'] = self.options['name']
+
+        self.ui.tool_offset_entry.hide()
+        self.ui.tool_offset_lbl.hide()
+
+        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
+        try:
+            self.ser_attrs.remove('tools')
+        except TypeError:
+            pass
+        self.ser_attrs.append('tools')
+
+        self.ui_connect()
+        self.build_ui()
+
+        # if there is no 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)
+
+    def on_tool_copy(self, all_tools=None):
+        self.ui_disconnect()
+
+        # find the tool_uid maximum value in the self.tools
+        uid_list = []
+        for key in self.tools:
+            uid_list.append(int(key))
+        try:
+            max_uid = max(uid_list, key=int)
+        except ValueError:
+            max_uid = 0
+
+        if all_tools is None:
+            if self.ui.geo_tools_table.selectedItems():
+                for current_row in self.ui.geo_tools_table.selectedItems():
+                    # sometime the header get selected and it has row number -1
+                    # we don't want to do anything with the header :)
+                    if current_row.row() < 0:
+                        continue
+                    try:
+                        tooluid_copy = int(self.ui.geo_tools_table.item(current_row.row(), 5).text())
+                        self.set_tool_offset_visibility(current_row.row())
+                        max_uid += 1
+                        self.tools[int(max_uid)] = deepcopy(self.tools[tooluid_copy])
+                    except AttributeError:
+                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy."))
+                        self.ui_connect()
+                        self.build_ui()
+                        return
+                    except Exception as e:
+                        log.debug("on_tool_copy() --> " + str(e))
+                # deselect the table
+                # self.ui.geo_tools_table.clearSelection()
+            else:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy."))
+                self.ui_connect()
+                self.build_ui()
+                return
+        else:
+            # we copy all tools in geo_tools_table
+            try:
+                temp_tools = deepcopy(self.tools)
+                max_uid += 1
+                for tooluid in temp_tools:
+                    self.tools[int(max_uid)] = deepcopy(temp_tools[tooluid])
+                temp_tools.clear()
+            except Exception as e:
+                log.debug("on_tool_copy() --> " + str(e))
+
+        # if there are no more tools in geo tools table then hide the tool offset
+        if not self.tools:
+            self.ui.tool_offset_entry.hide()
+            self.ui.tool_offset_lbl.hide()
+
+        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
+        try:
+            self.ser_attrs.remove('tools')
+        except ValueError:
+            pass
+        self.ser_attrs.append('tools')
+
+        self.ui_connect()
+        self.build_ui()
+        self.app.inform.emit('[success] %s' % _("Tool was copied in Tool Table."))
+
+    def on_tool_edit(self, current_item):
+        self.ui_disconnect()
+
+        current_row = current_item.row()
+        try:
+            d = float(self.ui.geo_tools_table.item(current_row, 1).text())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                d = float(self.ui.geo_tools_table.item(current_row, 1).text().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
+                return
+
+        tool_dia = float('%.*f' % (self.decimals, d))
+        tooluid = int(self.ui.geo_tools_table.item(current_row, 5).text())
+
+        self.tools[tooluid]['tooldia'] = tool_dia
+
+        try:
+            self.ser_attrs.remove('tools')
+            self.ser_attrs.append('tools')
+        except (TypeError, ValueError):
+            pass
+
+        self.app.inform.emit('[success] %s' % _("Tool was edited in Tool Table."))
+        self.ui_connect()
+        self.build_ui()
+
+    def on_tool_delete(self, all_tools=None):
+        self.ui_disconnect()
+
+        if all_tools is None:
+            if self.ui.geo_tools_table.selectedItems():
+                for current_row in self.ui.geo_tools_table.selectedItems():
+                    # sometime the header get selected and it has row number -1
+                    # we don't want to do anything with the header :)
+                    if current_row.row() < 0:
+                        continue
+                    try:
+                        tooluid_del = int(self.ui.geo_tools_table.item(current_row.row(), 5).text())
+                        self.set_tool_offset_visibility(current_row.row())
+
+                        temp_tools = deepcopy(self.tools)
+                        for tooluid_key in self.tools:
+                            if int(tooluid_key) == tooluid_del:
+                                # if the self.tools has only one tool and we delete it then we move the solid_geometry
+                                # as a property of the object otherwise there will be nothing to hold it
+                                if len(self.tools) == 1:
+                                    self.solid_geometry = deepcopy(self.tools[tooluid_key]['solid_geometry'])
+                                temp_tools.pop(tooluid_del, None)
+                        self.tools = deepcopy(temp_tools)
+                        temp_tools.clear()
+                    except AttributeError:
+                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete."))
+                        self.ui_connect()
+                        self.build_ui()
+                        return
+                    except Exception as e:
+                        log.debug("on_tool_delete() --> " + str(e))
+                # deselect the table
+                # self.ui.geo_tools_table.clearSelection()
+            else:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete."))
+                self.ui_connect()
+                self.build_ui()
+                return
+        else:
+            # we delete all tools in geo_tools_table
+            self.tools.clear()
+
+        self.app.plot_all()
+
+        # if there are no more tools in geo tools table then hide the tool offset
+        if not self.tools:
+            self.ui.tool_offset_entry.hide()
+            self.ui.tool_offset_lbl.hide()
+
+        # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list
+        try:
+            self.ser_attrs.remove('tools')
+        except TypeError:
+            pass
+        self.ser_attrs.append('tools')
+
+        self.ui_connect()
+        self.build_ui()
+        self.app.inform.emit('[success] %s' % _("Tool was deleted in Tool Table."))
+
+        obj_active = self.app.collection.get_active()
+        # if the object was MultiGeo and now it has no tool at all (therefore no geometry)
+        # we make it back SingleGeo
+        if self.ui.geo_tools_table.rowCount() <= 0:
+            obj_active.multigeo = False
+            obj_active.options['xmin'] = 0
+            obj_active.options['ymin'] = 0
+            obj_active.options['xmax'] = 0
+            obj_active.options['ymax'] = 0
+
+        if obj_active.multigeo is True:
+            try:
+                xmin, ymin, xmax, ymax = obj_active.bounds()
+                obj_active.options['xmin'] = xmin
+                obj_active.options['ymin'] = ymin
+                obj_active.options['xmax'] = xmax
+                obj_active.options['ymax'] = ymax
+            except Exception:
+                obj_active.options['xmin'] = 0
+                obj_active.options['ymin'] = 0
+                obj_active.options['xmax'] = 0
+                obj_active.options['ymax'] = 0
+
+        # if there is no tool left in the Tools Table, disable the parameters GUI
+        if self.ui.geo_tools_table.rowCount() == 0:
+            self.ui.geo_param_frame.setDisabled(True)
+
+    def ui_update_v_shape(self, tool_type_txt):
+        if tool_type_txt == 'V':
+            self.ui.tipdialabel.show()
+            self.ui.tipdia_entry.show()
+            self.ui.tipanglelabel.show()
+            self.ui.tipangle_entry.show()
+            self.ui.cutz_entry.setDisabled(True)
+
+            self.update_cutz()
+        else:
+            self.ui.tipdialabel.hide()
+            self.ui.tipdia_entry.hide()
+            self.ui.tipanglelabel.hide()
+            self.ui.tipangle_entry.hide()
+            self.ui.cutz_entry.setDisabled(False)
+
+    def update_cutz(self):
+        vdia = float(self.ui.tipdia_entry.get_value())
+        half_vangle = float(self.ui.tipangle_entry.get_value()) / 2
+
+        row = self.ui.geo_tools_table.currentRow()
+        tool_uid_item = self.ui.geo_tools_table.item(row, 5)
+        if tool_uid_item is None:
+            return
+        tool_uid = int(tool_uid_item.text())
+
+        tool_dia_item = self.ui.geo_tools_table.item(row, 1)
+        if tool_dia_item is None:
+            return
+        tooldia = float(tool_dia_item.text())
+
+        try:
+            new_cutz = (tooldia - vdia) / (2 * math.tan(math.radians(half_vangle)))
+        except ZeroDivisionError:
+            new_cutz = self.old_cutz
+
+        new_cutz = float('%.*f' % (self.decimals, new_cutz)) * -1.0   # this value has to be negative
+
+        self.ui.cutz_entry.set_value(new_cutz)
+
+        # store the new CutZ value into storage (self.tools)
+        for tooluid_key, tooluid_value in self.tools.items():
+            if int(tooluid_key) == tool_uid:
+                tooluid_value['data']['cutz'] = new_cutz
+
+    def on_tooltable_cellwidget_change(self):
+        cw = self.sender()
+        # assert isinstance(cw, FCComboBox) or isinstance(cw, FCCheckBox),\
+        #     "Expected a FCCombobox or a FCCheckbox got %s" % type(cw)
+        cw_index = self.ui.geo_tools_table.indexAt(cw.pos())
+        cw_row = cw_index.row()
+        cw_col = cw_index.column()
+        current_uid = int(self.ui.geo_tools_table.item(cw_row, 5).text())
+
+        # store the text of the cellWidget that changed it's index in the self.tools
+        for tooluid_key, tooluid_value in self.tools.items():
+            if int(tooluid_key) == current_uid:
+                cb_txt = cw.currentText()
+                if cw_col == 2:
+                    tooluid_value['offset'] = cb_txt
+                    if cb_txt == 'Custom':
+                        self.ui.tool_offset_entry.show()
+                        self.ui.tool_offset_lbl.show()
+                    else:
+                        self.ui.tool_offset_entry.hide()
+                        self.ui.tool_offset_lbl.hide()
+                        # reset the offset_value in storage self.tools
+                        tooluid_value['offset_value'] = 0.0
+                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'))
+                        self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx)
+                    else:
+                        tooluid_value['type'] = cb_txt
+                elif cw_col == 4:
+                    tooluid_value['tool_type'] = cb_txt
+
+                    # 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'))
+                        self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx)
+                    else:
+                        self.ui.cutz_entry.set_value(self.old_cutz)
+
+                self.ui_update_v_shape(tool_type_txt=self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText())
+
+    def update_form(self, dict_storage):
+        for form_key in self.form_fields:
+            for storage_key in dict_storage:
+                if form_key == storage_key:
+                    try:
+                        self.form_fields[form_key].set_value(dict_storage[form_key])
+                    except Exception as e:
+                        log.debug(str(e))
+
+        # this is done here because those buttons control through OptionalInputSelection if some entry's are Enabled
+        # or not. But due of using the ui_disconnect() status is no longer updated and I had to do it here
+        self.ui.ois_dwell_geo.on_cb_change()
+        self.ui.ois_mpass_geo.on_cb_change()
+        self.ui.ois_tcz_geo.on_cb_change()
+
+    def on_apply_param_to_all_clicked(self):
+        if self.ui.geo_tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            log.debug("GeometryObject.gui_form_to_storage() --> no tool in Tools Table, aborting.")
+            return
+
+        self.ui_disconnect()
+
+        row = self.ui.geo_tools_table.currentRow()
+        if row < 0:
+            row = 0
+
+        # store all the data associated with the row parameter to the self.tools storage
+        tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text())
+        offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText()
+        type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText()
+        tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText()
+
+        offset_value_item = float(self.ui.tool_offset_entry.get_value())
+
+        # this new dict will hold the actual useful data, another dict that is the value of key 'data'
+        temp_tools = {}
+        temp_dia = {}
+        temp_data = {}
+
+        for tooluid_key, tooluid_value in self.tools.items():
+            for key, value in tooluid_value.items():
+                if key == 'tooldia':
+                    temp_dia[key] = tooldia_item
+                # update the 'offset', 'type' and 'tool_type' sections
+                if key == 'offset':
+                    temp_dia[key] = offset_item
+                if key == 'type':
+                    temp_dia[key] = type_item
+                if key == 'tool_type':
+                    temp_dia[key] = tool_type_item
+                if key == 'offset_value':
+                    temp_dia[key] = offset_value_item
+
+                if key == 'data':
+                    # update the 'data' section
+                    for data_key in tooluid_value[key].keys():
+                        for form_key, form_value in self.form_fields.items():
+                            if form_key == data_key:
+                                temp_data[data_key] = form_value.get_value()
+                        # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
+                        # updated from self.app.defaults
+                        if data_key not in self.form_fields:
+                            temp_data[data_key] = value[data_key]
+                    temp_dia[key] = deepcopy(temp_data)
+                    temp_data.clear()
+
+                if key == 'solid_geometry':
+                    temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry'])
+
+                temp_tools[tooluid_key] = deepcopy(temp_dia)
+
+        self.tools.clear()
+        self.tools = deepcopy(temp_tools)
+        temp_tools.clear()
+
+        self.ui_connect()
+
+    def gui_form_to_storage(self):
+        if self.ui.geo_tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            log.debug("GeometryObject.gui_form_to_storage() --> no tool in Tools Table, aborting.")
+            return
+
+        self.ui_disconnect()
+        widget_changed = self.sender()
+        try:
+            widget_idx = self.ui.grid3.indexOf(widget_changed)
+        except Exception:
+            return
+
+        # those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z
+        if widget_idx == 1 or widget_idx == 3:
+            self.update_cutz()
+
+        # the original connect() function of the OptionalInputSelection is no longer working because of the
+        # ui_diconnect() so I use this 'hack'
+        if isinstance(widget_changed, FCCheckBox):
+            if widget_changed.text() == 'Multi-Depth:':
+                self.ui.ois_mpass_geo.on_cb_change()
+
+            if widget_changed.text() == 'Tool change':
+                self.ui.ois_tcz_geo.on_cb_change()
+
+            if widget_changed.text() == 'Dwell:':
+                self.ui.ois_dwell_geo.on_cb_change()
+
+        row = self.ui.geo_tools_table.currentRow()
+        if row < 0:
+            row = 0
+
+        # store all the data associated with the row parameter to the self.tools storage
+        tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text())
+        offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText()
+        type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText()
+        tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText()
+        tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text())
+
+        offset_value_item = float(self.ui.tool_offset_entry.get_value())
+
+        # this new dict will hold the actual useful data, another dict that is the value of key 'data'
+        temp_tools = {}
+        temp_dia = {}
+        temp_data = {}
+
+        for tooluid_key, tooluid_value in self.tools.items():
+            if int(tooluid_key) == tooluid_item:
+                for key, value in tooluid_value.items():
+                    if key == 'tooldia':
+                        temp_dia[key] = tooldia_item
+                    # update the 'offset', 'type' and 'tool_type' sections
+                    if key == 'offset':
+                        temp_dia[key] = offset_item
+                    if key == 'type':
+                        temp_dia[key] = type_item
+                    if key == 'tool_type':
+                        temp_dia[key] = tool_type_item
+                    if key == 'offset_value':
+                        temp_dia[key] = offset_value_item
+
+                    if key == 'data':
+                        # update the 'data' section
+                        for data_key in tooluid_value[key].keys():
+                            for form_key, form_value in self.form_fields.items():
+                                if form_key == data_key:
+                                    temp_data[data_key] = form_value.get_value()
+                            # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
+                            # updated from self.app.defaults
+                            if data_key not in self.form_fields:
+                                temp_data[data_key] = value[data_key]
+                        temp_dia[key] = deepcopy(temp_data)
+                        temp_data.clear()
+
+                    if key == 'solid_geometry':
+                        temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry'])
+
+                    temp_tools[tooluid_key] = deepcopy(temp_dia)
+            else:
+                temp_tools[tooluid_key] = deepcopy(tooluid_value)
+
+        self.tools.clear()
+        self.tools = deepcopy(temp_tools)
+        temp_tools.clear()
+        self.ui_connect()
+
+    def update_common_param_in_storage(self):
+        for tooluid_value in self.tools.values():
+            tooluid_value['data']['ppname_g'] = self.ui.pp_geometry_name_cb.get_value()
+
+    def select_tools_table_row(self, row, clearsel=None):
+        if clearsel:
+            self.ui.geo_tools_table.clearSelection()
+
+        if self.ui.geo_tools_table.rowCount() > 0:
+            # self.ui.geo_tools_table.item(row, 0).setSelected(True)
+            self.ui.geo_tools_table.setCurrentItem(self.ui.geo_tools_table.item(row, 0))
+
+    def export_dxf(self):
+        dwg = None
+        try:
+            dwg = ezdxf.new('R2010')
+            msp = dwg.modelspace()
+
+            def g2dxf(dxf_space, geo_obj):
+                if isinstance(geo_obj, MultiPolygon):
+                    for poly in geo_obj:
+                        ext_points = list(poly.exterior.coords)
+                        dxf_space.add_lwpolyline(ext_points)
+                        for interior in poly.interiors:
+                            dxf_space.add_lwpolyline(list(interior.coords))
+                if isinstance(geo_obj, Polygon):
+                    ext_points = list(geo_obj.exterior.coords)
+                    dxf_space.add_lwpolyline(ext_points)
+                    for interior in geo_obj.interiors:
+                        dxf_space.add_lwpolyline(list(interior.coords))
+                if isinstance(geo_obj, MultiLineString):
+                    for line in geo_obj:
+                        dxf_space.add_lwpolyline(list(line.coords))
+                if isinstance(geo_obj, LineString) or isinstance(geo_obj, LinearRing):
+                    dxf_space.add_lwpolyline(list(geo_obj.coords))
+
+            multigeo_solid_geometry = []
+            if self.multigeo:
+                for tool in self.tools:
+                    multigeo_solid_geometry += self.tools[tool]['solid_geometry']
+            else:
+                multigeo_solid_geometry = self.solid_geometry
+
+            for geo in multigeo_solid_geometry:
+                if type(geo) == list:
+                    for g in geo:
+                        g2dxf(msp, g)
+                else:
+                    g2dxf(msp, geo)
+
+                # points = GeometryObject.get_pts(geo)
+                # msp.add_lwpolyline(points)
+        except Exception as e:
+            log.debug(str(e))
+
+        return dwg
+
+    def get_selected_tools_table_items(self):
+        """
+        Returns a list of lists, each list in the list is made out of row elements
+
+        :return: List of table_tools items.
+        :rtype: list
+        """
+        table_tools_items = []
+        if self.multigeo:
+            for x in self.ui.geo_tools_table.selectedItems():
+                elem = []
+                txt = ''
+
+                for column in range(0, self.ui.geo_tools_table.columnCount()):
+                    try:
+                        txt = self.ui.geo_tools_table.item(x.row(), column).text()
+                    except AttributeError:
+                        try:
+                            txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText()
+                        except AttributeError:
+                            pass
+                    elem.append(txt)
+                table_tools_items.append(deepcopy(elem))
+                # table_tools_items.append([self.ui.geo_tools_table.item(x.row(), column).text()
+                #                           for column in range(0, self.ui.geo_tools_table.columnCount())])
+        else:
+            for x in self.ui.geo_tools_table.selectedItems():
+                r = []
+                txt = ''
+
+                # the last 2 columns for single-geo geometry are irrelevant and create problems reading
+                # so we don't read them
+                for column in range(0, self.ui.geo_tools_table.columnCount() - 2):
+                    # the columns have items that have text but also have items that are widgets
+                    # for which the text they hold has to be read differently
+                    try:
+                        txt = self.ui.geo_tools_table.item(x.row(), column).text()
+                    except AttributeError:
+                        try:
+                            txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText()
+                        except AttributeError:
+                            pass
+                    r.append(txt)
+                table_tools_items.append(r)
+
+        for item in table_tools_items:
+            item[0] = str(item[0])
+        return table_tools_items
+
+    def on_pp_changed(self):
+        current_pp = self.ui.pp_geometry_name_cb.get_value()
+        if current_pp == 'hpgl':
+            self.old_pp_state = self.ui.mpass_cb.get_value()
+            self.old_toolchangeg_state = self.ui.toolchangeg_cb.get_value()
+
+            self.ui.mpass_cb.set_value(False)
+            self.ui.mpass_cb.setDisabled(True)
+
+            self.ui.toolchangeg_cb.set_value(True)
+            self.ui.toolchangeg_cb.setDisabled(True)
+        else:
+            self.ui.mpass_cb.set_value(self.old_pp_state)
+            self.ui.mpass_cb.setDisabled(False)
+
+            self.ui.toolchangeg_cb.set_value(self.old_toolchangeg_state)
+            self.ui.toolchangeg_cb.setDisabled(False)
+
+        if "toolchange_probe" in current_pp.lower():
+            self.ui.pdepth_entry.setVisible(True)
+            self.ui.pdepth_label.show()
+
+            self.ui.feedrate_probe_entry.setVisible(True)
+            self.ui.feedrate_probe_label.show()
+        else:
+            self.ui.pdepth_entry.setVisible(False)
+            self.ui.pdepth_label.hide()
+
+            self.ui.feedrate_probe_entry.setVisible(False)
+            self.ui.feedrate_probe_label.hide()
+
+        if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
+            self.ui.fr_rapidlabel.show()
+            self.ui.feedrate_rapid_entry.show()
+        else:
+            self.ui.fr_rapidlabel.hide()
+            self.ui.feedrate_rapid_entry.hide()
+
+        if 'laser' in current_pp.lower():
+            self.ui.cutzlabel.hide()
+            self.ui.cutz_entry.hide()
+            try:
+                self.ui.mpass_cb.hide()
+                self.ui.maxdepth_entry.hide()
+            except AttributeError:
+                pass
+
+            if 'marlin' in current_pp.lower():
+                self.ui.travelzlabel.setText('%s:' % _("Focus Z"))
+                self.ui.endz_label.show()
+                self.ui.endz_entry.show()
+            else:
+                self.ui.travelzlabel.hide()
+                self.ui.travelz_entry.hide()
+
+                self.ui.endz_label.hide()
+                self.ui.endz_entry.hide()
+
+            try:
+                self.ui.frzlabel.hide()
+                self.ui.feedrate_z_entry.hide()
+            except AttributeError:
+                pass
+
+            self.ui.dwell_cb.hide()
+            self.ui.dwelltime_entry.hide()
+
+            self.ui.spindle_label.setText('%s:' % _("Laser Power"))
+
+            try:
+                self.ui.tool_offset_label.hide()
+                self.ui.offset_entry.hide()
+            except AttributeError:
+                pass
+        else:
+            self.ui.cutzlabel.show()
+            self.ui.cutz_entry.show()
+            try:
+                self.ui.mpass_cb.show()
+                self.ui.maxdepth_entry.show()
+            except AttributeError:
+                pass
+
+            self.ui.travelzlabel.setText('%s:' % _('Travel Z'))
+
+            self.ui.travelzlabel.show()
+            self.ui.travelz_entry.show()
+
+            self.ui.endz_label.show()
+            self.ui.endz_entry.show()
+
+            try:
+                self.ui.frzlabel.show()
+                self.ui.feedrate_z_entry.show()
+            except AttributeError:
+                pass
+            self.ui.dwell_cb.show()
+            self.ui.dwelltime_entry.show()
+
+            self.ui.spindle_label.setText('%s:' % _('Spindle speed'))
+
+            try:
+                self.ui.tool_offset_lbl.show()
+                self.ui.offset_entry.show()
+            except AttributeError:
+                pass
+
+    def on_generatecnc_button_click(self, *args):
+        log.debug("Generating CNCJob from Geometry ...")
+        self.app.report_usage("geometry_on_generatecnc_button")
+
+        # this reads the values in the UI form to the self.options dictionary
+        self.read_form()
+
+        self.sel_tools = {}
+
+        try:
+            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"))
+                )
+                return
+        except AttributeError:
+            pass
+
+        # test to see if we have tools available in the tool table
+        if self.ui.geo_tools_table.selectedItems():
+            for x in self.ui.geo_tools_table.selectedItems():
+                # try:
+                #     tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text())
+                # except ValueError:
+                #     # try to convert comma to decimal point. if it's still not working error message and return
+                #     try:
+                #         tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text().replace(',', '.'))
+                #     except ValueError:
+                #         self.app.inform.emit('[ERROR_NOTCL] %s' %
+                #                              _("Wrong value format entered, use a number."))
+                #         return
+                tooluid = int(self.ui.geo_tools_table.item(x.row(), 5).text())
+
+                for tooluid_key, tooluid_value in self.tools.items():
+                    if int(tooluid_key) == tooluid:
+                        self.sel_tools.update({
+                            tooluid: deepcopy(tooluid_value)
+                        })
+            self.mtool_gen_cncjob()
+            self.ui.geo_tools_table.clearSelection()
+
+        elif self.ui.geo_tools_table.rowCount() == 1:
+            tooluid = int(self.ui.geo_tools_table.item(0, 5).text())
+
+            for tooluid_key, tooluid_value in self.tools.items():
+                if int(tooluid_key) == tooluid:
+                    self.sel_tools.update({
+                        tooluid: deepcopy(tooluid_value)
+                    })
+            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 ..."))
+
+    def mtool_gen_cncjob(self, outname=None, tools_dict=None, tools_in_use=None, segx=None, segy=None,
+                         plot=True, use_thread=True):
+        """
+        Creates a multi-tool CNCJob out of this Geometry object.
+        The actual work is done by the target CNCJobObject object's
+        `generate_from_geometry_2()` method.
+
+        :param tools_dict: a dictionary that holds the whole data needed to create the Gcode
+        (including the solid_geometry)
+
+        :param tools_in_use: the tools that are used, needed by some preprocessors
+        :type list of lists, each list in the list is made out of row elements of tools table from GUI
+
+        :param outname:
+        :param tools_dict:
+        :param tools_in_use:
+        :param segx:            number of segments on the X axis, for auto-levelling
+        :param segy:            number of segments on the Y axis, for auto-levelling
+        :param plot:            if True the generated object will be plotted; if False will not be plotted
+        :param use_thread:      if True use threading
+        :return:                None
+        """
+
+        # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
+        outname = "%s_%s" % (self.options["name"], 'cnc') if outname is None else outname
+
+        tools_dict = self.sel_tools if tools_dict is None else tools_dict
+        tools_in_use = tools_in_use if tools_in_use is not None else self.get_selected_tools_table_items()
+        segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
+        segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
+
+        try:
+            xmin = self.options['xmin']
+            ymin = self.options['ymin']
+            xmax = self.options['xmax']
+            ymax = self.options['ymax']
+        except Exception as e:
+            log.debug("FlatCAMObj.GeometryObject.mtool_gen_cncjob() --> %s\n" % str(e))
+
+            msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
+            msg += '%s %s' % ('FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->', str(e))
+            msg += traceback.format_exc()
+            self.app.inform.emit(msg)
+            return
+
+        # Object initialization function for app.new_object()
+        # RUNNING ON SEPARATE THREAD!
+        def job_init_single_geometry(job_obj, app_obj):
+            log.debug("Creating a CNCJob out of a single-geometry")
+            assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
+
+            job_obj.options['xmin'] = xmin
+            job_obj.options['ymin'] = ymin
+            job_obj.options['xmax'] = xmax
+            job_obj.options['ymax'] = ymax
+
+            # count the tools
+            tool_cnt = 0
+
+            dia_cnc_dict = {}
+
+            # this turn on the FlatCAMCNCJob plot for multiple tools
+            job_obj.multitool = True
+            job_obj.multigeo = False
+            job_obj.cnc_tools.clear()
+
+            job_obj.options['Tools_in_use'] = tools_in_use
+            job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"])
+            job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"])
+
+            job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"])
+            job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"])
+
+            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'])))
+                dia_cnc_dict.update({
+                    'tooldia': tooldia_val
+                })
+
+                if dia_cnc_dict['offset'] == 'in':
+                    tool_offset = -dia_cnc_dict['tooldia'] / 2
+                elif dia_cnc_dict['offset'].lower() == 'out':
+                    tool_offset = dia_cnc_dict['tooldia'] / 2
+                elif dia_cnc_dict['offset'].lower() == 'custom':
+                    try:
+                        offset_value = float(self.ui.tool_offset_entry.get_value())
+                    except ValueError:
+                        # try to convert comma to decimal point. if it's still not working error message and return
+                        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."))
+                            return
+                    if offset_value:
+                        tool_offset = float(offset_value)
+                    else:
+                        self.app.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.")
+                        )
+                        return
+                else:
+                    tool_offset = 0.0
+
+                dia_cnc_dict.update({
+                    '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']
+                tool_solid_geometry = self.solid_geometry
+
+                job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
+                job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
+
+                # Propagate options
+                job_obj.options["tooldia"] = tooldia_val
+                job_obj.options['type'] = 'Geometry'
+                job_obj.options['tool_dia'] = tooldia_val
+
+                # 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_val, offset=tool_offset, tolerance=tol,
+                    z_cut=z_cut, z_move=z_move,
+                    feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
+                    spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
+                    multidepth=multidepth, depthpercut=depthpercut,
+                    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)
+
+                if res == 'fail':
+                    log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed")
+                    return 'fail'
+                else:
+                    dia_cnc_dict['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"
+
+                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..."))
+
+                # 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'] = cascaded_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"))
+                except Exception as er:
+                    self.app.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()
+
+        # Object initialization function for app.new_object()
+        # RUNNING ON SEPARATE THREAD!
+        def job_init_multi_geometry(job_obj, app_obj):
+            log.debug("Creating a CNCJob out of a multi-geometry")
+            assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
+
+            job_obj.options['xmin'] = xmin
+            job_obj.options['ymin'] = ymin
+            job_obj.options['xmax'] = xmax
+            job_obj.options['ymax'] = ymax
+
+            # count the tools
+            tool_cnt = 0
+
+            dia_cnc_dict = {}
+
+            # this turn on the FlatCAMCNCJob plot for multiple tools
+            job_obj.multitool = True
+            job_obj.multigeo = True
+            job_obj.cnc_tools.clear()
+
+            job_obj.options['Tools_in_use'] = tools_in_use
+            job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"])
+            job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"])
+
+            job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"])
+            job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"])
+
+            # make sure that trying to make a CNCJob from an empty file is not creating an app crash
+            if not self.solid_geometry:
+                a = 0
+                for tooluid_key in self.tools:
+                    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'))
+                    return 'fail'
+
+            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'])))
+
+                dia_cnc_dict.update({
+                    'tooldia': tooldia_val
+                })
+
+                # find the tool_dia associated with the tooluid_key
+                # search in the self.tools for the sel_tool_dia and when found see what tooluid has
+                # on the found tooluid in self.tools we also have the solid_geometry that interest us
+                # for k, v in self.tools.items():
+                #     if float('%.*f' % (self.decimals, float(v['tooldia']))) == tooldia_val:
+                #         current_uid = int(k)
+                #         break
+
+                if dia_cnc_dict['offset'] == 'in':
+                    tool_offset = -tooldia_val / 2
+                elif dia_cnc_dict['offset'].lower() == 'out':
+                    tool_offset = tooldia_val / 2
+                elif dia_cnc_dict['offset'].lower() == 'custom':
+                    offset_value = float(self.ui.tool_offset_entry.get_value())
+                    if offset_value:
+                        tool_offset = float(offset_value)
+                    else:
+                        self.app.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."))
+                        return
+                else:
+                    tool_offset = 0.0
+
+                dia_cnc_dict.update({
+                    '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']
+                tool_solid_geometry = self.tools[tooluid_key]['solid_geometry']
+
+                job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
+                job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
+
+                # Propagate options
+                job_obj.options["tooldia"] = tooldia_val
+                job_obj.options['type'] = 'Geometry'
+                job_obj.options['tool_dia'] = tooldia_val
+
+                # 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_multitool_geometry(
+                    tool_solid_geometry, 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,
+                    spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
+                    multidepth=multidepth, depthpercut=depthpercut,
+                    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)
+
+                if res == 'fail':
+                    log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed")
+                    return 'fail'
+                else:
+                    dia_cnc_dict['gcode'] = res
+
+                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..."))
+
+                # 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 = cascaded_union([
+                #     geo['geom'] for geo in dia_cnc_dict['gcode_parsed'] if geo['geom'].is_valid is True
+                # ])
+                try:
+                    dia_cnc_dict['solid_geometry'] = deepcopy(tool_solid_geometry)
+                    self.app.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)))
+
+                # 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.cnc_tools.update({
+                    tooluid_key: deepcopy(dia_cnc_dict)
+                })
+                dia_cnc_dict.clear()
+
+        if use_thread:
+            # To be run in separate thread
+            def job_thread(app_obj):
+                if self.multigeo is False:
+                    with self.app.proc_container.new(_("Generating CNC Code")):
+                        if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
+                            app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
+                else:
+                    with self.app.proc_container.new(_("Generating CNC Code")):
+                        if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
+                            app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
+
+            # Create a promise with the name
+            self.app.collection.promise(outname)
+            # Send to worker
+            self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+        else:
+            if self.solid_geometry:
+                self.app.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
+            else:
+                self.app.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
+
+    def generatecncjob(
+            self, outname=None,
+            dia=None, offset=None,
+            z_cut=None, z_move=None,
+            feedrate=None, feedrate_z=None, feedrate_rapid=None,
+            spindlespeed=None, dwell=None, dwelltime=None,
+            multidepth=None, depthperpass=None,
+            toolchange=None, toolchangez=None, toolchangexy=None,
+            extracut=None, extracut_length=None, startz=None, endz=None,
+            pp=None,
+            segx=None, segy=None,
+            use_thread=True,
+            plot=True):
+        """
+        Only used for TCL Command.
+        Creates a CNCJob out of this Geometry object. The actual
+        work is done by the target camlib.CNCjob
+        `generate_from_geometry_2()` method.
+
+        :param outname:         Name of the new object
+        :param dia:             Tool diameter
+        :param offset:
+        :param z_cut:           Cut depth (negative value)
+        :param z_move:          Height of the tool when travelling (not cutting)
+        :param feedrate:        Feed rate while cutting on X - Y plane
+        :param feedrate_z:      Feed rate while cutting on Z plane
+        :param feedrate_rapid:  Feed rate while moving with rapids
+        :param spindlespeed:    Spindle speed (RPM)
+        :param dwell:
+        :param dwelltime:
+        :param multidepth:
+        :param depthperpass:
+        :param toolchange:
+        :param toolchangez:
+        :param toolchangexy:
+        :param extracut:
+        :param extracut_length:
+        :param startz:
+        :param endz:
+        :param pp:              Name of the preprocessor
+        :param segx:
+        :param segy:
+        :param use_thread:
+        :param plot:
+        :return: None
+        """
+
+        tooldia = dia if dia else float(self.options["cnctooldia"])
+        outname = outname if outname is not None else self.options["name"]
+
+        z_cut = z_cut if z_cut is not None else float(self.options["cutz"])
+        z_move = z_move if z_move is not None else float(self.options["travelz"])
+
+        feedrate = feedrate if feedrate is not None else float(self.options["feedrate"])
+        feedrate_z = feedrate_z if feedrate_z is not None else float(self.options["feedrate_z"])
+        feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"])
+
+        multidepth = multidepth if multidepth is not None else self.options["multidepth"]
+        depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"])
+
+        segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
+        segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
+
+        extracut = extracut if extracut is not None else float(self.options["extracut"])
+        extracut_length = extracut_length if extracut_length is not None else float(self.options["extracut_length"])
+
+        startz = startz if startz is not None else self.options["startz"]
+        endz = endz if endz is not None else float(self.options["endz"])
+        endxy = self.options["endxy"]
+
+        toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
+        toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
+        toolchange = toolchange if toolchange else self.options["toolchange"]
+
+        offset = offset if offset else 0.0
+
+        # int or None.
+        spindlespeed = spindlespeed if spindlespeed else self.options['spindlespeed']
+        dwell = dwell if dwell else self.options["dwell"]
+        dwelltime = dwelltime if dwelltime else float(self.options["dwelltime"])
+
+        ppname_g = pp if pp else self.options["ppname_g"]
+
+        # Object initialization function for app.new_object()
+        # RUNNING ON SEPARATE THREAD!
+        def job_init(job_obj, app_obj):
+            assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
+
+            # Propagate options
+            job_obj.options["tooldia"] = tooldia
+
+            job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
+            job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
+
+            job_obj.options['type'] = 'Geometry'
+            job_obj.options['tool_dia'] = tooldia
+
+            job_obj.segx = segx
+            job_obj.segy = segy
+
+            job_obj.z_pdepth = float(self.options["z_pdepth"])
+            job_obj.feedrate_probe = float(self.options["feedrate_probe"])
+
+            job_obj.options['xmin'] = self.options['xmin']
+            job_obj.options['ymin'] = self.options['ymin']
+            job_obj.options['xmax'] = self.options['xmax']
+            job_obj.options['ymax'] = self.options['ymax']
+
+            # 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
+            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
+            )
+
+            # 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..."))
+
+        if use_thread:
+            # To be run in separate thread
+            def job_thread(app_obj):
+                with self.app.proc_container.new(_("Generating CNC Code")):
+                    app_obj.new_object("cncjob", outname, job_init, plot=plot)
+                    app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname)
+
+            # Create a promise with the name
+            self.app.collection.promise(outname)
+            # Send to worker
+            self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+        else:
+            self.app.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 scale(self, xfactor, yfactor=None, point=None):
+        """
+        Scales all geometry by a given factor.
+
+        :param xfactor:     Factor by which to scale the object's geometry/
+        :type xfactor:      float
+        :param yfactor:     Factor by which to scale the object's geometry/
+        :type yfactor:      float
+        :param point:       Point around which to scale
+        :return: None
+        :rtype: None
+        """
+        log.debug("FlatCAMObj.GeometryObject.scale()")
+
+        try:
+            xfactor = float(xfactor)
+        except Exception:
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float."))
+            return
+
+        if yfactor is None:
+            yfactor = xfactor
+        else:
+            try:
+                yfactor = float(yfactor)
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float."))
+                return
+
+        if xfactor == 1 and yfactor == 1:
+            return
+
+        if point is None:
+            px = 0
+            py = 0
+        else:
+            px, py = point
+
+        self.geo_len = 0
+        self.old_disp_number = 0
+        self.el_count = 0
+
+        def scale_recursion(geom):
+            if type(geom) is list:
+                geoms = []
+                for local_geom in geom:
+                    geoms.append(scale_recursion(local_geom))
+                return geoms
+            else:
+                try:
+                    self.el_count += 1
+                    disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
+                    if self.old_disp_number < disp_number <= 100:
+                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                        self.old_disp_number = disp_number
+
+                    return affinity.scale(geom, xfactor, yfactor, origin=(px, py))
+                except AttributeError:
+                    return geom
+
+        if self.multigeo is True:
+            for tool in self.tools:
+                # variables to display the percentage of work done
+                self.geo_len = 0
+                try:
+                    self.geo_len = len(self.tools[tool]['solid_geometry'])
+                except TypeError:
+                    self.geo_len = 1
+                self.old_disp_number = 0
+                self.el_count = 0
+
+                self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry'])
+
+        try:
+            # variables to display the percentage of work done
+            self.geo_len = 0
+            try:
+                self.geo_len = len(self.solid_geometry)
+            except TypeError:
+                self.geo_len = 1
+            self.old_disp_number = 0
+            self.el_count = 0
+
+            self.solid_geometry = scale_recursion(self.solid_geometry)
+        except AttributeError:
+            self.solid_geometry = []
+            return
+
+        self.app.proc_container.new_text = ''
+        self.app.inform.emit('[success] %s' % _("Geometry Scale done."))
+
+    def offset(self, vect):
+        """
+        Offsets all geometry by a given vector/
+
+        :param vect: (x, y) vector by which to offset the object's geometry.
+        :type vect: tuple
+        :return: None
+        :rtype: None
+        """
+        log.debug("FlatCAMObj.GeometryObject.offset()")
+
+        try:
+            dx, dy = vect
+        except TypeError:
+            self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                 _("An (x,y) pair of values are needed. "
+                                   "Probable you entered only one value in the Offset field.")
+                                 )
+            return
+
+        if dx == 0 and dy == 0:
+            return
+
+        self.geo_len = 0
+        self.old_disp_number = 0
+        self.el_count = 0
+
+        def translate_recursion(geom):
+            if type(geom) is list:
+                geoms = []
+                for local_geom in geom:
+                    geoms.append(translate_recursion(local_geom))
+                return geoms
+            else:
+                try:
+                    self.el_count += 1
+                    disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
+                    if self.old_disp_number < disp_number <= 100:
+                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                        self.old_disp_number = disp_number
+
+                    return affinity.translate(geom, xoff=dx, yoff=dy)
+                except AttributeError:
+                    return geom
+
+        if self.multigeo is True:
+            for tool in self.tools:
+                # variables to display the percentage of work done
+                self.geo_len = 0
+                try:
+                    self.geo_len = len(self.tools[tool]['solid_geometry'])
+                except TypeError:
+                    self.geo_len = 1
+                self.old_disp_number = 0
+                self.el_count = 0
+
+                self.tools[tool]['solid_geometry'] = translate_recursion(self.tools[tool]['solid_geometry'])
+
+        # variables to display the percentage of work done
+        self.geo_len = 0
+        try:
+            self.geo_len = len(self.solid_geometry)
+        except TypeError:
+            self.geo_len = 1
+
+        self.old_disp_number = 0
+        self.el_count = 0
+
+        self.solid_geometry = translate_recursion(self.solid_geometry)
+
+        self.app.proc_container.new_text = ''
+        self.app.inform.emit('[success] %s' % _("Geometry Offset done."))
+
+    def convert_units(self, units):
+        log.debug("FlatCAMObj.GeometryObject.convert_units()")
+
+        self.ui_disconnect()
+
+        factor = Geometry.convert_units(self, units)
+
+        self.options['cutz'] = float(self.options['cutz']) * factor
+        self.options['depthperpass'] = float(self.options['depthperpass']) * factor
+        self.options['travelz'] = float(self.options['travelz']) * factor
+        self.options['feedrate'] = float(self.options['feedrate']) * factor
+        self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor
+        self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
+        self.options['endz'] = float(self.options['endz']) * factor
+        # self.options['cnctooldia'] *= factor
+        # self.options['painttooldia'] *= factor
+        # self.options['paintmargin'] *= factor
+        # self.options['paintoverlap'] *= factor
+
+        self.options["toolchangez"] = float(self.options["toolchangez"]) * factor
+
+        if self.app.defaults["geometry_toolchangexy"] == '':
+            self.options['toolchangexy'] = "0.0, 0.0"
+        else:
+            coords_xy = [float(eval(coord)) for coord in self.app.defaults["geometry_toolchangexy"].split(",")]
+            if len(coords_xy) < 2:
+                self.app.inform.emit('[ERROR] %s' %
+                                     _("The Toolchange X,Y field in Edit -> Preferences "
+                                       "has to be in the format (x, y)\n"
+                                       "but now there is only one value, not two.")
+                                     )
+                return 'fail'
+            coords_xy[0] *= factor
+            coords_xy[1] *= factor
+            self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
+
+        if self.options['startz'] is not None:
+            self.options['startz'] = float(self.options['startz']) * factor
+
+        param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
+                      'endz', 'toolchangez']
+
+        if isinstance(self, GeometryObject):
+            temp_tools_dict = {}
+            tool_dia_copy = {}
+            data_copy = {}
+            for tooluid_key, tooluid_value in self.tools.items():
+                for dia_key, dia_value in tooluid_value.items():
+                    if dia_key == 'tooldia':
+                        dia_value *= factor
+                        dia_value = float('%.*f' % (self.decimals, dia_value))
+                        tool_dia_copy[dia_key] = dia_value
+                    if dia_key == 'offset':
+                        tool_dia_copy[dia_key] = dia_value
+                    if dia_key == 'offset_value':
+                        dia_value *= factor
+                        tool_dia_copy[dia_key] = dia_value
+
+                        # convert the value in the Custom Tool Offset entry in UI
+                        custom_offset = None
+                        try:
+                            custom_offset = float(self.ui.tool_offset_entry.get_value())
+                        except ValueError:
+                            # try to convert comma to decimal point. if it's still not working error message and return
+                            try:
+                                custom_offset = 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."))
+                                return
+                        except TypeError:
+                            pass
+
+                        if custom_offset:
+                            custom_offset *= factor
+                            self.ui.tool_offset_entry.set_value(custom_offset)
+
+                    if dia_key == 'type':
+                        tool_dia_copy[dia_key] = dia_value
+                    if dia_key == 'tool_type':
+                        tool_dia_copy[dia_key] = dia_value
+                    if dia_key == 'data':
+                        for data_key, data_value in dia_value.items():
+                            # convert the form fields that are convertible
+                            for param in param_list:
+                                if data_key == param and data_value is not None:
+                                    data_copy[data_key] = data_value * factor
+                            # copy the other dict entries that are not convertible
+                            if data_key not in param_list:
+                                data_copy[data_key] = data_value
+                        tool_dia_copy[dia_key] = deepcopy(data_copy)
+                        data_copy.clear()
+
+                temp_tools_dict.update({
+                    tooluid_key: deepcopy(tool_dia_copy)
+                })
+                tool_dia_copy.clear()
+
+            self.tools.clear()
+            self.tools = deepcopy(temp_tools_dict)
+
+        # if there is a value in the new tool field then convert that one too
+        try:
+            self.ui.addtool_entry.returnPressed.disconnect()
+        except TypeError:
+            pass
+        tooldia = self.ui.addtool_entry.get_value()
+        if tooldia:
+            tooldia *= factor
+            tooldia = float('%.*f' % (self.decimals, tooldia))
+
+            self.ui.addtool_entry.set_value(tooldia)
+        self.ui.addtool_entry.returnPressed.connect(self.on_tool_add)
+
+        return factor
+
+    def plot_element(self, element, color=None, visible=None):
+
+        if color is None:
+            color = '#FF0000FF'
+
+        visible = visible if visible else self.options['plot']
+        try:
+            for sub_el in element:
+                self.plot_element(sub_el, color=color)
+
+        except TypeError:  # Element is not iterable...
+            # if self.app.is_legacy is False:
+            self.add_shape(shape=element, color=color, visible=visible, layer=0)
+
+    def plot(self, visible=None, kind=None):
+        """
+        Plot the object.
+
+        :param visible: Controls if the added shape is visible of not
+        :param kind: added so there is no error when a project is loaded and it has both geometry and CNCJob, because
+        CNCJob require the 'kind' parameter. Perhaps the FlatCAMObj.plot() has to be rewrited
+        :return:
+        """
+
+        # Does all the required setup and returns False
+        # if the 'ptint' option is set to False.
+        if not FlatCAMObj.plot(self):
+            return
+
+        try:
+            # plot solid geometries found as members of self.tools attribute dict
+            # for MultiGeo
+            if self.multigeo is True:  # geo multi tool usage
+                for tooluid_key in self.tools:
+                    solid_geometry = self.tools[tooluid_key]['solid_geometry']
+                    self.plot_element(solid_geometry, visible=visible,
+                                      color=self.app.defaults["geometry_plot_line"])
+            else:
+                # plot solid geometry that may be an direct attribute of the geometry object
+                # for SingleGeo
+                if self.solid_geometry:
+                    self.plot_element(self.solid_geometry, visible=visible,
+                                      color=self.app.defaults["geometry_plot_line"])
+
+            # self.plot_element(self.solid_geometry, visible=self.options['plot'])
+
+            self.shapes.redraw()
+
+        except (ObjectDeleted, AttributeError):
+            self.shapes.clear(update=True)
+
+    def on_plot_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('plot')
+        self.plot()
+
+        self.ui_disconnect()
+        cb_flag = self.ui.plot_cb.isChecked()
+        for row in range(self.ui.geo_tools_table.rowCount()):
+            table_cb = self.ui.geo_tools_table.cellWidget(row, 6)
+            if cb_flag:
+                table_cb.setChecked(True)
+            else:
+                table_cb.setChecked(False)
+        self.ui_connect()
+
+    def on_plot_cb_click_table(self):
+        # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked)
+        self.ui_disconnect()
+        # cw = self.sender()
+        # cw_index = self.ui.geo_tools_table.indexAt(cw.pos())
+        # cw_row = cw_index.row()
+        check_row = 0
+
+        self.shapes.clear(update=True)
+        for tooluid_key in self.tools:
+            solid_geometry = self.tools[tooluid_key]['solid_geometry']
+
+            # find the geo_tool_table row associated with the tooluid_key
+            for row in range(self.ui.geo_tools_table.rowCount()):
+                tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text())
+                if tooluid_item == int(tooluid_key):
+                    check_row = row
+                    break
+            if self.ui.geo_tools_table.cellWidget(check_row, 6).isChecked():
+                self.plot_element(element=solid_geometry, visible=True)
+        self.shapes.redraw()
+
+        # make sure that the general plot is disabled if one of the row plot's are disabled and
+        # if all the row plot's are enabled also enable the general plot checkbox
+        cb_cnt = 0
+        total_row = self.ui.geo_tools_table.rowCount()
+        for row in range(total_row):
+            if self.ui.geo_tools_table.cellWidget(row, 6).isChecked():
+                cb_cnt += 1
+            else:
+                cb_cnt -= 1
+        if cb_cnt < total_row:
+            self.ui.plot_cb.setChecked(False)
+        else:
+            self.ui.plot_cb.setChecked(True)
+        self.ui_connect()
+
+    def merge(self, geo_list, geo_final, multigeo=None):
+        """
+        Merges the geometry of objects in grb_list into
+        the geometry of geo_final.
+
+        :param geo_list: List of GerberObject Objects to join.
+        :param geo_final: Destination GerberObject object.
+        :param multigeo: if the merged geometry objects are of type MultiGeo
+        :return: None
+        """
+
+        if geo_final.solid_geometry is None:
+            geo_final.solid_geometry = []
+
+        try:
+            __ = iter(geo_final.solid_geometry)
+        except TypeError:
+            geo_final.solid_geometry = [geo_final.solid_geometry]
+
+        new_solid_geometry = []
+        new_options = {}
+        new_tools = {}
+
+        for geo_obj in geo_list:
+            for option in geo_obj.options:
+                if option != 'name':
+                    try:
+                        new_options[option] = deepcopy(geo_obj.options[option])
+                    except Exception as e:
+                        log.warning("Failed to copy option %s. Error: %s" % (str(option), str(e)))
+
+            # Expand lists
+            if type(geo_obj) is list:
+                GeometryObject.merge(self, geo_list=geo_obj, geo_final=geo_final)
+            # If not list, just append
+            else:
+                if multigeo is None or multigeo is False:
+                    geo_final.multigeo = False
+                else:
+                    geo_final.multigeo = True
+
+                try:
+                    new_solid_geometry += deepcopy(geo_obj.solid_geometry)
+                except Exception as e:
+                    log.debug("GeometryObject.merge() --> %s" % str(e))
+
+                # find the tool_uid maximum value in the geo_final
+                try:
+                    max_uid = max([int(i) for i in new_tools.keys()])
+                except ValueError:
+                    max_uid = 0
+
+                # add and merge tools. If what we try to merge as Geometry is Excellon's and/or Gerber's then don't try
+                # to merge the obj.tools as it is likely there is none to merge.
+                if geo_obj.kind != 'gerber' and geo_obj.kind != 'excellon':
+                    for tool_uid in geo_obj.tools:
+                        max_uid += 1
+                        new_tools[max_uid] = deepcopy(geo_obj.tools[tool_uid])
+
+        geo_final.options.update(new_options)
+        geo_final.solid_geometry = new_solid_geometry
+        geo_final.tools = new_tools
+
+    @staticmethod
+    def get_pts(o):
+        """
+        Returns a list of all points in the object, where
+        the object can be a MultiPolygon, Polygon, Not a polygon, or a list
+        of such. Search is done recursively.
+
+        :param: geometric object
+        :return: List of points
+        :rtype: list
+        """
+        pts = []
+
+        # Iterable: descend into each item.
+        try:
+            for subo in o:
+                pts += GeometryObject.get_pts(subo)
+
+        # Non-iterable
+        except TypeError:
+            if o is not None:
+                if type(o) == MultiPolygon:
+                    for poly in o:
+                        pts += GeometryObject.get_pts(poly)
+                # ## Descend into .exerior and .interiors
+                elif type(o) == Polygon:
+                    pts += GeometryObject.get_pts(o.exterior)
+                    for i in o.interiors:
+                        pts += GeometryObject.get_pts(i)
+                elif type(o) == MultiLineString:
+                    for line in o:
+                        pts += GeometryObject.get_pts(line)
+                # ## Has .coords: list them.
+                else:
+                    pts += list(o.coords)
+            else:
+                return
+        return pts

+ 1870 - 0
flatcamObjects/FlatCAMGerber.py

@@ -0,0 +1,1870 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# ##########################################################
+
+# ##########################################################
+# File modified by: Marius Stanciu                         #
+# ##########################################################
+
+
+from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
+from shapely.ops import cascaded_union
+
+from flatcamParsers.ParseGerber import Gerber
+from flatcamObjects.FlatCAMObj import *
+
+import math
+import numpy as np
+from copy import deepcopy
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class GerberObject(FlatCAMObj, Gerber):
+    """
+    Represents Gerber code.
+    """
+    optionChanged = QtCore.pyqtSignal(str)
+    replotApertures = QtCore.pyqtSignal()
+
+    ui_type = GerberObjectUI
+
+    def merge(self, grb_list, grb_final):
+        """
+        Merges the geometry of objects in geo_list into
+        the geometry of geo_final.
+
+        :param grb_list: List of GerberObject Objects to join.
+        :param grb_final: Destination GeometryObject object.
+        :return: None
+        """
+
+        if grb_final.solid_geometry is None:
+            grb_final.solid_geometry = []
+            grb_final.follow_geometry = []
+
+        if not grb_final.apertures:
+            grb_final.apertures = {}
+
+        if type(grb_final.solid_geometry) is not list:
+            grb_final.solid_geometry = [grb_final.solid_geometry]
+            grb_final.follow_geometry = [grb_final.follow_geometry]
+
+        for grb in grb_list:
+
+            # Expand lists
+            if type(grb) is list:
+                GerberObject.merge(grb, grb_final)
+            else:   # If not list, just append
+                for option in grb.options:
+                    if option != 'name':
+                        try:
+                            grb_final.options[option] = grb.options[option]
+                        except KeyError:
+                            log.warning("Failed to copy option.", option)
+
+                try:
+                    for geos in grb.solid_geometry:
+                        grb_final.solid_geometry.append(geos)
+                        grb_final.follow_geometry.append(geos)
+                except TypeError:
+                    grb_final.solid_geometry.append(grb.solid_geometry)
+                    grb_final.follow_geometry.append(grb.solid_geometry)
+
+                for ap in grb.apertures:
+                    if ap not in grb_final.apertures:
+                        grb_final.apertures[ap] = grb.apertures[ap]
+                    else:
+                        # create a list of integers out of the grb.apertures keys and find the max of that value
+                        # then, the aperture duplicate is assigned an id value incremented with 1,
+                        # and finally made string because the apertures dict keys are strings
+                        max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1)
+                        grb_final.apertures[max_ap] = {}
+                        grb_final.apertures[max_ap]['geometry'] = []
+
+                        for k, v in grb.apertures[ap].items():
+                            grb_final.apertures[max_ap][k] = deepcopy(v)
+
+        grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
+        grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
+
+    def __init__(self, name):
+        self.decimals = self.app.decimals
+
+        self.circle_steps = int(self.app.defaults["gerber_circle_steps"])
+
+        Gerber.__init__(self, steps_per_circle=self.circle_steps)
+        FlatCAMObj.__init__(self, name)
+
+        self.kind = "gerber"
+
+        # The 'name' is already in self.options from FlatCAMObj
+        # Automatically updates the UI
+        self.options.update({
+            "plot": True,
+            "multicolored": False,
+            "solid": False,
+            "tool_type": 'circular',
+            "vtipdia": 0.1,
+            "vtipangle": 30,
+            "vcutz": -0.05,
+            "isotooldia": 0.016,
+            "isopasses": 1,
+            "isooverlap": 15,
+            "milling_type": "cl",
+            "combine_passes": True,
+            "noncoppermargin": 0.0,
+            "noncopperrounded": False,
+            "bboxmargin": 0.0,
+            "bboxrounded": False,
+            "aperture_display": False,
+            "follow": False,
+            "iso_scope": 'all',
+            "iso_type": 'full'
+        })
+
+        # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
+        self.iso_type = 2
+
+        self.multigeo = False
+
+        self.follow = False
+
+        self.apertures_row = 0
+
+        # store the source file here
+        self.source_file = ""
+
+        # list of rows with apertures plotted
+        self.marked_rows = []
+
+        # Mouse events
+        self.mr = None
+        self.mm = None
+        self.mp = None
+
+        # dict to store the polygons selected for isolation; key is the shape added to be plotted and value is the poly
+        self.poly_dict = {}
+
+        # store the status of grid snapping
+        self.grid_status_memory = None
+
+        self.units_found = self.app.defaults['units']
+
+        self.fill_color = self.app.defaults['gerber_plot_fill']
+        self.outline_color = self.app.defaults['gerber_plot_line']
+        self.alpha_level = 'bf'
+
+        # keep track if the UI is built so we don't have to build it every time
+        self.ui_build = False
+
+        # build only once the aperture storage (takes time)
+        self.build_aperture_storage = False
+
+        # Attributes to be included in serialization
+        # Always append to it because it carries contents
+        # from predecessors.
+        self.ser_attrs += ['options', 'kind', 'fill_color', 'outline_color', 'alpha_level']
+
+    def set_ui(self, ui):
+        """
+        Maps options with GUI inputs.
+        Connects GUI events to methods.
+
+        :param ui: GUI object.
+        :type ui: GerberObjectUI
+        :return: None
+        """
+        FlatCAMObj.set_ui(self, ui)
+        log.debug("GerberObject.set_ui()")
+
+        self.units = self.app.defaults['units'].upper()
+
+        self.replotApertures.connect(self.on_mark_cb_click_table)
+
+        self.form_fields.update({
+            "plot": self.ui.plot_cb,
+            "multicolored": self.ui.multicolored_cb,
+            "solid": self.ui.solid_cb,
+            "tool_type": self.ui.tool_type_radio,
+            "vtipdia": self.ui.tipdia_spinner,
+            "vtipangle": self.ui.tipangle_spinner,
+            "vcutz": self.ui.cutz_spinner,
+            "isotooldia": self.ui.iso_tool_dia_entry,
+            "isopasses": self.ui.iso_width_entry,
+            "isooverlap": self.ui.iso_overlap_entry,
+            "milling_type": self.ui.milling_type_radio,
+            "combine_passes": self.ui.combine_passes_cb,
+            "noncoppermargin": self.ui.noncopper_margin_entry,
+            "noncopperrounded": self.ui.noncopper_rounded_cb,
+            "bboxmargin": self.ui.bbmargin_entry,
+            "bboxrounded": self.ui.bbrounded_cb,
+            "aperture_display": self.ui.aperture_table_visibility_cb,
+            "follow": self.ui.follow_cb,
+            "iso_scope": self.ui.iso_scope_radio,
+            "iso_type": self.ui.iso_type_radio
+        })
+
+        # Fill form fields only on object create
+        self.to_form()
+
+        assert isinstance(self.ui, GerberObjectUI)
+        self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
+        self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
+        self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
+        self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
+        self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
+        self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run)
+        self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
+        self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
+        self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
+        self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
+
+        # set the model for the Area Exception comboboxes
+        self.ui.obj_combo.setModel(self.app.collection)
+        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ui.obj_combo.is_last = True
+        self.ui.obj_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Geometry"): "Geometry"
+        }[self.ui.type_obj_combo.get_value()]
+        self.on_type_obj_index_changed()
+
+        self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
+
+        self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change)
+        # establish visibility for the GUI elements found in the slot function
+        self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type'])
+
+        # Show/Hide Advanced Options
+        if self.app.defaults["global_app_level"] == 'b':
+            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
+            self.options['tool_type'] = 'circular'
+
+            self.ui.tool_type_label.hide()
+            self.ui.tool_type_radio.hide()
+
+            # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
+            self.ui.tool_type_radio.set_value('circular')
+
+            self.ui.tipdialabel.hide()
+            self.ui.tipdia_spinner.hide()
+            self.ui.tipanglelabel.hide()
+            self.ui.tipangle_spinner.hide()
+            self.ui.cutzlabel.hide()
+            self.ui.cutz_spinner.hide()
+
+            self.ui.apertures_table_label.hide()
+            self.ui.aperture_table_visibility_cb.hide()
+            self.ui.milling_type_label.hide()
+            self.ui.milling_type_radio.hide()
+            self.ui.iso_type_label.hide()
+            self.ui.iso_type_radio.hide()
+
+            self.ui.follow_cb.hide()
+            self.ui.except_cb.setChecked(False)
+            self.ui.except_cb.hide()
+        else:
+            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
+            self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia)
+            self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia)
+            self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia)
+
+        if self.app.defaults["gerber_buffering"] == 'no':
+            self.ui.create_buffer_button.show()
+            try:
+                self.ui.create_buffer_button.clicked.disconnect(self.on_generate_buffer)
+            except TypeError:
+                pass
+            self.ui.create_buffer_button.clicked.connect(self.on_generate_buffer)
+        else:
+            self.ui.create_buffer_button.hide()
+
+        # set initial state of the aperture table and associated widgets
+        self.on_aperture_table_visibility_change()
+
+        self.build_ui()
+        self.units_found = self.app.defaults['units']
+
+    def on_calculate_tooldia(self):
+        try:
+            tdia = float(self.ui.tipdia_spinner.get_value())
+        except Exception:
+            return
+        try:
+            dang = float(self.ui.tipangle_spinner.get_value())
+        except Exception:
+            return
+        try:
+            cutz = float(self.ui.cutz_spinner.get_value())
+        except Exception:
+            return
+
+        cutz *= -1
+        if cutz < 0:
+            cutz *= -1
+
+        half_tip_angle = dang / 2
+
+        tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle)))
+        self.ui.iso_tool_dia_entry.set_value(tool_diameter)
+
+    def on_type_obj_index_changed(self):
+        val = self.ui.type_obj_combo.get_value()
+        obj_type = {"Gerber": 0, "Geometry": 2}[val]
+        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ui.obj_combo.setCurrentIndex(0)
+        self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val]
+
+    def on_tool_type_change(self, state):
+        if state == 'circular':
+            self.ui.tipdialabel.hide()
+            self.ui.tipdia_spinner.hide()
+            self.ui.tipanglelabel.hide()
+            self.ui.tipangle_spinner.hide()
+            self.ui.cutzlabel.hide()
+            self.ui.cutz_spinner.hide()
+            self.ui.iso_tool_dia_entry.setDisabled(False)
+            # update the value in the self.iso_tool_dia_entry once this is selected
+            self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia'])
+        else:
+            self.ui.tipdialabel.show()
+            self.ui.tipdia_spinner.show()
+            self.ui.tipanglelabel.show()
+            self.ui.tipangle_spinner.show()
+            self.ui.cutzlabel.show()
+            self.ui.cutz_spinner.show()
+            self.ui.iso_tool_dia_entry.setDisabled(True)
+            # update the value in the self.iso_tool_dia_entry once this is selected
+            self.on_calculate_tooldia()
+
+    def build_ui(self):
+        FlatCAMObj.build_ui(self)
+
+        if self.ui.aperture_table_visibility_cb.get_value() and self.ui_build is False:
+            self.ui_build = True
+
+            try:
+                # if connected, disconnect the signal from the slot on item_changed as it creates issues
+                self.ui.apertures_table.itemChanged.disconnect()
+            except (TypeError, AttributeError):
+                pass
+
+            self.apertures_row = 0
+            aper_no = self.apertures_row + 1
+            sort = []
+            for k, v in list(self.apertures.items()):
+                sort.append(int(k))
+            sorted_apertures = sorted(sort)
+
+            n = len(sorted_apertures)
+            self.ui.apertures_table.setRowCount(n)
+
+            for ap_code in sorted_apertures:
+                ap_code = str(ap_code)
+
+                ap_id_item = QtWidgets.QTableWidgetItem('%d' % int(self.apertures_row + 1))
+                ap_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+                self.ui.apertures_table.setItem(self.apertures_row, 0, ap_id_item)  # Tool name/id
+
+                ap_code_item = QtWidgets.QTableWidgetItem(ap_code)
+                ap_code_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+                ap_type_item = QtWidgets.QTableWidgetItem(str(self.apertures[ap_code]['type']))
+                ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+                if str(self.apertures[ap_code]['type']) == 'R' or str(self.apertures[ap_code]['type']) == 'O':
+                    ap_dim_item = QtWidgets.QTableWidgetItem(
+                        '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['width'],
+                                        self.decimals, self.apertures[ap_code]['height']
+                                        )
+                    )
+                    ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
+                elif str(self.apertures[ap_code]['type']) == 'P':
+                    ap_dim_item = QtWidgets.QTableWidgetItem(
+                        '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['diam'],
+                                        self.decimals, self.apertures[ap_code]['nVertices'])
+                    )
+                    ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
+                else:
+                    ap_dim_item = QtWidgets.QTableWidgetItem('')
+                    ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+                try:
+                    if self.apertures[ap_code]['size'] is not None:
+                        ap_size_item = QtWidgets.QTableWidgetItem(
+                            '%.*f' % (self.decimals, float(self.apertures[ap_code]['size'])))
+                    else:
+                        ap_size_item = QtWidgets.QTableWidgetItem('')
+                except KeyError:
+                    ap_size_item = QtWidgets.QTableWidgetItem('')
+                ap_size_item.setFlags(QtCore.Qt.ItemIsEnabled)
+
+                mark_item = FCCheckBox()
+                mark_item.setLayoutDirection(QtCore.Qt.RightToLeft)
+                # if self.ui.aperture_table_visibility_cb.isChecked():
+                #     mark_item.setChecked(True)
+
+                self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item)  # Aperture Code
+                self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item)  # Aperture Type
+                self.ui.apertures_table.setItem(self.apertures_row, 3, ap_size_item)   # Aperture Dimensions
+                self.ui.apertures_table.setItem(self.apertures_row, 4, ap_dim_item)   # Aperture Dimensions
+
+                empty_plot_item = QtWidgets.QTableWidgetItem('')
+                empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+                self.ui.apertures_table.setItem(self.apertures_row, 5, empty_plot_item)
+                self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item)
+
+                self.apertures_row += 1
+
+            self.ui.apertures_table.selectColumn(0)
+            self.ui.apertures_table.resizeColumnsToContents()
+            self.ui.apertures_table.resizeRowsToContents()
+
+            vertical_header = self.ui.apertures_table.verticalHeader()
+            # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
+            vertical_header.hide()
+            self.ui.apertures_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+            horizontal_header = self.ui.apertures_table.horizontalHeader()
+            horizontal_header.setMinimumSectionSize(10)
+            horizontal_header.setDefaultSectionSize(70)
+            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+            horizontal_header.resizeSection(0, 27)
+            horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
+            horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
+            horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
+            horizontal_header.setSectionResizeMode(4,  QtWidgets.QHeaderView.Stretch)
+            horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed)
+            horizontal_header.resizeSection(5, 17)
+            self.ui.apertures_table.setColumnWidth(5, 17)
+
+            self.ui.apertures_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+            self.ui.apertures_table.setSortingEnabled(False)
+            self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight())
+            self.ui.apertures_table.setMaximumHeight(self.ui.apertures_table.getHeight())
+
+            # update the 'mark' checkboxes state according with what is stored in the self.marked_rows list
+            if self.marked_rows:
+                for row in range(self.ui.apertures_table.rowCount()):
+                    try:
+                        self.ui.apertures_table.cellWidget(row, 5).set_value(self.marked_rows[row])
+                    except IndexError:
+                        pass
+
+            self.ui_connect()
+
+    def ui_connect(self):
+        for row in range(self.ui.apertures_table.rowCount()):
+            try:
+                self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect(self.on_mark_cb_click_table)
+            except (TypeError, AttributeError):
+                pass
+            self.ui.apertures_table.cellWidget(row, 5).clicked.connect(self.on_mark_cb_click_table)
+
+        try:
+            self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click)
+        except (TypeError, AttributeError):
+            pass
+        self.ui.mark_all_cb.clicked.connect(self.on_mark_all_click)
+
+    def ui_disconnect(self):
+        for row in range(self.ui.apertures_table.rowCount()):
+            try:
+                self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect()
+            except (TypeError, AttributeError):
+                pass
+
+        try:
+            self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click)
+        except (TypeError, AttributeError):
+            pass
+
+    def on_generate_buffer(self):
+        self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry"))
+
+        def buffer_task():
+            with self.app.proc_container.new('%s...' % _("Buffering")):
+                if isinstance(self.solid_geometry, list):
+                    self.solid_geometry = MultiPolygon(self.solid_geometry)
+
+                self.solid_geometry = self.solid_geometry.buffer(0.0000001)
+                self.solid_geometry = self.solid_geometry.buffer(-0.0000001)
+                self.app.inform.emit('[success] %s.' % _("Done"))
+                self.plot_single_object.emit()
+
+        self.app.worker_task.emit({'fcn': buffer_task, 'params': []})
+
+    def on_generatenoncopper_button_click(self, *args):
+        self.app.report_usage("gerber_on_generatenoncopper_button")
+
+        self.read_form()
+        name = self.options["name"] + "_noncopper"
+
+        def geo_init(geo_obj, app_obj):
+            assert geo_obj.kind == 'geometry', "Expected a Geometry object got %s" % type(geo_obj)
+
+            if isinstance(self.solid_geometry, list):
+                try:
+                    self.solid_geometry = MultiPolygon(self.solid_geometry)
+                except Exception:
+                    self.solid_geometry = cascaded_union(self.solid_geometry)
+
+            bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
+            if not self.options["noncopperrounded"]:
+                bounding_box = bounding_box.envelope
+            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."))
+                return "fail"
+            geo_obj.solid_geometry = non_copper
+
+        self.app.new_object("geometry", name, geo_init)
+
+    def on_generatebb_button_click(self, *args):
+        self.app.report_usage("gerber_on_generatebb_button")
+        self.read_form()
+        name = self.options["name"] + "_bbox"
+
+        def geo_init(geo_obj, app_obj):
+            assert geo_obj.kind == 'geometry', "Expected a Geometry object got %s" % type(geo_obj)
+
+            if isinstance(self.solid_geometry, list):
+                try:
+                    self.solid_geometry = MultiPolygon(self.solid_geometry)
+                except Exception:
+                    self.solid_geometry = cascaded_union(self.solid_geometry)
+
+            # Bounding box with rounded corners
+            bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"]))
+            if not self.options["bboxrounded"]:  # Remove rounded corners
+                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."))
+                return "fail"
+            geo_obj.solid_geometry = bounding_box
+
+        self.app.new_object("geometry", name, geo_init)
+
+    def on_iso_button_click(self, *args):
+
+        obj = self.app.collection.get_active()
+
+        self.iso_type = 2
+        if self.ui.iso_type_radio.get_value() == 'ext':
+            self.iso_type = 0
+        if self.ui.iso_type_radio.get_value() == 'int':
+            self.iso_type = 1
+
+        def worker_task(iso_obj, app_obj):
+            with self.app.proc_container.new(_("Isolating...")):
+                if self.ui.follow_cb.get_value() is True:
+                    iso_obj.follow_geo()
+                    # in the end toggle the visibility of the origin object so we can see the generated Geometry
+                    iso_obj.ui.plot_cb.toggle()
+                else:
+                    app_obj.report_usage("gerber_on_iso_button")
+                    self.read_form()
+
+                    iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
+                    self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
+
+        self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
+
+    def follow_geo(self, outname=None):
+        """
+        Creates a geometry object "following" the gerber paths.
+
+        :return: None
+        """
+
+        # default_name = self.options["name"] + "_follow"
+        # follow_name = outname or default_name
+
+        if outname is None:
+            follow_name = self.options["name"] + "_follow"
+        else:
+            follow_name = outname
+
+        def follow_init(follow_obj, app):
+            # Propagate options
+            follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+            follow_obj.solid_geometry = self.follow_geometry
+
+        # TODO: Do something if this is None. Offer changing name?
+        try:
+            self.app.new_object("geometry", follow_name, follow_init)
+        except Exception as e:
+            return "Operation failed: %s" % str(e)
+
+    def isolate_handler(self, iso_type, iso_scope):
+
+        if iso_scope == 'all':
+            self.isolate(iso_type=iso_type)
+        else:
+            # 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
+                self.app.ui.grid_snap_btn.trigger()
+            else:
+                self.grid_status_memory = False
+
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
+
+            if self.app.is_legacy is False:
+                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.mr)
+
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
+
+    def on_mouse_click_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            right_button = 2
+            self.app.event_is_dragging = self.app.event_is_dragging
+        else:
+            event_pos = (event.xdata, event.ydata)
+            right_button = 3
+            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+
+        event_pos = (x, y)
+        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+        if self.app.grid_status():
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+        else:
+            curr_pos = (curr_pos[0], curr_pos[1])
+
+        if event.button == 1:
+            clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
+
+            if self.app.selection_type is not None:
+                self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
+                self.app.selection_type = None
+            elif clicked_poly:
+                if clicked_poly not in self.poly_dict.values():
+                    shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
+                                                        color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                        face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                        visible=True)
+                    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."))
+                    )
+                else:
+                    try:
+                        for k, v in list(self.poly_dict.items()):
+                            if v == clicked_poly:
+                                self.app.tool_shapes.remove(k)
+                                self.poly_dict.pop(k)
+                                break
+                    except TypeError:
+                        return
+                    self.app.inform.emit(
+                        '%s. %s' % (_("Removed polygon"),
+                                    _("Click to add/remove next polygon or right click to start isolation."))
+                    )
+
+                self.app.tool_shapes.redraw()
+            else:
+                self.app.inform.emit(_("No polygon detected under click position."))
+        elif event.button == right_button and self.app.event_is_dragging is False:
+            # restore the Grid snapping if it was active before
+            if self.grid_status_memory is True:
+                self.app.ui.grid_snap_btn.trigger()
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+
+            self.app.tool_shapes.clear(update=True)
+
+            if self.poly_dict:
+                poly_list = deepcopy(list(self.poly_dict.values()))
+                self.isolate(iso_type=self.iso_type, geometry=poly_list)
+                self.poly_dict.clear()
+            else:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
+
+    def selection_area_handler(self, start_pos, end_pos, sel_type):
+        """
+        :param start_pos: mouse position when the selection LMB click was done
+        :param end_pos: mouse position when the left mouse button is released
+        :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
+        :return:
+        """
+        poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
+
+        # delete previous selection shape
+        self.app.delete_selection_shape()
+
+        added_poly_count = 0
+        try:
+            for geo in self.solid_geometry:
+                if geo not in self.poly_dict.values():
+                    if sel_type is True:
+                        if geo.within(poly_selection):
+                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                                shape=geo,
+                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                                face_color=self.app.defaults[
+                                                                               'global_sel_draw_color'] + 'AF',
+                                                                visible=True)
+                            self.poly_dict[shape_id] = geo
+                            added_poly_count += 1
+                    else:
+                        if poly_selection.intersects(geo):
+                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                                shape=geo,
+                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                                face_color=self.app.defaults[
+                                                                               'global_sel_draw_color'] + 'AF',
+                                                                visible=True)
+                            self.poly_dict[shape_id] = geo
+                            added_poly_count += 1
+        except TypeError:
+            if self.solid_geometry not in self.poly_dict.values():
+                if sel_type is True:
+                    if self.solid_geometry.within(poly_selection):
+                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                            shape=self.solid_geometry,
+                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                            face_color=self.app.defaults[
+                                                                           'global_sel_draw_color'] + 'AF',
+                                                            visible=True)
+                        self.poly_dict[shape_id] = self.solid_geometry
+                        added_poly_count += 1
+                else:
+                    if poly_selection.intersects(self.solid_geometry):
+                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                            shape=self.solid_geometry,
+                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                            face_color=self.app.defaults[
+                                                                           'global_sel_draw_color'] + 'AF',
+                                                            visible=True)
+                        self.poly_dict[shape_id] = self.solid_geometry
+                        added_poly_count += 1
+
+        if added_poly_count > 0:
+            self.app.tool_shapes.redraw()
+            self.app.inform.emit(
+                '%s: %d. %s' % (_("Added polygon"),
+                                int(added_poly_count),
+                                _("Click to add next polygon or right click to start isolation."))
+            )
+        else:
+            self.app.inform.emit(_("No polygon in selection."))
+
+    def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
+                milling_type=None, follow=None, plot=True):
+        """
+        Creates an isolation routing geometry object in the project.
+
+        :param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
+        :param geometry: specific geometry to isolate
+        :param dia: Tool diameter
+        :param passes: Number of tool widths to cut
+        :param overlap: Overlap between passes in fraction of tool diameter
+        :param outname: Base name of the output object
+        :param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
+        :param milling_type: type of milling: conventional or climbing
+        :param follow: Boolean: if to generate a 'follow' geometry
+        :param plot: Boolean: if to plot the resulting geometry object
+        :return: None
+        """
+
+        if geometry is None:
+            work_geo = self.follow_geometry if follow is True else self.solid_geometry
+        else:
+            work_geo = geometry
+
+        if dia is None:
+            dia = float(self.options["isotooldia"])
+
+        if passes is None:
+            passes = int(self.options["isopasses"])
+
+        if overlap is None:
+            overlap = float(self.options["isooverlap"])
+
+        overlap /= 100.0
+
+        combine = self.options["combine_passes"] if combine is None else bool(combine)
+
+        if milling_type is None:
+            milling_type = self.options["milling_type"]
+
+        if iso_type is None:
+            iso_t = 2
+        else:
+            iso_t = iso_type
+
+        base_name = self.options["name"]
+
+        if combine:
+            if outname is None:
+                if self.iso_type == 0:
+                    iso_name = base_name + "_ext_iso"
+                elif self.iso_type == 1:
+                    iso_name = base_name + "_int_iso"
+                else:
+                    iso_name = base_name + "_iso"
+            else:
+                iso_name = outname
+
+            def iso_init(geo_obj, app_obj):
+                # Propagate options
+                geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+                geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
+
+                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
+                if self.ui.tool_type_radio.get_value() == 'v':
+                    new_cutz = self.ui.cutz_spinner.get_value()
+                    new_vtipdia = self.ui.tipdia_spinner.get_value()
+                    new_vtipangle = self.ui.tipangle_spinner.get_value()
+                    tool_type = 'V'
+                else:
+                    new_cutz = self.app.defaults['geometry_cutz']
+                    new_vtipdia = self.app.defaults['geometry_vtipdia']
+                    new_vtipangle = self.app.defaults['geometry_vtipangle']
+                    tool_type = 'C1'
+
+                # store here the default data for Geometry Data
+                default_data = {}
+                default_data.update({
+                    "name": iso_name,
+                    "plot": self.app.defaults['geometry_plot'],
+                    "cutz": new_cutz,
+                    "vtipdia": new_vtipdia,
+                    "vtipangle": new_vtipangle,
+                    "travelz": self.app.defaults['geometry_travelz'],
+                    "feedrate": self.app.defaults['geometry_feedrate'],
+                    "feedrate_z": self.app.defaults['geometry_feedrate_z'],
+                    "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
+                    "dwell": self.app.defaults['geometry_dwell'],
+                    "dwelltime": self.app.defaults['geometry_dwelltime'],
+                    "multidepth": self.app.defaults['geometry_multidepth'],
+                    "ppname_g": self.app.defaults['geometry_ppname_g'],
+                    "depthperpass": self.app.defaults['geometry_depthperpass'],
+                    "extracut": self.app.defaults['geometry_extracut'],
+                    "extracut_length": self.app.defaults['geometry_extracut_length'],
+                    "toolchange": self.app.defaults['geometry_toolchange'],
+                    "toolchangez": self.app.defaults['geometry_toolchangez'],
+                    "endz": self.app.defaults['geometry_endz'],
+                    "spindlespeed": self.app.defaults['geometry_spindlespeed'],
+                    "toolchangexy": self.app.defaults['geometry_toolchangexy'],
+                    "startz": self.app.defaults['geometry_startz']
+                })
+
+                geo_obj.tools = {}
+                geo_obj.tools['1'] = {}
+                geo_obj.tools.update({
+                    '1': {
+                        'tooldia': float(self.options["isotooldia"]),
+                        'offset': 'Path',
+                        'offset_value': 0.0,
+                        'type': _('Rough'),
+                        'tool_type': tool_type,
+                        'data': default_data,
+                        'solid_geometry': geo_obj.solid_geometry
+                    }
+                })
+
+                for nr_pass in range(passes):
+                    iso_offset = dia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * dia)
+
+                    # if milling type is climb then the move is counter-clockwise around features
+                    mill_dir = 1 if milling_type == 'cl' else 0
+                    geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
+                                                  follow=follow, nr_passes=nr_pass)
+
+                    if geom == 'fail':
+                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                        return 'fail'
+                    geo_obj.solid_geometry.append(geom)
+
+                    # update the geometry in the tools
+                    geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
+
+                # detect if solid_geometry is empty and this require list flattening which is "heavy"
+                # or just looking in the lists (they are one level depth) and if any is not empty
+                # proceed with object creation, if there are empty and the number of them is the length
+                # of the list then we have an empty solid_geometry which should raise a Custom Exception
+                empty_cnt = 0
+                if not isinstance(geo_obj.solid_geometry, list) and \
+                        not isinstance(geo_obj.solid_geometry, MultiPolygon):
+                    geo_obj.solid_geometry = [geo_obj.solid_geometry]
+
+                for g in geo_obj.solid_geometry:
+                    if g:
+                        break
+                    else:
+                        empty_cnt += 1
+
+                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"]))
+
+                # even if combine is checked, one pass is still single-geo
+                geo_obj.multigeo = True if passes > 1 else False
+
+                # ############################################################
+                # ########## AREA SUBTRACTION ################################
+                # ############################################################
+                if self.ui.except_cb.get_value():
+                    self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                    geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
+
+            # TODO: Do something if this is None. Offer changing name?
+            self.app.new_object("geometry", iso_name, iso_init, plot=plot)
+        else:
+            for i in range(passes):
+
+                offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
+                if passes > 1:
+                    if outname is None:
+                        if self.iso_type == 0:
+                            iso_name = base_name + "_ext_iso" + str(i + 1)
+                        elif self.iso_type == 1:
+                            iso_name = base_name + "_int_iso" + str(i + 1)
+                        else:
+                            iso_name = base_name + "_iso" + str(i + 1)
+                    else:
+                        iso_name = outname
+                else:
+                    if outname is None:
+                        if self.iso_type == 0:
+                            iso_name = base_name + "_ext_iso"
+                        elif self.iso_type == 1:
+                            iso_name = base_name + "_int_iso"
+                        else:
+                            iso_name = base_name + "_iso"
+                    else:
+                        iso_name = outname
+
+                def iso_init(geo_obj, app_obj):
+                    # Propagate options
+                    geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+                    if self.ui.tool_type_radio.get_value() == 'v':
+                        geo_obj.tool_type = 'V'
+                    else:
+                        geo_obj.tool_type = 'C1'
+
+                    # if milling type is climb then the move is counter-clockwise around features
+                    mill_dir = 1 if milling_type == 'cl' else 0
+                    geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
+                                                  follow=follow,
+                                                  nr_passes=i)
+
+                    if geom == 'fail':
+                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                        return 'fail'
+
+                    geo_obj.solid_geometry = geom
+
+                    # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
+                    # even if the resulting geometry is not multigeo we add the tools dict which will hold the data
+                    # required to be transfered to the Geometry object
+                    if self.ui.tool_type_radio.get_value() == 'v':
+                        new_cutz = self.ui.cutz_spinner.get_value()
+                        new_vtipdia = self.ui.tipdia_spinner.get_value()
+                        new_vtipangle = self.ui.tipangle_spinner.get_value()
+                        tool_type = 'V'
+                    else:
+                        new_cutz = self.app.defaults['geometry_cutz']
+                        new_vtipdia = self.app.defaults['geometry_vtipdia']
+                        new_vtipangle = self.app.defaults['geometry_vtipangle']
+                        tool_type = 'C1'
+
+                    # store here the default data for Geometry Data
+                    default_data = {}
+                    default_data.update({
+                        "name": iso_name,
+                        "plot": self.app.defaults['geometry_plot'],
+                        "cutz": new_cutz,
+                        "vtipdia": new_vtipdia,
+                        "vtipangle": new_vtipangle,
+                        "travelz": self.app.defaults['geometry_travelz'],
+                        "feedrate": self.app.defaults['geometry_feedrate'],
+                        "feedrate_z": self.app.defaults['geometry_feedrate_z'],
+                        "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
+                        "dwell": self.app.defaults['geometry_dwell'],
+                        "dwelltime": self.app.defaults['geometry_dwelltime'],
+                        "multidepth": self.app.defaults['geometry_multidepth'],
+                        "ppname_g": self.app.defaults['geometry_ppname_g'],
+                        "depthperpass": self.app.defaults['geometry_depthperpass'],
+                        "extracut": self.app.defaults['geometry_extracut'],
+                        "extracut_length": self.app.defaults['geometry_extracut_length'],
+                        "toolchange": self.app.defaults['geometry_toolchange'],
+                        "toolchangez": self.app.defaults['geometry_toolchangez'],
+                        "endz": self.app.defaults['geometry_endz'],
+                        "spindlespeed": self.app.defaults['geometry_spindlespeed'],
+                        "toolchangexy": self.app.defaults['geometry_toolchangexy'],
+                        "startz": self.app.defaults['geometry_startz']
+                    })
+
+                    geo_obj.tools = {}
+                    geo_obj.tools['1'] = {}
+                    geo_obj.tools.update({
+                        '1': {
+                            'tooldia': float(self.options["isotooldia"]),
+                            'offset': 'Path',
+                            'offset_value': 0.0,
+                            'type': _('Rough'),
+                            'tool_type': tool_type,
+                            'data': default_data,
+                            'solid_geometry': geo_obj.solid_geometry
+                        }
+                    })
+
+                    # detect if solid_geometry is empty and this require list flattening which is "heavy"
+                    # or just looking in the lists (they are one level depth) and if any is not empty
+                    # proceed with object creation, if there are empty and the number of them is the length
+                    # of the list then we have an empty solid_geometry which should raise a Custom Exception
+                    empty_cnt = 0
+                    if not isinstance(geo_obj.solid_geometry, list):
+                        geo_obj.solid_geometry = [geo_obj.solid_geometry]
+
+                    for g in geo_obj.solid_geometry:
+                        if g:
+                            break
+                        else:
+                            empty_cnt += 1
+
+                    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"]))
+                    geo_obj.multigeo = False
+
+                    # ############################################################
+                    # ########## AREA SUBTRACTION ################################
+                    # ############################################################
+                    if self.ui.except_cb.get_value():
+                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                        geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
+
+                # TODO: Do something if this is None. Offer changing name?
+                self.app.new_object("geometry", iso_name, iso_init, plot=plot)
+
+    def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
+        # isolation_geometry produces an envelope that is going on the left of the geometry
+        # (the copper features). To leave the least amount of burrs on the features
+        # the tool needs to travel on the right side of the features (this is called conventional milling)
+        # the first pass is the one cutting all of the features, so it needs to be reversed
+        # the other passes overlap preceding ones and cut the left over copper. It is better for them
+        # to cut on the right side of the left over copper i.e on the left side of the features.
+
+        if follow:
+            geom = self.isolation_geometry(offset, geometry=geometry, follow=follow)
+        else:
+            try:
+                geom = self.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type, passes=nr_passes)
+            except Exception as e:
+                log.debug('GerberObject.isolate().generate_envelope() --> %s' % str(e))
+                return 'fail'
+
+        if invert:
+            try:
+                pl = []
+                for p in geom:
+                    if p is not None:
+                        if isinstance(p, Polygon):
+                            pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
+                        elif isinstance(p, LinearRing):
+                            pl.append(Polygon(p.coords[::-1]))
+                geom = MultiPolygon(pl)
+            except TypeError:
+                if isinstance(geom, Polygon) and geom is not None:
+                    geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
+                elif isinstance(geom, LinearRing) and geom is not None:
+                    geom = Polygon(geom.coords[::-1])
+                else:
+                    log.debug("GerberObject.isolate().generate_envelope() Error --> Unexpected Geometry %s" %
+                              type(geom))
+            except Exception as e:
+                log.debug("GerberObject.isolate().generate_envelope() Error --> %s" % str(e))
+                return 'fail'
+        return geom
+
+    def area_subtraction(self, geo, subtractor_geo=None):
+        """
+        Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
+
+        :param geo: target geometry from which to subtract
+        :param subtractor_geo: geometry that acts as subtractor
+        :return:
+        """
+        new_geometry = []
+        target_geo = geo
+
+        if subtractor_geo:
+            sub_union = cascaded_union(subtractor_geo)
+        else:
+            name = self.ui.obj_combo.currentText()
+            subtractor_obj = self.app.collection.get_by_name(name)
+            sub_union = cascaded_union(subtractor_obj.solid_geometry)
+
+        try:
+            for geo_elem in target_geo:
+                if isinstance(geo_elem, Polygon):
+                    for ring in self.poly2rings(geo_elem):
+                        new_geo = ring.difference(sub_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+                elif isinstance(geo_elem, MultiPolygon):
+                    for poly in geo_elem:
+                        for ring in self.poly2rings(poly):
+                            new_geo = ring.difference(sub_union)
+                            if new_geo and not new_geo.is_empty:
+                                new_geometry.append(new_geo)
+                elif isinstance(geo_elem, LineString):
+                    new_geo = geo_elem.difference(sub_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+                elif isinstance(geo_elem, MultiLineString):
+                    for line_elem in geo_elem:
+                        new_geo = line_elem.difference(sub_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+        except TypeError:
+            if isinstance(target_geo, Polygon):
+                for ring in self.poly2rings(target_geo):
+                    new_geo = ring.difference(sub_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+            elif isinstance(target_geo, LineString):
+                new_geo = target_geo.difference(sub_union)
+                if new_geo and not new_geo.is_empty:
+                    new_geometry.append(new_geo)
+            elif isinstance(target_geo, MultiLineString):
+                for line_elem in target_geo:
+                    new_geo = line_elem.difference(sub_union)
+                    if new_geo and not new_geo.is_empty:
+                        new_geometry.append(new_geo)
+        return new_geometry
+
+    def on_plot_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('plot')
+        self.plot()
+
+    def on_solid_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('solid')
+        self.plot()
+
+    def on_multicolored_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('multicolored')
+        self.plot()
+
+    def on_follow_cb_click(self):
+        if self.muted_ui:
+            return
+        self.plot()
+
+    def on_aperture_table_visibility_change(self):
+        if self.ui.aperture_table_visibility_cb.isChecked():
+            # add the shapes storage for marking apertures
+            if self.build_aperture_storage is False:
+                self.build_aperture_storage = True
+
+                if self.app.is_legacy is False:
+                    for ap_code in self.apertures:
+                        self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=1)
+                else:
+                    for ap_code in self.apertures:
+                        self.mark_shapes[ap_code] = ShapeCollectionLegacy(obj=self, app=self.app,
+                                                                          name=self.options['name'] + str(ap_code))
+
+            self.ui.apertures_table.setVisible(True)
+            for ap in self.mark_shapes:
+                self.mark_shapes[ap].enabled = True
+
+            self.ui.mark_all_cb.setVisible(True)
+            self.ui.mark_all_cb.setChecked(False)
+            self.build_ui()
+        else:
+            self.ui.apertures_table.setVisible(False)
+
+            self.ui.mark_all_cb.setVisible(False)
+
+            # on hide disable all mark plots
+            try:
+                for row in range(self.ui.apertures_table.rowCount()):
+                    self.ui.apertures_table.cellWidget(row, 5).set_value(False)
+                self.clear_plot_apertures()
+
+                # for ap in list(self.mark_shapes.keys()):
+                #     # self.mark_shapes[ap].enabled = False
+                #     del self.mark_shapes[ap]
+            except Exception as e:
+                log.debug(" GerberObject.on_aperture_visibility_changed() --> %s" % str(e))
+
+    def convert_units(self, units):
+        """
+        Converts the units of the object by scaling dimensions in all geometry
+        and options.
+
+        :param units: Units to which to convert the object: "IN" or "MM".
+        :type units: str
+        :return: None
+        :rtype: None
+        """
+
+        # units conversion to get a conversion should be done only once even if we found multiple
+        # units declaration inside a Gerber file (it can happen to find also the obsolete declaration)
+        if self.conversion_done is True:
+            log.debug("Gerber units conversion cancelled. Already done.")
+            return
+
+        log.debug("FlatCAMObj.GerberObject.convert_units()")
+
+        factor = Gerber.convert_units(self, units)
+
+        # self.options['isotooldia'] = float(self.options['isotooldia']) * factor
+        # self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor
+
+    def plot(self, kind=None, **kwargs):
+        """
+
+        :param kind:    Not used, for compatibility with the plot method for other objects
+        :param kwargs:  Color and face_color, visible
+        :return:
+        """
+        log.debug(str(inspect.stack()[1][3]) + " --> GerberObject.plot()")
+
+        # Does all the required setup and returns False
+        # if the 'ptint' option is set to False.
+        if not FlatCAMObj.plot(self):
+            return
+
+        if 'color' in kwargs:
+            color = kwargs['color']
+        else:
+            color = self.outline_color
+
+        if 'face_color' in kwargs:
+            face_color = kwargs['face_color']
+        else:
+            face_color = self.fill_color
+
+        if 'visible' not in kwargs:
+            visible = self.options['plot']
+        else:
+            visible = kwargs['visible']
+
+        # if the Follow Geometry checkbox is checked then plot only the follow geometry
+        if self.ui.follow_cb.get_value():
+            geometry = self.follow_geometry
+        else:
+            geometry = self.solid_geometry
+
+        # Make sure geometry is iterable.
+        try:
+            __ = iter(geometry)
+        except TypeError:
+            geometry = [geometry]
+
+        if self.app.is_legacy is False:
+            def random_color():
+                r_color = np.random.rand(4)
+                r_color[3] = 1
+                return r_color
+        else:
+            def random_color():
+                while True:
+                    r_color = np.random.rand(4)
+                    r_color[3] = 1
+
+                    new_color = '#'
+                    for idx in range(len(r_color)):
+                        new_color += '%x' % int(r_color[idx] * 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
+                    if len(new_color) == 9:
+                        break
+                return new_color
+
+        try:
+            if self.options["solid"]:
+                for g in geometry:
+                    if type(g) == Polygon or type(g) == LineString:
+                        self.add_shape(shape=g, color=color,
+                                       face_color=random_color() if self.options['multicolored']
+                                       else face_color, visible=visible)
+                    elif type(g) == Point:
+                        pass
+                    else:
+                        try:
+                            for el in g:
+                                self.add_shape(shape=el, color=color,
+                                               face_color=random_color() if self.options['multicolored']
+                                               else face_color, visible=visible)
+                        except TypeError:
+                            self.add_shape(shape=g, color=color,
+                                           face_color=random_color() if self.options['multicolored']
+                                           else face_color, visible=visible)
+            else:
+                for g in geometry:
+                    if type(g) == Polygon or type(g) == LineString:
+                        self.add_shape(shape=g, color=random_color() if self.options['multicolored'] else 'black',
+                                       visible=visible)
+                    elif type(g) == Point:
+                        pass
+                    else:
+                        for el in g:
+                            self.add_shape(shape=el, color=random_color() if self.options['multicolored'] else 'black',
+                                           visible=visible)
+            self.shapes.redraw(
+                # update_colors=(self.fill_color, self.outline_color),
+                # indexes=self.app.plotcanvas.shape_collection.data.keys()
+            )
+        except (ObjectDeleted, AttributeError):
+            self.shapes.clear(update=True)
+        except Exception as e:
+            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=True, **kwargs):
+        """
+
+        :param run_thread: if True run the aperture plot as a thread in a worker
+        :param kwargs: color and face_color
+        :return:
+        """
+
+        log.debug(str(inspect.stack()[1][3]) + " --> GerberObject.plot_aperture()")
+
+        # Does all the required setup and returns False
+        # if the 'ptint' option is set to False.
+        # if not FlatCAMObj.plot(self):
+        #     return
+
+        # for marking apertures, line color and fill color are the same
+        if 'color' in kwargs:
+            color = kwargs['color']
+        else:
+            color = self.app.defaults['gerber_plot_fill']
+
+        if 'marked_aperture' not in kwargs:
+            return
+        else:
+            aperture_to_plot_mark = kwargs['marked_aperture']
+            if aperture_to_plot_mark is None:
+                return
+
+        if 'visible' not in kwargs:
+            visibility = True
+        else:
+            visibility = kwargs['visible']
+
+        with self.app.proc_container.new(_("Plotting Apertures")):
+
+            def job_thread(app_obj):
+                try:
+                    if aperture_to_plot_mark in self.apertures:
+                        for elem in self.apertures[aperture_to_plot_mark]['geometry']:
+                            if 'solid' in elem:
+                                geo = elem['solid']
+                                if type(geo) == Polygon or type(geo) == LineString:
+                                    self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color,
+                                                        face_color=color, visible=visibility)
+                                else:
+                                    for el in geo:
+                                        self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color,
+                                                            face_color=color, visible=visibility)
+
+                    self.mark_shapes[aperture_to_plot_mark].redraw()
+
+                except (ObjectDeleted, AttributeError):
+                    self.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)
+
+    def clear_plot_apertures(self, aperture='all'):
+        """
+
+        :param aperture: string; aperture for which to clear the mark shapes
+        :return:
+        """
+
+        if self.mark_shapes:
+            if aperture == 'all':
+                for apid in list(self.apertures.keys()):
+                    try:
+                        if self.app.is_legacy is True:
+                            self.mark_shapes[apid].clear(update=False)
+                        else:
+                            self.mark_shapes[apid].clear(update=True)
+                    except Exception as e:
+                        log.debug("GerberObject.clear_plot_apertures() 'all' --> %s" % str(e))
+            else:
+                try:
+                    if self.app.is_legacy is True:
+                        self.mark_shapes[aperture].clear(update=False)
+                    else:
+                        self.mark_shapes[aperture].clear(update=True)
+                except Exception as e:
+                    log.debug("GerberObject.clear_plot_apertures() 'aperture' --> %s" % str(e))
+
+    def clear_mark_all(self):
+        self.ui.mark_all_cb.set_value(False)
+        self.marked_rows[:] = []
+
+    def on_mark_cb_click_table(self):
+        """
+        Will mark aperture geometries on canvas or delete the markings depending on the checkbox state
+        :return:
+        """
+
+        self.ui_disconnect()
+        try:
+            cw = self.sender()
+            assert isinstance(cw, FCCheckBox),\
+                "Expected a cellWidget but got %s" % type(cw)
+            cw_index = self.ui.apertures_table.indexAt(cw.pos())
+            cw_row = cw_index.row()
+        except AttributeError:
+            cw_row = 0
+        except TypeError:
+            return
+
+        self.marked_rows[:] = []
+
+        try:
+            aperture = self.ui.apertures_table.item(cw_row, 1).text()
+        except AttributeError:
+            return
+
+        if self.ui.apertures_table.cellWidget(cw_row, 5).isChecked():
+            self.marked_rows.append(True)
+            # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
+            self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                               marked_aperture=aperture, visible=True, run_thread=True)
+            # self.mark_shapes[aperture].redraw()
+        else:
+            self.marked_rows.append(False)
+            self.clear_plot_apertures(aperture=aperture)
+
+        # make sure that the Mark All is disabled if one of the row mark's are disabled and
+        # if all the row mark's are enabled also enable the Mark All checkbox
+        cb_cnt = 0
+        total_row = self.ui.apertures_table.rowCount()
+        for row in range(total_row):
+            if self.ui.apertures_table.cellWidget(row, 5).isChecked():
+                cb_cnt += 1
+            else:
+                cb_cnt -= 1
+        if cb_cnt < total_row:
+            self.ui.mark_all_cb.setChecked(False)
+        else:
+            self.ui.mark_all_cb.setChecked(True)
+        self.ui_connect()
+
+    def on_mark_all_click(self):
+        self.ui_disconnect()
+        mark_all = self.ui.mark_all_cb.isChecked()
+        for row in range(self.ui.apertures_table.rowCount()):
+            # update the mark_rows list
+            if mark_all:
+                self.marked_rows.append(True)
+            else:
+                self.marked_rows[:] = []
+
+            mark_cb = self.ui.apertures_table.cellWidget(row, 5)
+            mark_cb.setChecked(mark_all)
+
+        if mark_all:
+            for aperture in self.apertures:
+                # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
+                self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                   marked_aperture=aperture, visible=True)
+            # HACK: enable/disable the grid for a better look
+            self.app.ui.grid_snap_btn.trigger()
+            self.app.ui.grid_snap_btn.trigger()
+        else:
+            self.clear_plot_apertures()
+            self.marked_rows[:] = []
+
+        self.ui_connect()
+
+    def export_gerber(self, whole, fract, g_zeros='L', factor=1):
+        """
+        Creates a Gerber file content to be exported to a file.
+
+        :param whole: how many digits in the whole part of coordinates
+        :param fract: how many decimals in coordinates
+        :param g_zeros: type of the zero suppression used: LZ or TZ; string
+        :param factor: factor to be applied onto the Gerber coordinates
+        :return: Gerber_code
+        """
+        log.debug("GerberObject.export_gerber() --> Generating the Gerber code from the selected Gerber file")
+
+        def tz_format(x, y, fac):
+            x_c = x * fac
+            y_c = y * fac
+
+            x_form = "{:.{dec}f}".format(x_c, dec=fract)
+            y_form = "{:.{dec}f}".format(y_c, dec=fract)
+
+            # extract whole part and decimal part
+            x_form = x_form.partition('.')
+            y_form = y_form.partition('.')
+
+            # left padd the 'whole' part with zeros
+            x_whole = x_form[0].rjust(whole, '0')
+            y_whole = y_form[0].rjust(whole, '0')
+
+            # restore the coordinate padded in the left with 0 and added the decimal part
+            # without the decinal dot
+            x_form = x_whole + x_form[2]
+            y_form = y_whole + y_form[2]
+            return x_form, y_form
+
+        def lz_format(x, y, fac):
+            x_c = x * fac
+            y_c = y * fac
+
+            x_form = "{:.{dec}f}".format(x_c, dec=fract).replace('.', '')
+            y_form = "{:.{dec}f}".format(y_c, dec=fract).replace('.', '')
+
+            # pad with rear zeros
+            x_form.ljust(length, '0')
+            y_form.ljust(length, '0')
+
+            return x_form, y_form
+
+        # Gerber code is stored here
+        gerber_code = ''
+
+        # apertures processing
+        try:
+            length = whole + fract
+            if '0' in self.apertures:
+                if 'geometry' in self.apertures['0']:
+                    for geo_elem in self.apertures['0']['geometry']:
+                        if 'solid' in geo_elem:
+                            geo = geo_elem['solid']
+                            if not geo.is_empty:
+                                gerber_code += 'G36*\n'
+                                geo_coords = list(geo.exterior.coords)
+                                # first command is a move with pen-up D02 at the beginning of the geo
+                                if g_zeros == 'T':
+                                    x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
+                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                   yform=y_formatted)
+                                else:
+                                    x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
+                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                   yform=y_formatted)
+                                for coord in geo_coords[1:]:
+                                    if g_zeros == 'T':
+                                        x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                       yform=y_formatted)
+                                    else:
+                                        x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                       yform=y_formatted)
+                                gerber_code += 'D02*\n'
+                                gerber_code += 'G37*\n'
+
+                                clear_list = list(geo.interiors)
+                                if clear_list:
+                                    gerber_code += '%LPC*%\n'
+                                    for clear_geo in clear_list:
+                                        gerber_code += 'G36*\n'
+                                        geo_coords = list(clear_geo.coords)
+
+                                        # first command is a move with pen-up D02 at the beginning of the geo
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+
+                                        prev_coord = geo_coords[0]
+                                        for coord in geo_coords[1:]:
+                                            if coord != prev_coord:
+                                                if g_zeros == 'T':
+                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                                else:
+                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                            prev_coord = coord
+
+                                        gerber_code += 'D02*\n'
+                                        gerber_code += 'G37*\n'
+                                    gerber_code += '%LPD*%\n'
+                        if 'clear' in geo_elem:
+                            geo = geo_elem['clear']
+                            if not geo.is_empty:
+                                gerber_code += '%LPC*%\n'
+                                gerber_code += 'G36*\n'
+                                geo_coords = list(geo.exterior.coords)
+                                # first command is a move with pen-up D02 at the beginning of the geo
+                                if g_zeros == 'T':
+                                    x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
+                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                   yform=y_formatted)
+                                else:
+                                    x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
+                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                   yform=y_formatted)
+
+                                prev_coord = geo_coords[0]
+                                for coord in geo_coords[1:]:
+                                    if coord != prev_coord:
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                            gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                            gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                    prev_coord = coord
+
+                                gerber_code += 'D02*\n'
+                                gerber_code += 'G37*\n'
+                                gerber_code += '%LPD*%\n'
+        except Exception as e:
+            log.debug("FlatCAMObj.GerberObject.export_gerber() '0' aperture --> %s" % str(e))
+
+        for apid in self.apertures:
+            if apid == '0':
+                continue
+            else:
+                gerber_code += 'D%s*\n' % str(apid)
+                if 'geometry' in self.apertures[apid]:
+                    for geo_elem in self.apertures[apid]['geometry']:
+                        try:
+                            if 'follow' in geo_elem:
+                                geo = geo_elem['follow']
+                                if not geo.is_empty:
+                                    if isinstance(geo, Point):
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                    else:
+                                        geo_coords = list(geo.coords)
+                                        # first command is a move with pen-up D02 at the beginning of the geo
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+
+                                        prev_coord = geo_coords[0]
+                                        for coord in geo_coords[1:]:
+                                            if coord != prev_coord:
+                                                if g_zeros == 'T':
+                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                                else:
+                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                            prev_coord = coord
+
+                                        # gerber_code += "D02*\n"
+                        except Exception as e:
+                            log.debug("FlatCAMObj.GerberObject.export_gerber() 'follow' --> %s" % str(e))
+
+                        try:
+                            if 'clear' in geo_elem:
+                                gerber_code += '%LPC*%\n'
+
+                                geo = geo_elem['clear']
+                                if not geo.is_empty:
+                                    if isinstance(geo, Point):
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                    elif isinstance(geo, Polygon):
+                                        geo_coords = list(geo.exterior.coords)
+                                        # first command is a move with pen-up D02 at the beginning of the geo
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+
+                                        prev_coord = geo_coords[0]
+                                        for coord in geo_coords[1:]:
+                                            if coord != prev_coord:
+                                                if g_zeros == 'T':
+                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                                else:
+                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+
+                                            prev_coord = coord
+
+                                        for geo_int in geo.interiors:
+                                            geo_coords = list(geo_int.coords)
+                                            # first command is a move with pen-up D02 at the beginning of the geo
+                                            if g_zeros == 'T':
+                                                x_formatted, y_formatted = tz_format(
+                                                    geo_coords[0][0], geo_coords[0][1], factor)
+                                                gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                               yform=y_formatted)
+                                            else:
+                                                x_formatted, y_formatted = lz_format(
+                                                    geo_coords[0][0], geo_coords[0][1], factor)
+                                                gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                               yform=y_formatted)
+
+                                            prev_coord = geo_coords[0]
+                                            for coord in geo_coords[1:]:
+                                                if coord != prev_coord:
+                                                    if g_zeros == 'T':
+                                                        x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                        gerber_code += "X{xform}Y{yform}D01*\n".format(
+                                                            xform=x_formatted,
+                                                            yform=y_formatted)
+                                                    else:
+                                                        x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                        gerber_code += "X{xform}Y{yform}D01*\n".format(
+                                                            xform=x_formatted,
+                                                            yform=y_formatted)
+
+                                                prev_coord = coord
+                                    else:
+                                        geo_coords = list(geo.coords)
+                                        # first command is a move with pen-up D02 at the beginning of the geo
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+
+                                        prev_coord = geo_coords[0]
+                                        for coord in geo_coords[1:]:
+                                            if coord != prev_coord:
+                                                if g_zeros == 'T':
+                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                                else:
+                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+
+                                            prev_coord = coord
+                                        # gerber_code += "D02*\n"
+                                    gerber_code += '%LPD*%\n'
+                        except Exception as e:
+                            log.debug("FlatCAMObj.GerberObject.export_gerber() 'clear' --> %s" % str(e))
+
+        if not self.apertures:
+            log.debug("FlatCAMObj.GerberObject.export_gerber() --> Gerber Object is empty: no apertures.")
+            return 'fail'
+
+        return gerber_code
+
+    def mirror(self, axis, point):
+        Gerber.mirror(self, axis=axis, point=point)
+        self.replotApertures.emit()
+
+    def offset(self, vect):
+        Gerber.offset(self, vect=vect)
+        self.replotApertures.emit()
+
+    def rotate(self, angle, point):
+        Gerber.rotate(self, angle=angle, point=point)
+        self.replotApertures.emit()
+
+    def scale(self, xfactor, yfactor=None, point=None):
+        Gerber.scale(self, xfactor=xfactor, yfactor=yfactor, point=point)
+        self.replotApertures.emit()
+
+    def skew(self, angle_x, angle_y, point):
+        Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
+        self.replotApertures.emit()
+
+    def buffer(self, distance, join, factor=None):
+        Gerber.buffer(self, distance=distance, join=join, factor=factor)
+        self.replotApertures.emit()
+
+    def serialize(self):
+        return {
+            "options": self.options,
+            "kind": self.kind
+        }

+ 505 - 0
flatcamObjects/FlatCAMObj.py

@@ -0,0 +1,505 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# ##########################################################
+
+# ##########################################################
+# File modified by: Marius Stanciu                         #
+# ##########################################################
+
+import inspect  # TODO: For debugging only.
+
+from flatcamGUI.ObjectUI import *
+
+from FlatCAMCommon import LoudDict
+from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+
+import sys
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+# Interrupts plotting process if FlatCAMObj has been deleted
+class ObjectDeleted(Exception):
+    pass
+
+
+class ValidationError(Exception):
+    def __init__(self, message, errors):
+        super().__init__(message)
+
+        self.errors = errors
+
+
+class FlatCAMObj(QtCore.QObject):
+    """
+    Base type of objects handled in FlatCAM. These become interactive
+    in the GUI, can be plotted, and their options can be modified
+    by the user in their respective forms.
+    """
+
+    # Instance of the application to which these are related.
+    # The app should set this value.
+    app = None
+
+    # signal to plot a single object
+    plot_single_object = QtCore.pyqtSignal()
+
+    def __init__(self, name):
+        """
+        Constructor.
+
+        :param name: Name of the object given by the user.
+        :return: FlatCAMObj
+        """
+
+        QtCore.QObject.__init__(self)
+
+        # View
+        self.ui = None
+
+        self.options = LoudDict(name=name)
+        self.options.set_change_callback(self.on_options_change)
+
+        self.form_fields = {}
+
+        # store here the default data for Geometry Data
+        self.default_data = {}
+
+        # 2D mode
+        # Axes must exist and be attached to canvas.
+        self.axes = None
+        self.kind = None  # Override with proper name
+
+        if self.app.is_legacy is False:
+            self.shapes = self.app.plotcanvas.new_shape_group()
+            # self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, pool=self.app.pool, layers=2)
+        else:
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name)
+
+        self.mark_shapes = {}
+
+        self.item = None  # Link with project view item
+
+        self.muted_ui = False
+        self.deleted = False
+
+        try:
+            self._drawing_tolerance = float(self.app.defaults["global_tolerance"]) if \
+                self.app.defaults["global_tolerance"] else 0.01
+        except ValueError:
+            self._drawing_tolerance = 0.01
+
+        self.isHovering = False
+        self.notHovering = True
+
+        # Flag to show if a selection shape is drawn
+        self.selection_shape_drawn = False
+
+        # self.units = 'IN'
+        self.units = self.app.defaults['units']
+
+        self.plot_single_object.connect(self.single_object_plot)
+
+    def __del__(self):
+        pass
+
+    def __str__(self):
+        return "<FlatCAMObj({:12s}): {:20s}>".format(self.kind, self.options["name"])
+
+    def from_dict(self, d):
+        """
+        This supersedes ``from_dict`` in derived classes. Derived classes
+        must inherit from FlatCAMObj first, then from derivatives of Geometry.
+
+        ``self.options`` is only updated, not overwritten. This ensures that
+        options set by the app do not vanish when reading the objects
+        from a project file.
+
+        :param d: Dictionary with attributes to set.
+        :return: None
+        """
+
+        for attr in self.ser_attrs:
+
+            if attr == 'options':
+                self.options.update(d[attr])
+            else:
+                try:
+                    setattr(self, attr, d[attr])
+                except KeyError:
+                    log.debug("FlatCAMObj.from_dict() --> KeyError: %s. "
+                              "Means that we are loading an old project that don't"
+                              "have all attributes in the latest FlatCAM." % str(attr))
+                    pass
+
+    def on_options_change(self, key):
+        # Update form on programmatically options change
+        self.set_form_item(key)
+
+        # Set object visibility
+        if key == 'plot':
+            self.visible = self.options['plot']
+
+        self.optionChanged.emit(key)
+
+    def set_ui(self, ui):
+        self.ui = ui
+
+        self.form_fields = {"name": self.ui.name_entry}
+
+        assert isinstance(self.ui, ObjectUI)
+        self.ui.name_entry.returnPressed.connect(self.on_name_activate)
+
+        try:
+            # it will raise an exception for those FlatCAM objects that do not build UI with the common elements
+            self.ui.offset_button.clicked.connect(self.on_offset_button_click)
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.ui.scale_button.clicked.connect(self.on_scale_button_click)
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click)
+        except (TypeError, AttributeError):
+            pass
+
+        # Creates problems on focusOut
+        try:
+            self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
+        except (TypeError, AttributeError):
+            pass
+
+        # self.ui.skew_button.clicked.connect(self.on_skew_button_click)
+
+    def build_ui(self):
+        """
+        Sets up the UI/form for this object. Show the UI
+        in the App.
+
+        :return: None
+        :rtype: None
+        """
+
+        self.muted_ui = True
+        log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()")
+
+        try:
+            # HACK: disconnect the scale entry signal since on focus out event will trigger an undesired scale()
+            # it seems that the takewidget() does generate a focus out event for the QDoubleSpinbox ...
+            # and reconnect after the takeWidget() is done
+            # self.ui.scale_entry.returnPressed.disconnect(self.on_scale_button_click)
+            self.app.ui.selected_scroll_area.takeWidget()
+            # self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
+        except Exception as e:
+            self.app.log.debug("FlatCAMObj.build_ui() --> Nothing to remove: %s" % str(e))
+
+        self.app.ui.selected_scroll_area.setWidget(self.ui)
+        # self.ui.setMinimumWidth(100)
+        # self.ui.setMaximumWidth(self.app.ui.selected_tab.sizeHint().width())
+
+        self.muted_ui = False
+
+    def on_name_activate(self, silent=None):
+        old_name = copy(self.options["name"])
+        new_name = self.ui.name_entry.get_value()
+
+        if new_name != old_name:
+            # update the SHELL auto-completer model data
+            try:
+                self.app.myKeywords.remove(old_name)
+                self.app.myKeywords.append(new_name)
+                self.app.shell._edit.set_model_data(self.app.myKeywords)
+                self.app.ui.code_editor.set_model_data(self.app.myKeywords)
+            except Exception:
+                log.debug("on_name_activate() --> Could not remove the old object name from auto-completer model list")
+
+            self.options["name"] = self.ui.name_entry.get_value()
+            self.default_data["name"] = self.ui.name_entry.get_value()
+            self.app.collection.update_view()
+            if silent:
+                self.app.inform.emit('[success] %s: %s %s: %s' % (
+                    _("Name changed from"), str(old_name), _("to"), str(new_name)
+                )
+                                     )
+
+    def on_offset_button_click(self):
+        self.app.report_usage("obj_on_offset_button")
+
+        self.read_form()
+        vector_val = self.ui.offsetvector_entry.get_value()
+
+        def worker_task():
+            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")):
+                self.plot()
+            self.app.object_changed.emit(self)
+
+        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
+
+    def on_scale_button_click(self):
+        self.read_form()
+        try:
+            factor = float(eval(self.ui.scale_entry.get_value()))
+        except Exception as e:
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
+            log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e))
+            return
+
+        if type(factor) != float:
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
+
+        # if factor is 1.0 do nothing, there is no point in scaling with a factor of 1.0
+        if factor == 1.0:
+            self.app.inform.emit('[success] %s' % _("Scale done."))
+            return
+
+        log.debug("FlatCAMObj.on_scale_button_click()")
+
+        def worker_task():
+            with self.app.proc_container.new(_("Scaling...")):
+                self.scale(factor)
+                self.app.inform.emit('[success] %s' % _("Scale done."))
+
+            self.app.proc_container.update_view_text('')
+            with self.app.proc_container.new('%s...' % _("Plotting")):
+                self.plot()
+            self.app.object_changed.emit(self)
+
+        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
+
+    def on_skew_button_click(self):
+        self.app.report_usage("obj_on_skew_button")
+        self.read_form()
+        x_angle = self.ui.xangle_entry.get_value()
+        y_angle = self.ui.yangle_entry.get_value()
+
+        def worker_task():
+            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")):
+                self.plot()
+            self.app.object_changed.emit(self)
+
+        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
+
+    def to_form(self):
+        """
+        Copies options to the UI form.
+
+        :return: None
+        """
+        log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.to_form()")
+        for option in self.options:
+            try:
+                self.set_form_item(option)
+            except Exception:
+                self.app.log.warning("Unexpected error:", sys.exc_info())
+
+    def read_form(self):
+        """
+        Reads form into ``self.options``.
+
+        :return: None
+        :rtype: None
+        """
+        log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
+        for option in self.options:
+            try:
+                self.read_form_item(option)
+            except Exception:
+                self.app.log.warning("Unexpected error:", sys.exc_info())
+
+    def set_form_item(self, option):
+        """
+        Copies the specified option to the UI form.
+
+        :param option: Name of the option (Key in ``self.options``).
+        :type option: str
+        :return: None
+        """
+
+        try:
+            self.form_fields[option].set_value(self.options[option])
+        except KeyError:
+            # self.app.log.warn("Tried to set an option or field that does not exist: %s" % option)
+            pass
+
+    def read_form_item(self, option):
+        """
+        Reads the specified option from the UI form into ``self.options``.
+
+        :param option: Name of the option.
+        :type option: str
+        :return: None
+        """
+        try:
+            self.options[option] = self.form_fields[option].get_value()
+        except KeyError:
+            pass
+            # self.app.log.warning("Failed to read option from field: %s" % option)
+
+    def plot(self, kind=None):
+        """
+        Plot this object (Extend this method to implement the actual plotting).
+        Call this in descendants before doing the plotting.
+
+        :param kind:    Used by only some of the FlatCAM objects
+        :return:        Whether to continue plotting or not depending on the "plot" option. Boolean
+        """
+        log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()")
+
+        if self.deleted:
+            return False
+
+        self.clear()
+        return True
+
+    def single_object_plot(self):
+        def plot_task():
+            with self.app.proc_container.new('%s...' % _("Plotting")):
+                self.plot()
+            self.app.object_changed.emit(self)
+
+        self.app.worker_task.emit({'fcn': plot_task, 'params': []})
+
+    def serialize(self):
+        """
+        Returns a representation of the object as a dictionary so
+        it can be later exported as JSON. Override this method.
+
+        :return: Dictionary representing the object
+        :rtype: dict
+        """
+        return
+
+    def deserialize(self, obj_dict):
+        """
+        Re-builds an object from its serialized version.
+
+        :param obj_dict: Dictionary representing a FlatCAMObj
+        :type obj_dict: dict
+        :return: None
+        """
+        return
+
+    def add_shape(self, **kwargs):
+        if self.deleted:
+            raise ObjectDeleted()
+        else:
+            key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs)
+        return key
+
+    def add_mark_shape(self, apid, **kwargs):
+        if self.deleted:
+            raise ObjectDeleted()
+        else:
+            key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, layer=0, **kwargs)
+        return key
+
+    def update_filters(self, last_ext, filter_string):
+        """
+        Will modify the filter string that is used when saving a file (a list of file extensions) to have the last
+        used file extension as the first one in the special string
+
+        :param last_ext:        The file extension that was last used to save a file
+        :param filter_string:   A key in self.app.defaults that holds a string with the filter from QFileDialog
+        used when saving a file
+        :return:                None
+        """
+
+        filters = copy(self.app.defaults[filter_string])
+        filter_list = filters.split(';;')
+        filter_list_enum_1 = enumerate(filter_list)
+
+        # search for the last element in the filters which should always be "All Files (*.*)"
+        last_elem = ''
+        for elem in list(filter_list_enum_1):
+            if '(*.*)' in elem[1]:
+                last_elem = filter_list.pop(elem[0])
+
+        filter_list_enum = enumerate(filter_list)
+        for elem in list(filter_list_enum):
+            if '.' + last_ext in elem[1]:
+                used_ext = filter_list.pop(elem[0])
+
+                # sort the extensions back
+                filter_list.sort(key=lambda x: x.rpartition('.')[2])
+
+                # add as a first element the last used extension
+                filter_list.insert(0, used_ext)
+                # add back the element that should always be the last (All Files)
+                filter_list.append(last_elem)
+
+                self.app.defaults[filter_string] = ';;'.join(filter_list)
+                return
+
+    @staticmethod
+    def poly2rings(poly):
+        return [poly.exterior] + [interior for interior in poly.interiors]
+
+    @property
+    def visible(self):
+        return self.shapes.visible
+
+    @visible.setter
+    def visible(self, value, threaded=True):
+        log.debug("FlatCAMObj.visible()")
+
+        def worker_task(app_obj):
+            self.shapes.visible = value
+
+            if self.app.is_legacy is False:
+                # Not all object types has annotations
+                try:
+                    self.annotation.visible = value
+                except Exception:
+                    pass
+
+        if threaded is False:
+            worker_task(app_obj=self.app)
+        else:
+            self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
+
+    @property
+    def drawing_tolerance(self):
+        self.units = self.app.defaults['units'].upper()
+        tol = self._drawing_tolerance if self.units == 'MM' or not self.units else self._drawing_tolerance / 25.4
+        return tol
+
+    @drawing_tolerance.setter
+    def drawing_tolerance(self, value):
+        self.units = self.app.defaults['units'].upper()
+        self._drawing_tolerance = value if self.units == 'MM' or not self.units else value / 25.4
+
+    def clear(self, update=False):
+        self.shapes.clear(update)
+
+        # Not all object types has annotations
+        try:
+            self.annotation.clear(update)
+        except AttributeError:
+            pass
+
+    def delete(self):
+        # Free resources
+        del self.ui
+        del self.options
+
+        # Set flag
+        self.deleted = True

+ 231 - 0
flatcamObjects/FlatCAMScript.py

@@ -0,0 +1,231 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# ##########################################################
+
+# ##########################################################
+# File modified by: Marius Stanciu                         #
+# ##########################################################
+
+from flatcamEditors.FlatCAMTextEditor import TextEditor
+from flatcamObjects.FlatCAMObj import *
+from flatcamGUI.ObjectUI import *
+
+import tkinter as tk
+import sys
+from copy import deepcopy
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class ScriptObject(FlatCAMObj):
+    """
+    Represents a TCL script object.
+    """
+    optionChanged = QtCore.pyqtSignal(str)
+    ui_type = ScriptObjectUI
+
+    def __init__(self, name):
+        self.decimals = self.app.decimals
+
+        log.debug("Creating a ScriptObject object...")
+        FlatCAMObj.__init__(self, name)
+
+        self.kind = "script"
+
+        self.options.update({
+            "plot": True,
+            "type": 'Script',
+            "source_file": '',
+        })
+
+        self.units = ''
+
+        self.ser_attrs = ['options', 'kind', 'source_file']
+        self.source_file = ''
+        self.script_code = ''
+
+        self.units_found = self.app.defaults['units']
+
+        # self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
+        self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
+
+    def set_ui(self, ui):
+        """
+        Sets the Object UI in Selected Tab for the FlatCAM Script type of object.
+        :param ui:
+        :return:
+        """
+        FlatCAMObj.set_ui(self, ui)
+        log.debug("ScriptObject.set_ui()")
+
+        assert isinstance(self.ui, ScriptObjectUI), \
+            "Expected a ScriptObjectUI, got %s" % type(self.ui)
+
+        self.units = self.app.defaults['units'].upper()
+        self.units_found = self.app.defaults['units']
+
+        # Fill form fields only on object create
+        self.to_form()
+
+        # Show/Hide Advanced Options
+        if self.app.defaults["global_app_level"] == 'b':
+            self.ui.level.setText(_(
+                '<span style="color:green;"><b>Basic</b></span>'
+            ))
+        else:
+            self.ui.level.setText(_(
+                '<span style="color:red;"><b>Advanced</b></span>'
+            ))
+
+        # tab_here = False
+        # # try to not add too many times a tab that it is already installed
+        # for idx in range(self.app.ui.plot_tab_area.count()):
+        #     if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
+        #         tab_here = True
+        #         break
+        #
+        # # add the tab if it is not already added
+        # if tab_here is False:
+        #     self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
+        #     self.script_editor_tab.setObjectName(self.options['name'])
+
+        self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
+        self.script_editor_tab.setObjectName(self.options['name'])
+
+        # first clear previous text in text editor (if any)
+        # self.script_editor_tab.code_editor.clear()
+        # self.script_editor_tab.code_editor.setReadOnly(False)
+
+        self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter'])
+        self.on_autocomplete_changed(state=self.app.defaults['script_autocompleter'])
+
+        self.script_editor_tab.buttonRun.show()
+
+        # Switch plot_area to CNCJob tab
+        self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
+
+        flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
+        self.script_editor_tab.buttonOpen.clicked.disconnect()
+        self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt))
+        self.script_editor_tab.buttonSave.clicked.disconnect()
+        self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt))
+
+        self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code)
+        self.script_editor_tab.handleTextChanged()
+
+        self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
+
+        self.ser_attrs = ['options', 'kind', 'source_file']
+
+        # ---------------------------------------------------- #
+        # ----------- LOAD THE TEXT SOURCE FILE -------------- #
+        # ---------------------------------------------------- #
+        self.app.proc_container.view.set_busy(_("Loading..."))
+        self.script_editor_tab.t_frame.hide()
+
+        try:
+            self.script_editor_tab.code_editor.setPlainText(self.source_file)
+            # for line in self.source_file.splitlines():
+            #     QtWidgets.QApplication.processEvents()
+            #     self.script_editor_tab.code_editor.append(line)
+        except Exception as e:
+            log.debug("ScriptObject.set_ui() --> %s" % str(e))
+
+        self.script_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.End)
+        self.script_editor_tab.t_frame.show()
+
+        self.app.proc_container.view.set_idle()
+        self.build_ui()
+
+    def build_ui(self):
+        FlatCAMObj.build_ui(self)
+
+    def handle_run_code(self):
+        # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
+        # tries to print on a hidden widget, therefore show the dock if hidden
+        if self.app.ui.shell_dock.isHidden():
+            self.app.ui.shell_dock.show()
+
+        self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText())
+
+        old_line = ''
+        for tcl_command_line in self.script_code.splitlines():
+            # do not process lines starting with '#' = comment and empty lines
+            if not tcl_command_line.startswith('#') and tcl_command_line != '':
+                # id FlatCAM is run in Windows then replace all the slashes with
+                # the UNIX style slash that TCL understands
+                if sys.platform == 'win32':
+                    if "open" in tcl_command_line:
+                        tcl_command_line = tcl_command_line.replace('\\', '/')
+
+                if old_line != '':
+                    new_command = old_line + tcl_command_line + '\n'
+                else:
+                    new_command = tcl_command_line
+
+                # execute the actual Tcl command
+                try:
+                    self.app.shell.open_processing()  # Disables input box.
+
+                    result = self.app.tcl.eval(str(new_command))
+                    if result != 'None':
+                        self.app.shell.append_output(result + '\n')
+
+                    old_line = ''
+                except tk.TclError:
+                    old_line = old_line + tcl_command_line + '\n'
+                except Exception as e:
+                    log.debug("ScriptObject.handleRunCode() --> %s" % str(e))
+
+        if old_line != '':
+            # it means that the script finished with an error
+            result = self.app.tcl.eval("set errorInfo")
+            log.error("Exec command Exception: %s" % (result + '\n'))
+            self.app.shell.append_error('ERROR: ' + result + '\n')
+
+        self.app.shell.close_processing()
+
+    def on_autocomplete_changed(self, state):
+        if state:
+            self.script_editor_tab.code_editor.completer_enable = True
+        else:
+            self.script_editor_tab.code_editor.completer_enable = False
+
+    def to_dict(self):
+        """
+        Returns a representation of the object as a dictionary.
+        Attributes to include are listed in ``self.ser_attrs``.
+
+        :return: A dictionary-encoded copy of the object.
+        :rtype: dict
+        """
+        d = {}
+        for attr in self.ser_attrs:
+            d[attr] = getattr(self, attr)
+        return d
+
+    def from_dict(self, d):
+        """
+        Sets object's attributes from a dictionary.
+        Attributes to include are listed in ``self.ser_attrs``.
+        This method will look only for only and all the
+        attributes in ``self.ser_attrs``. They must all
+        be present. Use only for deserializing saved
+        objects.
+
+        :param d: Dictionary of attributes to set in the object.
+        :type d: dict
+        :return: None
+        """
+        for attr in self.ser_attrs:
+            setattr(self, attr, d[attr])

+ 0 - 0
flatcamObjects/__init__.py


+ 62 - 63
flatcamParsers/ParseExcellon.py

@@ -7,7 +7,6 @@
 # ########################################################## ##
 
 from camlib import Geometry
-import FlatCAMApp
 
 import shapely.affinity as affinity
 from shapely.geometry import Point, LineString
@@ -19,6 +18,7 @@ import traceback
 from copy import deepcopy
 
 import FlatCAMTranslation as fcTranslate
+from FlatCAMCommon import GracefulException as grace
 
 import gettext
 import builtins
@@ -86,6 +86,7 @@ class Excellon(Geometry):
         :return: Excellon object.
         :rtype: Excellon
         """
+
         self.decimals = self.app.decimals
 
         if geo_steps_per_circle is None:
@@ -241,12 +242,12 @@ class Excellon(Geometry):
 
     def parse_file(self, filename=None, file_obj=None):
         """
-        Reads the specified file as array of lines as
-        passes it to ``parse_lines()``.
+        Reads the specified file as array of lines as passes it to ``parse_lines()``.
 
-        :param filename: The file to be read and parsed.
-        :type filename: str
-        :return: None
+        :param filename:    The file to be read and parsed.
+        :param file_obj:
+        :type filename:     str
+        :return:            None
         """
         if file_obj:
             estr = file_obj
@@ -298,7 +299,7 @@ class Excellon(Geometry):
             for eline in elines:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 line_num += 1
                 # log.debug("%3d %s" % (line_num, str(eline)))
@@ -526,7 +527,7 @@ class Excellon(Geometry):
                             slot_dia = 0.05
                             try:
                                 slot_dia = float(self.tools[current_tool]['C'])
-                            except Exception as e:
+                            except Exception:
                                 pass
                             log.debug(
                                 'Milling/Drilling slot with tool %s, diam=%f' % (
@@ -596,7 +597,7 @@ class Excellon(Geometry):
                             slot_dia = 0.05
                             try:
                                 slot_dia = float(self.tools[current_tool]['C'])
-                            except Exception as e:
+                            except Exception:
                                 pass
                             log.debug(
                                 'Milling/Drilling slot with tool %s, diam=%f' % (
@@ -893,9 +894,8 @@ class Excellon(Geometry):
             log.info("Zeros: %s, Units %s." % (self.zeros, self.units))
         except Exception:
             log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
-            msg = '[ERROR_NOTCL] %s' % \
-                  _("An internal error has ocurred. See shell.\n")
-            msg += ('{e_code} {tx} {l_nr}: {line}\n').format(
+            msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n")
+            msg += '{e_code} {tx} {l_nr}: {line}\n'.format(
                 e_code='[ERROR]',
                 tx=_("Excellon Parser error.\nParsing Failed. Line"),
                 l_nr=line_num,
@@ -1010,13 +1010,13 @@ class Excellon(Geometry):
                       "Excellon geometry creation failed due of ERROR: %s" % str(e))
             return "fail"
 
-    def bounds(self):
+    def bounds(self, flatten=None):
         """
         Returns coordinates of rectangular bounds
         of Excellon geometry: (xmin, ymin, xmax, ymax).
+
+        :param flatten:     No used
         """
-        # fixed issue of getting bounds only for one level lists of objects
-        # now it can get bounds for nested lists of objects
 
         log.debug("flatcamParsers.ParseExcellon.Excellon.bounds()")
 
@@ -1056,11 +1056,11 @@ class Excellon(Geometry):
         maxy_list = []
 
         for tool in self.tools:
-            minx, miny, maxx, maxy = bounds_rec(self.tools[tool]['solid_geometry'])
-            minx_list.append(minx)
-            miny_list.append(miny)
-            maxx_list.append(maxx)
-            maxy_list.append(maxy)
+            eminx, eminy, emaxx, emaxy = bounds_rec(self.tools[tool]['solid_geometry'])
+            minx_list.append(eminx)
+            miny_list.append(eminy)
+            maxx_list.append(emaxx)
+            maxy_list.append(emaxy)
 
         return min(minx_list), min(miny_list), max(maxx_list), max(maxy_list)
 
@@ -1075,8 +1075,9 @@ class Excellon(Geometry):
 
         Kind of convolute way to make the conversion and it is based on the assumption that the Excellon file
         will have detected the units before the tools are parsed and stored in self.tools
-        :param units:
-        :type str: IN or MM
+
+        :param units:   'IN' or 'MM'. String
+
         :return:
         """
 
@@ -1109,12 +1110,13 @@ class Excellon(Geometry):
         Scales geometry on the XY plane in the object by a given factor.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
 
-        :param xfactor: Number by which to scale the object.
-        :type xfactor: float
-        :param yfactor: Number by which to scale the object.
-        :type yfactor: float
-        :return: None
-        :rtype: NOne
+        :param xfactor:     Number by which to scale the object.
+        :type xfactor:      float
+        :param yfactor:     Number by which to scale the object.
+        :type yfactor:      float
+        :param point:       Origin point for scale
+        :return:            None
+        :rtype:             None
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.scale()")
 
@@ -1145,8 +1147,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
@@ -1190,12 +1191,12 @@ class Excellon(Geometry):
             return
 
         def offset_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
-                for g in obj:
-                    new_obj.append(offset_geom(g))
+                for geo in obj:
+                    new_obj.append(offset_geom(geo))
                 return new_obj
-            else:
+            except TypeError:
                 try:
                     return affinity.translate(obj, xoff=dx, yoff=dy)
                 except AttributeError:
@@ -1204,8 +1205,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
@@ -1237,11 +1237,11 @@ class Excellon(Geometry):
     def mirror(self, axis, point):
         """
 
-        :param axis: "X" or "Y" indicates around which axis to mirror.
-        :type axis: str
-        :param point: [x, y] point belonging to the mirror axis.
-        :type point: list
-        :return: None
+        :param axis:        "X" or "Y" indicates around which axis to mirror.
+        :type axis:         str
+        :param point:       [x, y] point belonging to the mirror axis.
+        :type point:        list
+        :return:            None
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.mirror()")
 
@@ -1249,12 +1249,12 @@ class Excellon(Geometry):
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
 
         def mirror_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
-                for g in obj:
-                    new_obj.append(mirror_geom(g))
+                for geo in obj:
+                    new_obj.append(mirror_geom(geo))
                 return new_obj
-            else:
+            except TypeError:
                 try:
                     return affinity.scale(obj, xscale, yscale, origin=(px, py))
                 except AttributeError:
@@ -1265,8 +1265,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
@@ -1300,12 +1299,12 @@ class Excellon(Geometry):
         Shear/Skew the geometries of an object by angles along x and y dimensions.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
 
-        Parameters
-        ----------
-        xs, ys : float, float
+        :param angle_x:
+        :param angle_y:
             The shear angle(s) for the x and y axes respectively. These can be
             specified in either degrees (default) or radians by setting
             use_radians=True.
+        :param point:       Origin point for Skew
 
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
@@ -1322,12 +1321,12 @@ class Excellon(Geometry):
             return
 
         def skew_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
                 for g in obj:
                     new_obj.append(skew_geom(g))
                 return new_obj
-            else:
+            except TypeError:
                 try:
                     return affinity.skew(obj, angle_x, angle_y, origin=(px, py))
                 except AttributeError:
@@ -1336,8 +1335,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
@@ -1393,9 +1391,10 @@ class Excellon(Geometry):
     def rotate(self, angle, point=None):
         """
         Rotate the geometry of an object by an angle around the 'point' coordinates
+
         :param angle:
-        :param point: tuple of coordinates (x, y)
-        :return:
+        :param point:   tuple of coordinates (x, y)
+        :return:        None
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.rotate()")
 
@@ -1423,8 +1422,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
@@ -1476,9 +1474,10 @@ class Excellon(Geometry):
     def buffer(self, distance, join, factor):
         """
 
-        :param distance: if 'factor' is True then distance is the factor
-        :param factor: True or False (None)
-        :return:
+        :param distance:    if 'factor' is True then distance is the factor
+        :param factor:      True or False (None)
+        :param join:        The type of line joint used by the shapely buffer method: round, square, bevel
+        :return:            None
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
 
@@ -1486,12 +1485,12 @@ class Excellon(Geometry):
             return
 
         def buffer_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
                 for g in obj:
                     new_obj.append(buffer_geom(g))
                 return new_obj
-            else:
+            except TypeError:
                 try:
                     if factor is None:
                         return obj.buffer(distance, resolution=self.geo_steps_per_circle)

+ 24 - 25
flatcamParsers/ParseGerber.py

@@ -1,6 +1,5 @@
 from PyQt5 import QtWidgets
 from camlib import Geometry, arc, arc_angle, ApertureMacro
-import FlatCAMApp
 
 import numpy as np
 import re
@@ -9,15 +8,16 @@ import traceback
 from copy import deepcopy
 import sys
 
-from shapely.ops import cascaded_union, unary_union
-from shapely.geometry import Polygon, MultiPolygon, LineString, Point
+from shapely.ops import cascaded_union
+from shapely.affinity import scale, translate
 import shapely.affinity as affinity
-from shapely.geometry import box as shply_box
+from shapely.geometry import box as shply_box, Polygon, LineString, Point, MultiPolygon
 
 from lxml import etree as ET
-from flatcamParsers.ParseSVG import *
-
+from flatcamParsers.ParseSVG import svgparselength, getsvggeo
+from FlatCAMCommon import GracefulException as grace
 import FlatCAMTranslation as fcTranslate
+
 import gettext
 import builtins
 
@@ -255,7 +255,7 @@ class Gerber(Geometry):
         """
         if self.app.abort_flag:
             # graceful abort requested by the user
-            raise FlatCAMApp.GracefulException
+            raise grace
 
         # Found some Gerber with a leading zero in the aperture id and the
         # referenced it without the zero, so this is a hack to handle that.
@@ -403,7 +403,7 @@ class Gerber(Geometry):
 
         # Absolute or Relative/Incremental coordinates
         # Not implemented
-        absolute = True
+        # absolute = True
 
         # How to interpret circular interpolation: SINGLE or MULTI
         quadrant_mode = None
@@ -428,7 +428,7 @@ class Gerber(Geometry):
             for gline in glines:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 line_num += 1
                 self.source_file += gline + '\n'
@@ -986,7 +986,7 @@ class Gerber(Geometry):
                                         if 'geometry' not in self.apertures[current_aperture]:
                                             self.apertures[current_aperture]['geometry'] = []
                                         self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
-                                except Exception as e:
+                                except Exception:
                                     pass
                             last_path_aperture = current_aperture
                             # we do this for the case that a region is done without having defined any aperture
@@ -1229,25 +1229,25 @@ class Gerber(Geometry):
                     try:
                         circular_x = parse_gerber_number(circular_x,
                                                          self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         circular_x = current_x
 
                     try:
                         circular_y = parse_gerber_number(circular_y,
                                                          self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         circular_y = current_y
 
                     # According to Gerber specification i and j are not modal, which means that when i or j are missing,
                     # they are to be interpreted as being zero
                     try:
                         i = parse_gerber_number(i, self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         i = 0
 
                     try:
                         j = parse_gerber_number(j, self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         j = 0
 
                     if quadrant_mode is None:
@@ -1668,13 +1668,14 @@ class Gerber(Geometry):
             bbox = bbox.envelope
         return bbox
 
-    def bounds(self):
+    def bounds(self, flatten=None):
         """
         Returns coordinates of rectangular bounds
         of Gerber geometry: (xmin, ymin, xmax, ymax).
+
+        :param flatten:     Not used, it is here for compatibility with base class method
+        :return:            None
         """
-        # fixed issue of getting bounds only for one level lists of objects
-        # now it can get bounds for nested lists of objects
 
         log.debug("parseGerber.Gerber.bounds()")
 
@@ -1999,8 +2000,7 @@ class Gerber(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for __ in self.solid_geometry:
-                self.geo_len += 1
+            self.geo_len = len(self.solid_geometry)
         except TypeError:
             self.geo_len = 1
 
@@ -2078,8 +2078,7 @@ class Gerber(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for __ in self.solid_geometry:
-                self.geo_len += 1
+            self.geo_len = len(self.solid_geometry)
         except TypeError:
             self.geo_len = 1
 
@@ -2217,8 +2216,7 @@ class Gerber(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            for __ in self.solid_geometry:
-                self.geo_len += 1
+            self.geo_len = len(self.solid_geometry)
         except TypeError:
             self.geo_len = 1
 
@@ -2266,8 +2264,9 @@ class Gerber(Geometry):
     def buffer(self, distance, join, factor=None):
         """
 
-        :param distance: if 'factor' is True then distance is the factor
-        :param factor: True or False (None)
+        :param distance:    If 'factor' is True then distance is the factor
+        :param join:        The type of joining used by the Shapely buffer method. Can be: round, square and bevel
+        :param factor:      True or False (None)
         :return:
         """
         log.debug("parseGerber.Gerber.buffer()")

+ 3 - 3
flatcamParsers/ParseHPGL2.py

@@ -7,7 +7,6 @@
 # ############################################################
 
 from camlib import arc, three_point_circle
-import FlatCAMApp
 
 import numpy as np
 import re
@@ -19,6 +18,7 @@ import sys
 from shapely.ops import unary_union
 from shapely.geometry import LineString, Point
 
+from FlatCAMCommon import GracefulException as grace
 import FlatCAMTranslation as fcTranslate
 import gettext
 import builtins
@@ -180,7 +180,7 @@ class HPGL2:
             for gline in glines:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 line_num += 1
                 self.source_file += gline + '\n'
@@ -304,7 +304,7 @@ class HPGL2:
                                                  (_("Coordinates missing, line ignored"), str(gline)))
 
                         if current_x is not None and current_y is not None:
-                            radius = match.group(1)
+                            radius = float(match.group(1))
                             geo = Point((current_x, current_y)).buffer(radius, int(self.steps_per_circle))
                             geo_line = geo.exterior
                             self.tools[current_tool]['solid_geometry'].append(geo_line)

+ 3 - 3
flatcamTools/ToolAlignObjects.py

@@ -382,7 +382,7 @@ class AlignObjects(FlatCAMTool):
     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()
@@ -397,14 +397,14 @@ class AlignObjects(FlatCAMTool):
                 return
             else:
                 self.app.inform.emit('%s: %s. %s' % (
-                    _("Second Point"), _("Click on the START point."), _(" Or right click to cancel.")))
+                    _("Second Point"), _("Click on the START point."), _("Or right click to cancel.")))
                 self.target_obj = self.aligned_obj
                 self.reset_color()
                 self.set_color()
 
         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()

+ 7 - 8
flatcamTools/ToolCopperThieving.py

@@ -7,10 +7,9 @@
 
 from PyQt5 import QtWidgets, QtCore
 
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, FCEntry, FCComboBox
-from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon
 
 import shapely.geometry.base as base
 from shapely.ops import cascaded_union, unary_union
@@ -994,7 +993,7 @@ class ToolCopperThieving(FlatCAMTool):
                 for pol in app_obj.grb_object.solid_geometry:
                     if app_obj.app.abort_flag:
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
 
                     clearance_geometry.append(
                         pol.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
@@ -1073,7 +1072,7 @@ class ToolCopperThieving(FlatCAMTool):
                     for poly in working_obj:
                         if app_obj.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
                         geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
                 except TypeError:
                     geo_buff_list.append(working_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
@@ -1082,7 +1081,7 @@ class ToolCopperThieving(FlatCAMTool):
             else:   # ref_selected == 'box'
                 geo_n = working_obj.solid_geometry
 
-                if isinstance(working_obj, FlatCAMGeometry):
+                if working_obj.kind == 'geometry':
                     try:
                         __ = iter(geo_n)
                     except Exception as e:
@@ -1093,11 +1092,11 @@ class ToolCopperThieving(FlatCAMTool):
                     for poly in geo_n:
                         if app_obj.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
                         geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
 
                     bounding_box = cascaded_union(geo_buff_list)
-                elif isinstance(working_obj, FlatCAMGerber):
+                elif working_obj.kind == 'gerber':
                     geo_n = cascaded_union(geo_n).convex_hull
                     bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
                     bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
@@ -1192,7 +1191,7 @@ class ToolCopperThieving(FlatCAMTool):
                     for pol in app_obj.grb_object.solid_geometry:
                         if app_obj.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         outline_geometry.append(
                             pol.buffer(c_val+half_thick_line, int(int(app_obj.geo_steps_per_circle) / 4))

+ 44 - 39
flatcamTools/ToolCutOut.py

@@ -8,7 +8,6 @@
 from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, OptionalInputSection, FCButton
-from FlatCAMObj import FlatCAMGerber
 
 from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing
 from shapely.ops import cascaded_union, unary_union
@@ -270,7 +269,7 @@ class CutOut(FlatCAMTool):
         form_layout_2.addRow(gaps_label, self.gaps)
 
         # Buttons
-        self.ff_cutout_object_btn = QtWidgets.QPushButton(_("Generate Freeform Geometry"))
+        self.ff_cutout_object_btn = FCButton(_("Generate Freeform Geometry"))
         self.ff_cutout_object_btn.setToolTip(
             _("Cutout the selected object.\n"
               "The cutout shape can be of any shape.\n"
@@ -284,7 +283,7 @@ class CutOut(FlatCAMTool):
                         """)
         grid0.addWidget(self.ff_cutout_object_btn, 20, 0, 1, 2)
 
-        self.rect_cutout_object_btn = QtWidgets.QPushButton(_("Generate Rectangular Geometry"))
+        self.rect_cutout_object_btn = FCButton(_("Generate Rectangular Geometry"))
         self.rect_cutout_object_btn.setToolTip(
             _("Cutout the selected object.\n"
               "The resulting cutout shape is\n"
@@ -335,7 +334,7 @@ class CutOut(FlatCAMTool):
 
         # form_layout_3.addRow(e_lab_0)
 
-        self.man_geo_creation_btn = QtWidgets.QPushButton(_("Generate Manual Geometry"))
+        self.man_geo_creation_btn = FCButton(_("Generate Manual Geometry"))
         self.man_geo_creation_btn.setToolTip(
             _("If the object to be cutout is a Gerber\n"
               "first create a Geometry that surrounds it,\n"
@@ -350,7 +349,7 @@ class CutOut(FlatCAMTool):
                         """)
         grid0.addWidget(self.man_geo_creation_btn, 24, 0, 1, 2)
 
-        self.man_gaps_creation_btn = QtWidgets.QPushButton(_("Manual Add Bridge Gaps"))
+        self.man_gaps_creation_btn = FCButton(_("Manual Add Bridge Gaps"))
         self.man_gaps_creation_btn.setToolTip(
             _("Use the left mouse button (LMB) click\n"
               "to create a bridge gap to separate the PCB from\n"
@@ -369,7 +368,7 @@ class CutOut(FlatCAMTool):
         self.layout.addStretch()
 
         # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button = FCButton(_("Reset Tool"))
         self.reset_button.setToolTip(
             _("Will reset the tool parameters.")
         )
@@ -525,7 +524,7 @@ class CutOut(FlatCAMTool):
         def geo_init(geo_obj, app_obj):
             solid_geo = []
 
-            if isinstance(cutout_obj, FlatCAMGerber):
+            if cutout_obj.kind == 'gerber':
                 if isinstance(cutout_obj.solid_geometry, list):
                     cutout_obj.solid_geometry = MultiPolygon(cutout_obj.solid_geometry)
 
@@ -542,12 +541,12 @@ class CutOut(FlatCAMTool):
 
             def cutout_handler(geom):
                 # Get min and max data for each object as we just cut rectangles across X or Y
-                xmin, ymin, xmax, ymax = recursive_bounds(geom)
+                xxmin, yymin, xxmax, yymax = recursive_bounds(geom)
 
-                px = 0.5 * (xmin + xmax) + margin
-                py = 0.5 * (ymin + ymax) + margin
-                lenx = (xmax - xmin) + (margin * 2)
-                leny = (ymax - ymin) + (margin * 2)
+                px = 0.5 * (xxmin + xxmax) + margin
+                py = 0.5 * (yymin + yymax) + margin
+                lenx = (xxmax - xxmin) + (margin * 2)
+                leny = (yymax - yymin) + (margin * 2)
 
                 proc_geometry = []
                 if gaps == 'None':
@@ -555,41 +554,41 @@ class CutOut(FlatCAMTool):
                 else:
                     if gaps == '8' or gaps == '2LR':
                         geom = self.subtract_poly_from_geo(geom,
-                                                           xmin - gapsize,  # botleft_x
+                                                           xxmin - gapsize,  # botleft_x
                                                            py - gapsize + leny / 4,  # botleft_y
-                                                           xmax + gapsize,  # topright_x
+                                                           xxmax + gapsize,  # topright_x
                                                            py + gapsize + leny / 4)  # topright_y
                         geom = self.subtract_poly_from_geo(geom,
-                                                           xmin - gapsize,
+                                                           xxmin - gapsize,
                                                            py - gapsize - leny / 4,
-                                                           xmax + gapsize,
+                                                           xxmax + gapsize,
                                                            py + gapsize - leny / 4)
 
                     if gaps == '8' or gaps == '2TB':
                         geom = self.subtract_poly_from_geo(geom,
                                                            px - gapsize + lenx / 4,
-                                                           ymin - gapsize,
+                                                           yymin - gapsize,
                                                            px + gapsize + lenx / 4,
-                                                           ymax + gapsize)
+                                                           yymax + gapsize)
                         geom = self.subtract_poly_from_geo(geom,
                                                            px - gapsize - lenx / 4,
-                                                           ymin - gapsize,
+                                                           yymin - gapsize,
                                                            px + gapsize - lenx / 4,
-                                                           ymax + gapsize)
+                                                           yymax + gapsize)
 
                     if gaps == '4' or gaps == 'LR':
                         geom = self.subtract_poly_from_geo(geom,
-                                                           xmin - gapsize,
+                                                           xxmin - gapsize,
                                                            py - gapsize,
-                                                           xmax + gapsize,
+                                                           xxmax + gapsize,
                                                            py + gapsize)
 
                     if gaps == '4' or gaps == 'TB':
                         geom = self.subtract_poly_from_geo(geom,
                                                            px - gapsize,
-                                                           ymin - gapsize,
+                                                           yymin - gapsize,
                                                            px + gapsize,
-                                                           ymax + gapsize)
+                                                           yymax + gapsize)
 
                 try:
                     for g in geom:
@@ -603,7 +602,7 @@ class CutOut(FlatCAMTool):
                 object_geo = unary_union(object_geo)
 
                 # for geo in object_geo:
-                if isinstance(cutout_obj, FlatCAMGerber):
+                if cutout_obj.kind == 'gerber':
                     if isinstance(object_geo, MultiPolygon):
                         x0, y0, x1, y1 = object_geo.bounds
                         object_geo = box(x0, y0, x1, y1)
@@ -623,7 +622,7 @@ class CutOut(FlatCAMTool):
                     object_geo = [object_geo]
 
                 for geom_struct in object_geo:
-                    if isinstance(cutout_obj, FlatCAMGerber):
+                    if cutout_obj.kind == 'gerber':
                         if margin >= 0:
                             geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior
                         else:
@@ -775,7 +774,7 @@ class CutOut(FlatCAMTool):
 
                 # if Gerber create a buffer at a distance
                 # if Geometry then cut through the geometry
-                if isinstance(cutout_obj, FlatCAMGerber):
+                if cutout_obj.kind == 'gerber':
                     if margin >= 0:
                         geo = geo.buffer(margin + abs(dia / 2))
                     else:
@@ -909,7 +908,7 @@ class CutOut(FlatCAMTool):
                                    "Select one and try again."))
             return
 
-        if not isinstance(cutout_obj, FlatCAMGerber):
+        if cutout_obj.kind != 'gerber':
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("The selected object has to be of Gerber type.\n"
                                    "Select a Gerber file and try again."))
@@ -988,11 +987,11 @@ class CutOut(FlatCAMTool):
 
         if self.app.is_legacy is False:
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
+            # event_is_dragging = event.is_dragging
             right_button = 2
         else:
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
+            # event_is_dragging = self.app.plotcanvas.is_dragging
             right_button = 3
 
         try:
@@ -1038,11 +1037,11 @@ class CutOut(FlatCAMTool):
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_is_dragging = event.is_dragging
-            right_button = 2
+            # right_button = 2
         else:
             event_pos = (event.xdata, event.ydata)
             event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+            # right_button = 3
 
         try:
             x = float(event_pos[0])
@@ -1159,13 +1158,17 @@ class CutOut(FlatCAMTool):
             if '+' in key_string:
                 mod, __, key_text = key_string.rpartition('+')
                 if mod.lower() == 'ctrl':
-                    modifiers = QtCore.Qt.ControlModifier
+                    # modifiers = QtCore.Qt.ControlModifier
+                    pass
                 elif mod.lower() == 'alt':
-                    modifiers = QtCore.Qt.AltModifier
+                    # modifiers = QtCore.Qt.AltModifier
+                    pass
                 elif mod.lower() == 'shift':
-                    modifiers = QtCore.Qt.ShiftModifier
+                    # modifiers = QtCore.Qt.ShiftModifier
+                    pass
                 else:
-                    modifiers = QtCore.Qt.NoModifier
+                    # modifiers = QtCore.Qt.NoModifier
+                    pass
                 key = QtGui.QKeySequence(key_text)
         # events from Vispy are of type KeyEvent
         else:
@@ -1203,7 +1206,8 @@ class CutOut(FlatCAMTool):
             geo = self.cutting_geo(pos=(l_x, l_y))
             self.draw_utility_geometry(geo=geo)
 
-    def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
+    @staticmethod
+    def subtract_poly_from_geo(solid_geo, x0, y0, x1, y1):
         """
         Subtract polygon made from points from the given object.
         This only operates on the paths in the original geometry,
@@ -1270,8 +1274,9 @@ def flatten(geometry):
 
 def recursive_bounds(geometry):
     """
-    Returns coordinates of rectangular bounds
-    of geometry: (xmin, ymin, xmax, ymax).
+
+    :param geometry:    a iterable object that holds geometry
+    :return:            Returns coordinates of rectangular bounds of geometry: (xmin, ymin, xmax, ymax).
     """
 
     # now it can get bounds for nested lists of objects

+ 4 - 5
flatcamTools/ToolDblSided.py

@@ -3,7 +3,6 @@ from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry, FCButton, FCComboBox
-from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry
 
 from numpy import Inf
 
@@ -192,7 +191,7 @@ class DblSidedTool(FlatCAMTool):
         # Add a reference
         self.add_point_button = QtWidgets.QPushButton(_("Add"))
         self.add_point_button.setToolTip(
-            _("Add the coordinates in format <b>(x, y)</b> through which the mirroring axis \n "
+            _("Add the coordinates in format <b>(x, y)</b> through which the mirroring axis\n "
               "selected in 'MIRROR AXIS' pass.\n"
               "The (x, y) coordinates are captured by pressing SHIFT key\n"
               "and left mouse button click on canvas or you can enter the coordinates manually.")
@@ -658,7 +657,7 @@ class DblSidedTool(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             return
 
-        if not isinstance(fcobj, FlatCAMGerber):
+        if fcobj.kind != 'gerber':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             return
 
@@ -701,7 +700,7 @@ class DblSidedTool(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ..."))
             return
 
-        if not isinstance(fcobj, FlatCAMExcellon):
+        if fcobj.kind != 'excellon':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             return
 
@@ -745,7 +744,7 @@ class DblSidedTool(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object loaded ..."))
             return
 
-        if not isinstance(fcobj, FlatCAMGeometry):
+        if fcobj.kind != 'geometry':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             return
 

+ 6 - 7
flatcamTools/ToolMove.py

@@ -8,7 +8,6 @@
 from PyQt5 import QtWidgets, QtCore
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.VisPyVisuals import *
-from FlatCAMObj import FlatCAMGerber
 
 from copy import copy
 import logging
@@ -128,7 +127,7 @@ class ToolMove(FlatCAMTool):
                 pos_canvas = self.app.plotcanvas.translate_coords(event_pos)
 
                 # if GRID is active we need to get the snapped positions
-                if self.app.grid_status() == True:
+                if self.app.grid_status():
                     pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                 else:
                     pos = pos_canvas
@@ -148,7 +147,7 @@ class ToolMove(FlatCAMTool):
                     self.delete_shape()
 
                     # if GRID is active we need to get the snapped positions
-                    if self.app.grid_status() == True:
+                    if self.app.grid_status():
                         pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                     else:
                         pos = pos_canvas
@@ -171,7 +170,7 @@ class ToolMove(FlatCAMTool):
                                 # remove any mark aperture shape that may be displayed
                                 for sel_obj in obj_list:
                                     # if the Gerber mark shapes are enabled they need to be disabled before move
-                                    if isinstance(sel_obj, FlatCAMGerber):
+                                    if sel_obj.kind == 'gerber':
                                         sel_obj.ui.aperture_table_visibility_cb.setChecked(False)
 
                                     try:
@@ -198,8 +197,8 @@ class ToolMove(FlatCAMTool):
                                     elif sel_obj.kind == 'excellon':
                                         sel_obj.source_file = self.app.export_excellon(
                                             obj_name=out_name, filename=None, local_use=sel_obj, use_thread=False)
-                            except Exception as e:
-                                log.debug('[ERROR_NOTCL] %s --> %s' % ('ToolMove.on_left_click()', str(e)))
+                            except Exception as err:
+                                log.debug('[ERROR_NOTCL] %s --> %s' % ('ToolMove.on_left_click()', str(err)))
                                 return "fail"
 
                             # time to plot the moved objects
@@ -249,7 +248,7 @@ class ToolMove(FlatCAMTool):
         pos_canvas = self.app.plotcanvas.translate_coords((x, y))
 
         # if GRID is active we need to get the snapped positions
-        if self.app.grid_status() == True:
+        if self.app.grid_status():
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
             pos = pos_canvas

+ 30 - 30
flatcamTools/ToolNCC.py

@@ -12,7 +12,7 @@ from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTabl
     FCComboBox, OptionalInputSection
 from flatcamParsers.ParseGerber import Gerber
 
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 from copy import deepcopy
 
@@ -1987,7 +1987,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             for poly in env_obj:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
             bounding_box = cascaded_union(geo_buff_list)
         elif ncc_select == _("Reference Object"):
@@ -1996,7 +1996,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 for poly in env_obj:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
                 bounding_box = cascaded_union(geo_buff_list)
@@ -2090,7 +2090,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             if isinstance(geo_elem, Polygon):
                                 for ring in self.poly2rings(geo_elem):
@@ -2263,7 +2263,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # ##########################################################################################
         def gen_clear_area(geo_obj, app_obj):
             assert geo_obj.kind == 'geometry', \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+                "Initializer expected a GeometryObject, got %s" % type(geo_obj)
 
             # provide the app with a way to process the GUI events when in a blocking loop
             if not run_threaded:
@@ -2312,7 +2312,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 # provide the app with a way to process the GUI events when in a blocking loop
                 QtWidgets.QApplication.processEvents()
@@ -2377,7 +2377,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             # clean the polygon
                             p = p.buffer(0)
@@ -2551,7 +2551,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # ###########################################################################################
         def gen_clear_area_rest(geo_obj, app_obj):
             assert geo_obj.kind == 'geometry', \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+                "Initializer expected a GeometryObject, got %s" % type(geo_obj)
 
             log.debug("NCC Tool. Rest machining copper clearing task started.")
             app_obj.inform.emit('_(NCC Tool. Rest machining copper clearing task started.')
@@ -2595,7 +2595,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 # provide the app with a way to process the GUI events when in a blocking loop
                 QtWidgets.QApplication.processEvents()
@@ -2644,7 +2644,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                     if self.app.abort_flag:
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     try:
                         area = area.difference(poly)
                     except Exception:
@@ -2674,7 +2674,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         for p in area.geoms:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             # clean the polygon
                             p = p.buffer(0)
@@ -2753,7 +2753,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
@@ -2771,7 +2771,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                             for p in cleared_area:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
                                 poly = p.buffer(buffer_value)
                                 cleared_by_last_tool.append(poly)
@@ -2836,7 +2836,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     app_obj.new_object("geometry", name, gen_clear_area_rest)
                 else:
                     app_obj.new_object("geometry", name, gen_clear_area)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 if run_threaded:
                     proc.done()
                 return
@@ -2999,7 +2999,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             for poly in geo_n:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
             bounding_box = cascaded_union(geo_buff_list)
@@ -3017,7 +3017,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 for poly in geo_n:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
                 bounding_box = cascaded_union(geo_buff_list)
@@ -3045,7 +3045,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # ##########################################################################################
         def gen_clear_area(geo_obj, app_obj):
             assert geo_obj.kind == 'geometry', \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+                "Initializer expected a GeometryObject, got %s" % type(geo_obj)
 
             # provide the app with a way to process the GUI events when in a blocking loop
             if not run_threaded:
@@ -3141,7 +3141,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
                                 if isinstance(geo_elem, Polygon):
                                     for ring in self.poly2rings(geo_elem):
@@ -3242,7 +3242,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 # provide the app with a way to process the GUI events when in a blocking loop
                 QtWidgets.QApplication.processEvents()
@@ -3283,7 +3283,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             # clean the polygon
                             p = p.buffer(0)
@@ -3446,7 +3446,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # ###########################################################################################
         def gen_clear_area_rest(geo_obj, app_obj):
             assert geo_obj.kind == 'geometry', \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+                "Initializer expected a GeometryObject, got %s" % type(geo_obj)
 
             log.debug("NCC Tool. Rest machining copper clearing task started.")
             app_obj.inform.emit('_(NCC Tool. Rest machining copper clearing task started.')
@@ -3520,7 +3520,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
                                 if isinstance(geo_elem, Polygon):
                                     for ring in self.poly2rings(geo_elem):
@@ -3614,7 +3614,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
             if self.app.abort_flag:
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
             if type(empty) is Polygon:
                 empty = MultiPolygon([empty])
@@ -3628,7 +3628,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             while sorted_tools:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 tool = sorted_tools.pop(0)
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
@@ -3648,7 +3648,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                     if self.app.abort_flag:
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     try:
                         area = area.difference(poly_r)
                     except Exception:
@@ -3678,7 +3678,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         for p in area.geoms:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             # clean the polygon
                             p = p.buffer(0)
@@ -3754,7 +3754,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
@@ -3772,7 +3772,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                             for p in cleared_area:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
                                 r_poly = p.buffer(buffer_value)
                                 cleared_by_last_tool.append(r_poly)
@@ -3833,7 +3833,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     app_obj.new_object("geometry", name, gen_clear_area_rest, plot=plot)
                 else:
                     app_obj.new_object("geometry", name, gen_clear_area, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 if run_threaded:
                     proc.done()
                 return
@@ -3887,7 +3887,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                     if self.app.abort_flag:
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     boundary = boundary.difference(el)
                     pol_nr += 1
                     disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))

+ 9 - 10
flatcamTools/ToolOptimal.py

@@ -9,8 +9,7 @@ from PyQt5 import QtWidgets, QtCore, QtGui
 
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox, FCComboBox
-from FlatCAMObj import FlatCAMGerber
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 from shapely.geometry import MultiPolygon
 from shapely.ops import nearest_points
@@ -343,7 +342,7 @@ class ToolOptimal(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             return
 
-        if not isinstance(fcobj, FlatCAMGerber):
+        if fcobj.kind != 'gerber':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
             return
 
@@ -365,7 +364,7 @@ class ToolOptimal(FlatCAMTool):
                         for geo_el in fcobj.apertures[ap]['geometry']:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                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'])
@@ -395,7 +394,7 @@ class ToolOptimal(FlatCAMTool):
                     for s_geo in total_geo[idx:]:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         # minimize the number of distances by not taking into considerations those that are too small
                         dist = geo.distance(s_geo)
@@ -459,7 +458,7 @@ class ToolOptimal(FlatCAMTool):
             log.debug("ToolOptimal.on_locate_position() --> first try %s" % str(e))
             self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
                                  "((x0, y0), (x1, y1)).")
-            return 'fail'
+            return
 
         try:
             loc_1 = loc[0]
@@ -471,7 +470,7 @@ class ToolOptimal(FlatCAMTool):
             self.app.on_jump_to(custom_location=loc)
         except Exception as e:
             log.debug("ToolOptimal.on_locate_position() --> sec try %s" % str(e))
-            return 'fail'
+            return
 
     def on_update_text(self, data):
         txt = ''
@@ -567,12 +566,12 @@ class ToolOptimal(FlatCAMTool):
             if self.selected_locations_text != '':
                 loc = eval(self.selected_locations_text)
             else:
-                return 'fail'
+                return
         except Exception as e:
             log.debug("ToolOptimal.on_locate_sec_position() --> first try %s" % str(e))
             self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
                                  "((x0, y0), (x1, y1)).")
-            return 'fail'
+            return
 
         try:
             loc_1 = loc[0]
@@ -584,7 +583,7 @@ class ToolOptimal(FlatCAMTool):
             self.app.on_jump_to(custom_location=loc)
         except Exception as e:
             log.debug("ToolOptimal.on_locate_sec_position() --> sec try %s" % str(e))
-            return 'fail'
+            return
 
     def reset_fields(self):
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 8 - 10
flatcamTools/ToolPDF.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 from shapely.geometry import Point, Polygon, LineString, MultiPolygon
 from shapely.ops import unary_union
@@ -190,7 +190,7 @@ class ToolPDF(FlatCAMTool):
 
         if self.app.abort_flag:
             # graceful abort requested by the user
-            raise FlatCAMApp.GracefulException
+            raise grace
 
         with self.app.proc_container.new(_("Parsing PDF file ...")):
             with open(filename, "rb") as f:
@@ -200,7 +200,7 @@ class ToolPDF(FlatCAMTool):
             for s in re.findall(self.stream_re, pdf):
                 if self.app.abort_flag:
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
                 stream_nr += 1
                 log.debug(" PDF STREAM: %d\n" % stream_nr)
@@ -291,7 +291,7 @@ class ToolPDF(FlatCAMTool):
     def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
         outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
 
-        def obj_init(grb_obj, app_obj):
+        def obj_init(grb_obj):
 
             grb_obj.apertures = ap_dict
 
@@ -404,7 +404,7 @@ class ToolPDF(FlatCAMTool):
                     for object_name in self.pdf_parsed:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         filename = deepcopy(self.pdf_parsed[object_name]['filename'])
                         pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
@@ -412,7 +412,7 @@ class ToolPDF(FlatCAMTool):
                         for k in pdf_content:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             ap_dict = pdf_content[k]
                             if ap_dict:
@@ -493,7 +493,7 @@ class ToolPDF(FlatCAMTool):
         for pline in lines:
             if self.app.abort_flag:
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
             line_nr += 1
             log.debug("line %d: %s" % (line_nr, pline))
@@ -868,7 +868,6 @@ class ToolPDF(FlatCAMTool):
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
-                        found_aperture = None
                     else:
                         if str(aperture) in apertures_dict.keys():
                             aperture += 1
@@ -1231,7 +1230,6 @@ class ToolPDF(FlatCAMTool):
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
-                        found_aperture = None
                     else:
                         if str(aperture) in apertures_dict.keys():
                             aperture += 1
@@ -1355,7 +1353,7 @@ class ToolPDF(FlatCAMTool):
 
         if self.app.abort_flag:
             # graceful abort requested by the user
-            raise FlatCAMApp.GracefulException
+            raise grace
 
         return object_dict
 

+ 23 - 23
flatcamTools/ToolPaint.py

@@ -14,7 +14,7 @@ from copy import deepcopy
 from flatcamParsers.ParseGerber import Gerber
 from camlib import Geometry, FlatCAMRTreeStorage
 from flatcamGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet, FCButton, FCComboBox
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 from shapely.geometry import base, Polygon, MultiPolygon, LinearRing, Point
 from shapely.ops import cascaded_union, unary_union, linemerge
@@ -1836,7 +1836,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                            contour=cont,
                                            connect=conn,
                                            prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Standard --> %s" % str(ee))
@@ -1850,7 +1850,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                             contour=cont,
                                             connect=conn,
                                             prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Seed --> %s" % str(ee))
@@ -1864,7 +1864,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                             contour=cont,
                                             connect=conn,
                                             prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Lines --> %s" % str(ee))
@@ -2015,7 +2015,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                 #                                  contour=cont,
                 #                                  connect=conn,
                 #                                  prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Laser Lines --> %s" % str(ee))
@@ -2052,7 +2052,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                                    contour=cont,
                                                    connect=conn,
                                                    prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Combo --> %s" % str(ee))
@@ -2199,7 +2199,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
                         geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
                                                             prog_plot=prog_plot)
@@ -2217,7 +2217,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     QtWidgets.QApplication.processEvents()
                     if self.app.abort_flag:
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
 
                     geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                         cont=cont, paint_method=paint_method, obj=obj,
@@ -2230,7 +2230,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     for x in cp:
                         total_geometry += list(x.get_objects())
                     final_solid_geometry += total_geometry
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
             except Exception as e:
                 log.debug("Could not Paint the polygons. %s" % str(e))
@@ -2305,7 +2305,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         def job_thread(app_obj):
             try:
                 ret = app_obj.new_object("geometry", name, job_init, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 proc.done()
                 return
             except Exception as er:
@@ -2376,7 +2376,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             """
             if self.app.abort_flag:
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
             if geometry is None:
                 return
@@ -2517,7 +2517,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
@@ -2542,7 +2542,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -2705,7 +2705,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
                                                                 prog_plot=prog_plot)
@@ -2723,7 +2723,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -2735,7 +2735,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         for x in cp:
                             cleared_geo += list(x.get_objects())
                         final_solid_geometry += cleared_geo
-                except FlatCAMApp.GracefulException:
+                except grace:
                     return "fail"
                 except Exception as e:
                     log.debug("Could not Paint the polygons. %s" % str(e))
@@ -2815,7 +2815,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot)
                 else:
                     ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 proc.done()
                 return
             except Exception as err:
@@ -2873,7 +2873,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             """
             if self.app.abort_flag:
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
             if geometry is None:
                 return
@@ -3015,7 +3015,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
@@ -3040,7 +3040,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -3193,7 +3193,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
@@ -3218,7 +3218,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -3312,7 +3312,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot)
                 else:
                     ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 proc.done()
                 return
             except Exception as err:

+ 18 - 20
flatcamTools/ToolPanelize.py

@@ -9,10 +9,8 @@ from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
 
 from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection, FCComboBox
-from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber, FlatCAMExcellon
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 from copy import deepcopy
-# from ObjectCollection import *
 import numpy as np
 
 import shapely.affinity as affinity
@@ -480,13 +478,13 @@ class Panelize(FlatCAMTool):
                     rows -= 1
                     panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
 
-        if isinstance(panel_obj, FlatCAMExcellon) or isinstance(panel_obj, FlatCAMGeometry):
+        if panel_obj.kind == 'excellon' or panel_obj.kind == 'geometry':
             # make a copy of the panelized Excellon or Geometry tools
             copied_tools = {}
             for tt, tt_val in list(panel_obj.tools.items()):
                 copied_tools[tt] = deepcopy(tt_val)
 
-        if isinstance(panel_obj, FlatCAMGerber):
+        if panel_obj.kind == 'gerber':
             # make a copy of the panelized Gerber apertures
             copied_apertures = {}
             for tt, tt_val in list(panel_obj.apertures.items()):
@@ -525,7 +523,7 @@ class Panelize(FlatCAMTool):
                                 for tool_dict in panel_obj.drills:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
 
                                     point_offseted = affinity.translate(tool_dict['point'], currentx, currenty)
                                     obj_fin.drills.append(
@@ -550,7 +548,7 @@ class Panelize(FlatCAMTool):
                                 for tool_dict in panel_obj.slots:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
 
                                     start_offseted = affinity.translate(tool_dict['start'], currentx, currenty)
                                     stop_offseted = affinity.translate(tool_dict['stop'], currentx, currenty)
@@ -600,20 +598,20 @@ class Panelize(FlatCAMTool):
                     obj_fin.solid_geometry = []
 
                     # create the initial structure on which to create the panel
-                    if isinstance(panel_obj, FlatCAMGeometry):
+                    if panel_obj.kind == 'geometry':
                         obj_fin.multigeo = panel_obj.multigeo
                         obj_fin.tools = copied_tools
                         if panel_obj.multigeo is True:
                             for tool in panel_obj.tools:
                                 obj_fin.tools[tool]['solid_geometry'][:] = []
-                    elif isinstance(panel_obj, FlatCAMGerber):
+                    elif panel_obj.kind == 'gerber':
                         obj_fin.apertures = copied_apertures
                         for ap in obj_fin.apertures:
                             obj_fin.apertures[ap]['geometry'] = []
 
                     # find the number of polygons in the source solid_geometry
                     geo_len = 0
-                    if isinstance(panel_obj, FlatCAMGeometry):
+                    if panel_obj.kind == 'geometry':
                         if panel_obj.multigeo is True:
                             for tool in panel_obj.tools:
                                 try:
@@ -625,7 +623,7 @@ class Panelize(FlatCAMTool):
                                 geo_len = len(panel_obj.solid_geometry)
                             except TypeError:
                                 geo_len = 1
-                    elif isinstance(panel_obj, FlatCAMGerber):
+                    elif panel_obj.kind == 'gerber':
                         for ap in panel_obj.apertures:
                             if 'geometry' in panel_obj.apertures[ap]:
                                 try:
@@ -641,12 +639,12 @@ class Panelize(FlatCAMTool):
                             element += 1
                             old_disp_number = 0
 
-                            if isinstance(panel_obj, FlatCAMGeometry):
+                            if panel_obj.kind == 'geometry':
                                 if panel_obj.multigeo is True:
                                     for tool in panel_obj.tools:
                                         if self.app.abort_flag:
                                             # graceful abort requested by the user
-                                            raise FlatCAMApp.GracefulException
+                                            raise grace
 
                                         # geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
                                         # if isinstance(geo, list):
@@ -678,7 +676,7 @@ class Panelize(FlatCAMTool):
                                     #     obj_fin.solid_geometry.append(geo)
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
 
                                     try:
                                         # calculate the number of polygons
@@ -690,7 +688,7 @@ class Panelize(FlatCAMTool):
                                         for geo_el in panel_obj.solid_geometry:
                                             if self.app.abort_flag:
                                                 # graceful abort requested by the user
-                                                raise FlatCAMApp.GracefulException
+                                                raise grace
 
                                             trans_geo = translate_recursion(geo_el)
                                             obj_fin.solid_geometry.append(trans_geo)
@@ -715,13 +713,13 @@ class Panelize(FlatCAMTool):
                                 #     obj_fin.solid_geometry.append(geo)
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
                                 try:
                                     for geo_el in panel_obj.solid_geometry:
                                         if self.app.abort_flag:
                                             # graceful abort requested by the user
-                                            raise FlatCAMApp.GracefulException
+                                            raise grace
 
                                         trans_geo = translate_recursion(geo_el)
                                         obj_fin.solid_geometry.append(trans_geo)
@@ -732,7 +730,7 @@ class Panelize(FlatCAMTool):
                                 for apid in panel_obj.apertures:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
                                     if 'geometry' in panel_obj.apertures[apid]:
                                         try:
                                             # calculate the number of polygons
@@ -743,7 +741,7 @@ class Panelize(FlatCAMTool):
                                         for el in panel_obj.apertures[apid]['geometry']:
                                             if self.app.abort_flag:
                                                 # graceful abort requested by the user
-                                                raise FlatCAMApp.GracefulException
+                                                raise grace
 
                                             new_el = {}
                                             if 'solid' in el:
@@ -786,7 +784,7 @@ class Panelize(FlatCAMTool):
                     self.app.proc_container.update_view_text('')
 
                 self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns))))
-                if isinstance(panel_obj, FlatCAMExcellon):
+                if panel_obj.kind == 'excellon':
                     self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
                 else:
                     self.app.new_object(panel_type, self.outname, job_init_geometry, plot=True, autoselected=True)

+ 4 - 4
flatcamTools/ToolPunchGerber.py

@@ -142,7 +142,7 @@ class ToolPunchGerber(FlatCAMTool):
               "- Excellon Object-> the Excellon object drills center will serve as reference.\n"
               "- Fixed Diameter -> will try to use the pads center as reference adding fixed diameter holes.\n"
               "- Fixed Annular Ring -> will try to keep a set annular ring.\n"
-              "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.\n")
+              "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.")
         )
         self.method_punch = RadioSet(
             [
@@ -604,8 +604,8 @@ class ToolPunchGerber(FlatCAMTool):
                 if grb_obj.apertures[apid]['type'] == 'C' and self.circular_cb.get_value():
                     if punch_size >= float(grb_obj.apertures[apid]['size']):
                         self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                             _(" Could not generate punched hole Gerber because the punch hole size"
-                                               "is bigger than some of the apertures in the Gerber object."))
+                                             _("Could not generate punched hole Gerber because the punch hole size"
+                                               " is bigger than some of the apertures in the Gerber object."))
                         return 'fail'
                     else:
                         for elem in grb_obj.apertures[apid]['geometry']:
@@ -617,7 +617,7 @@ class ToolPunchGerber(FlatCAMTool):
                             punch_size >= float(grb_obj.apertures[apid]['height']):
                         self.app.inform.emit('[ERROR_NOTCL] %s' %
                                              _("Could not generate punched hole Gerber because the punch hole size"
-                                               "is bigger than some of the apertures in the Gerber object."))
+                                               " is bigger than some of the apertures in the Gerber object."))
                         return 'fail'
                     elif round(float(grb_obj.apertures[apid]['width']), self.decimals) == \
                             round(float(grb_obj.apertures[apid]['height']), self.decimals) and \

+ 184 - 45
flatcamTools/ToolShell.py

@@ -13,8 +13,10 @@ from PyQt5.QtWidgets import QVBoxLayout, QWidget
 from flatcamGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit
 import html
 import sys
+import traceback
 
 import tkinter as tk
+import tclCommands
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -110,7 +112,7 @@ class TermWidget(QWidget):
         elif style == 'err':
             text = '<span style="font-weight: bold; color: red;">%s</span>'\
                    '<span style="font-weight: bold;">%s</span>'\
-                   %(mtype, body)
+                   % (mtype, body)
         elif style == 'warning':
             # text = '<span style="font-weight: bold; color: #f4b642;">%s</span>' % text
             text = '<span style="font-weight: bold; color: #f4b642;">%s</span>' \
@@ -253,15 +255,90 @@ class TermWidget(QWidget):
 
 
 class FCShell(TermWidget):
-    def __init__(self, sysShell, version, *args):
+    def __init__(self, app, version, *args):
         """
+        Initialize the TCL Shell. A dock widget that holds the GUI interface to the FlatCAM command line.
 
-        :param sysShell:    When instantiated the sysShell will be actually the FlatCAMApp.App() class
+        :param app:    When instantiated the sysShell will be actually the FlatCAMApp.App() class
         :param version:     FlatCAM version string
         :param args:        Parameters passed to the TermWidget parent class
         """
-        TermWidget.__init__(self, version, *args, app=sysShell)
-        self._sysShell = sysShell
+        TermWidget.__init__(self, version, *args, app=app)
+        self.app = app
+
+        self.tcl_commands_storage = {}
+
+        if hasattr(self, 'tcl') and self.tcl is not None:
+            # self.tcl = None
+            # new object cannot be used here as it will not remember values created for next passes,
+            # because tcl was executed in old instance of TCL
+            pass
+        else:
+            self.tcl = tk.Tcl()
+            self.setup_shell()
+
+        self._edit.set_model_data(self.app.myKeywords)
+        self.setWindowIcon(self.app.ui.app_icon)
+        self.setWindowTitle("FlatCAM Shell")
+        self.resize(*self.app.defaults["global_shell_shape"])
+        self._append_to_browser('in', "FlatCAM %s - " % version)
+        self.append_output('%s\n\n' % _("Type >help< to get started"))
+
+    def setup_shell(self):
+        """
+        Creates shell functions. Runs once at startup.
+
+        :return: None
+        """
+
+        '''
+            How to implement TCL shell commands:
+
+            All parameters passed to command should be possible to set as None and test it afterwards.
+            This is because we need to see error caused in tcl,
+            if None value as default parameter is not allowed TCL will return empty error.
+            Use:
+                def mycommand(name=None,...):
+
+            Test it like this:
+            if name is None:
+
+                self.raise_tcl_error('Argument name is missing.')
+
+            When error occurred, always use raise_tcl_error, never return "some text" on error,
+            otherwise we will miss it and processing will silently continue.
+            Method raise_tcl_error  pass error into TCL interpreter, then raise python exception,
+            which is caught in exec_command and displayed in TCL shell console with red background.
+            Error in console is displayed  with TCL  trace.
+
+            This behavior works only within main thread,
+            errors with promissed tasks can be catched and detected only with log.
+            TODO: this problem have to be addressed somehow, maybe rewrite promissing to be blocking somehow for
+            TCL shell.
+
+            Kamil's comment: I will rewrite existing TCL commands from time to time to follow this rules.
+
+        '''
+
+        # Import/overwrite tcl commands as objects of TclCommand descendants
+        # This modifies the variable 'self.tcl_commands_storage'.
+        tclCommands.register_all_commands(self.app, self.tcl_commands_storage)
+
+        # Add commands to the tcl interpreter
+        for cmd in self.tcl_commands_storage:
+            self.tcl.createcommand(cmd, self.tcl_commands_storage[cmd]['fcn'])
+
+        # Make the tcl puts function return instead of print to stdout
+        self.tcl.eval('''
+            rename puts original_puts
+            proc puts {args} {
+                if {[llength $args] == 1} {
+                    return "[lindex $args 0]"
+                } else {
+                    eval original_puts $args
+                }
+            }
+            ''')
 
     def is_command_complete(self, text):
         def skipQuotes(txt):
@@ -293,7 +370,7 @@ class FCShell(TermWidget):
         :return: output if there was any
         """
 
-        self._sysShell.report_usage('exec_command')
+        self.app.report_usage('exec_command')
 
         return self.exec_command_test(text, False, no_echo=no_echo)
 
@@ -315,15 +392,15 @@ class FCShell(TermWidget):
             if no_echo is False:
                 self.open_processing()  # Disables input box.
 
-            result = self._sysShell.tcl.eval(str(tcl_command_string))
+            result = self.tcl.eval(str(tcl_command_string))
             if result != 'None' and no_echo is False:
                 self.append_output(result + '\n')
 
         except tk.TclError as e:
 
             # This will display more precise answer if something in TCL shell fails
-            result = self._sysShell.tcl.eval("set errorInfo")
-            self._sysShell.log.error("Exec command Exception: %s" % (result + '\n'))
+            result = self.tcl.eval("set errorInfo")
+            self.app.log.error("Exec command Exception: %s" % (result + '\n'))
             if no_echo is False:
                 self.append_error('ERROR: ' + result + '\n')
             # Show error in console and just return or in test raise exception
@@ -335,39 +412,101 @@ class FCShell(TermWidget):
             pass
         return result
 
-        # """
-        # Code below is unsused. Saved for later.
-        # """
-
-        # parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
-        # parts = [p.replace('\n', '').replace('"', '') for p in parts]
-        # self.log.debug(parts)
-        # try:
-        #     if parts[0] not in commands:
-        #         self.shell.append_error("Unknown command\n")
-        #         return
-        #
-        #     #import inspect
-        #     #inspect.getargspec(someMethod)
-        #     if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \
-        #             (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]):
-        #         self.shell.append_error(
-        #             "Command %s takes %d arguments. %d given.\n" %
-        #             (parts[0], commands[parts[0]]["params"], len(parts)-1)
-        #         )
-        #         return
-        #
-        #     cmdfcn = commands[parts[0]]["fcn"]
-        #     cmdconv = commands[parts[0]]["converters"]
-        #     if len(parts) - 1 > 0:
-        #         retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)])
-        #     else:
-        #         retval = cmdfcn()
-        #     retfcn = commands[parts[0]]["retfcn"]
-        #     if retval and retfcn(retval):
-        #         self.shell.append_output(retfcn(retval) + "\n")
-        #
-        # except Exception as e:
-        #     #self.shell.append_error(''.join(traceback.format_exc()))
-        #     #self.shell.append_error("?\n")
-        #     self.shell.append_error(str(e) + "\n")
+    def raise_tcl_unknown_error(self, unknownException):
+        """
+        Raise exception if is different type than TclErrorException
+        this is here mainly to show unknown errors inside TCL shell console.
+
+        :param unknownException:
+        :return:
+        """
+
+        if not isinstance(unknownException, self.TclErrorException):
+            self.raise_tcl_error("Unknown error: %s" % str(unknownException))
+        else:
+            raise unknownException
+
+    def display_tcl_error(self, error, error_info=None):
+        """
+        Escape bracket [ with '\' otherwise there is error
+        "ERROR: missing close-bracket" instead of real error
+
+        :param error: it may be text  or exception
+        :param error_info: Some informations about the error
+        :return: None
+        """
+
+        if isinstance(error, Exception):
+            exc_type, exc_value, exc_traceback = error_info
+            if not isinstance(error, self.TclErrorException):
+                show_trace = 1
+            else:
+                show_trace = int(self.app.defaults['global_verbose_error_level'])
+
+            if show_trace > 0:
+                trc = traceback.format_list(traceback.extract_tb(exc_traceback))
+                trc_formated = []
+                for a in reversed(trc):
+                    trc_formated.append(a.replace("    ", " > ").replace("\n", ""))
+                text = "%s\nPython traceback: %s\n%s" % (exc_value, exc_type, "\n".join(trc_formated))
+            else:
+                text = "%s" % error
+        else:
+            text = error
+
+        text = text.replace('[', '\\[').replace('"', '\\"')
+        self.tcl.eval('return -code error "%s"' % text)
+
+    def raise_tcl_error(self, text):
+        """
+        This method  pass exception from python into TCL as error, so we get stacktrace and reason
+
+        :param text: text of error
+        :return: raise exception
+        """
+
+        self.display_tcl_error(text)
+        raise self.TclErrorException(text)
+
+    class TclErrorException(Exception):
+        """
+        this exception is defined here, to be able catch it if we successfully handle all errors from shell command
+        """
+        pass
+
+    # """
+    # Code below is unsused. Saved for later.
+    # """
+
+    # parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
+    # parts = [p.replace('\n', '').replace('"', '') for p in parts]
+    # self.log.debug(parts)
+    # try:
+    #     if parts[0] not in commands:
+    #         self.shell.append_error("Unknown command\n")
+    #         return
+    #
+    #     #import inspect
+    #     #inspect.getargspec(someMethod)
+    #     if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \
+    #             (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]):
+    #         self.shell.append_error(
+    #             "Command %s takes %d arguments. %d given.\n" %
+    #             (parts[0], commands[parts[0]]["params"], len(parts)-1)
+    #         )
+    #         return
+    #
+    #     cmdfcn = commands[parts[0]]["fcn"]
+    #     cmdconv = commands[parts[0]]["converters"]
+    #     if len(parts) - 1 > 0:
+    #         retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)])
+    #     else:
+    #         retval = cmdfcn()
+    #     retfcn = commands[parts[0]]["retfcn"]
+    #     if retval and retfcn(retval):
+    #         self.shell.append_output(retfcn(retval) + "\n")
+    #
+    # except Exception as e:
+    #     #self.shell.append_error(''.join(traceback.format_exc()))
+    #     #self.shell.append_error("?\n")
+    #     self.shell.append_error(str(e) + "\n")

+ 7 - 6
flatcamTools/ToolSolderPaste.py

@@ -11,7 +11,6 @@ from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCTable, \
     FCInputDialog, FCDoubleSpinner, FCSpinner, FCFileSaveDialog
 from FlatCAMApp import log
 from camlib import distance
-from FlatCAMObj import FlatCAMCNCjob
 from flatcamEditors.FlatCAMTextEditor import TextEditor
 
 from PyQt5 import QtGui, QtCore, QtWidgets
@@ -506,7 +505,8 @@ class SolderPaste(FlatCAMTool):
         self.flat_geometry = []
 
         # action to be added in the combobox context menu
-        self.combo_context_del_action = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/trash16.png'), _("Delete Object"))
+        self.combo_context_del_action = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/trash16.png'),
+                                                          _("Delete Object"))
 
         # ## Signals
         self.combo_context_del_action.triggered.connect(self.on_delete_object)
@@ -966,6 +966,7 @@ class SolderPaste(FlatCAMTool):
                 self.build_ui()
                 return
             else:
+                old_tool_dia = ''
                 # identify the old tool_dia and restore the text in tool table
                 for k, v in self.tooltable_tools.items():
                     if k == tooluid:
@@ -1332,9 +1333,9 @@ class SolderPaste(FlatCAMTool):
 
         # Object initialization function for app.new_object()
         # RUNNING ON SEPARATE THREAD!
-        def job_init(job_obj, app_obj):
-            assert isinstance(job_obj, FlatCAMCNCjob), \
-                "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
+        def job_init(job_obj):
+            assert job_obj.kind == 'cncjob', \
+                "Initializer expected a CNCJobObject, got %s" % type(job_obj)
 
             # this turn on the FlatCAMCNCJob plot for multiple tools
             job_obj.multitool = True
@@ -1364,7 +1365,7 @@ class SolderPaste(FlatCAMTool):
                 res = job_obj.generate_gcode_from_solderpaste_geo(**tooluid_value)
 
                 if res == 'fail':
-                    log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
+                    log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed")
                     return 'fail'
                 else:
                     tool_cnc_dict['gcode'] = res

+ 14 - 19
flatcamTools/ToolTransform.py

@@ -8,7 +8,6 @@
 from PyQt5 import QtWidgets
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2
-from FlatCAMObj import FlatCAMCNCjob
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -681,7 +680,7 @@ class ToolTransform(FlatCAMTool):
                 try:
                     # first get a bounding box to fit all
                     for obj in obj_list:
-                        if isinstance(obj, FlatCAMCNCjob):
+                        if obj.kind == 'cncjob':
                             pass
                         else:
                             xmin, ymin, xmax, ymax = obj.bounds()
@@ -699,7 +698,7 @@ class ToolTransform(FlatCAMTool):
                     px = 0.5 * (xminimal + xmaximal)
                     py = 0.5 * (yminimal + ymaximal)
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be rotated."))
                         else:
                             sel_obj.rotate(-num, point=(px, py))
@@ -735,7 +734,7 @@ class ToolTransform(FlatCAMTool):
                     else:
                         # first get a bounding box to fit all
                         for obj in obj_list:
-                            if isinstance(obj, FlatCAMCNCjob):
+                            if obj.kind == 'cncjob':
                                 pass
                             else:
                                 xmin, ymin, xmax, ymax = obj.bounds()
@@ -755,7 +754,7 @@ class ToolTransform(FlatCAMTool):
 
                     # execute mirroring
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be mirrored/flipped."))
                         else:
                             if axis == 'X':
@@ -803,7 +802,7 @@ class ToolTransform(FlatCAMTool):
                 try:
                     # first get a bounding box to fit all
                     for obj in obj_list:
-                        if isinstance(obj, FlatCAMCNCjob):
+                        if obj.kind == 'cncjob':
                             pass
                         else:
                             xmin, ymin, xmax, ymax = obj.bounds()
@@ -815,7 +814,7 @@ class ToolTransform(FlatCAMTool):
                     yminimal = min(yminlist)
 
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be skewed."))
                         else:
                             if axis == 'X':
@@ -842,15 +841,14 @@ class ToolTransform(FlatCAMTool):
         ymaxlist = []
 
         if not obj_list:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("No object selected. Please Select an object to scale!"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to scale!"))
             return
         else:
             with self.app.proc_container.new(_("Applying Scale")):
                 try:
                     # first get a bounding box to fit all
                     for obj in obj_list:
-                        if isinstance(obj, FlatCAMCNCjob):
+                        if obj.kind == 'cncjob':
                             pass
                         else:
                             xmin, ymin, xmax, ymax = obj.bounds()
@@ -873,7 +871,7 @@ class ToolTransform(FlatCAMTool):
                         py = 0
 
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be scaled."))
                         else:
                             sel_obj.scale(xfactor, yfactor, point=(px, py))
@@ -883,8 +881,7 @@ class ToolTransform(FlatCAMTool):
                             self.app.object_changed.emit(sel_obj)
                         sel_obj.plot()
 
-                    self.app.inform.emit('[success] %s %s %s...' %
-                                         (_('Scale on the'), str(axis), _('axis done')))
+                    self.app.inform.emit('[success] %s %s %s...' % (_('Scale on the'), str(axis), _('axis done')))
                 except Exception as e:
                     self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
                                          (_("Due of"), str(e), _("action was not executed.")))
@@ -894,14 +891,13 @@ class ToolTransform(FlatCAMTool):
         obj_list = self.app.collection.get_selected()
 
         if not obj_list:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("No object selected. Please Select an object to offset!"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to offset!"))
             return
         else:
             with self.app.proc_container.new(_("Applying Offset")):
                 try:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be offset."))
                         else:
                             if axis == 'X':
@@ -915,8 +911,7 @@ class ToolTransform(FlatCAMTool):
                             self.app.object_changed.emit(sel_obj)
                         sel_obj.plot()
 
-                    self.app.inform.emit('[success] %s %s %s...' %
-                                         (_('Offset on the'), str(axis), _('axis done')))
+                    self.app.inform.emit('[success] %s %s %s...' % (_('Offset on the'), str(axis), _('axis done')))
                 except Exception as e:
                     self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
                                          (_("Due of"), str(e),  _("action was not executed.")))
@@ -932,7 +927,7 @@ class ToolTransform(FlatCAMTool):
             with self.app.proc_container.new(_("Applying Buffer")):
                 try:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be buffered."))
                         elif sel_obj.kind.lower() == 'gerber':
                             sel_obj.buffer(value, join, factor)

BIN
locale/de/LC_MESSAGES/strings.mo


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 327 - 267
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 230 - 233
locale/en/LC_MESSAGES/strings.po


BIN
locale/es/LC_MESSAGES/strings.mo


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 230 - 233
locale/es/LC_MESSAGES/strings.po


BIN
locale/fr/LC_MESSAGES/strings.mo


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 229 - 232
locale/fr/LC_MESSAGES/strings.po


BIN
locale/hu/LC_MESSAGES/strings.mo


+ 21717 - 0
locale/hu/LC_MESSAGES/strings.po

@@ -0,0 +1,21717 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: 2020-04-27 12:30+0300\n"
+"PO-Revision-Date: 2020-04-27 12:31+0300\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: en\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"X-Generator: Poedit 2.2.4\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Basepath: ../../..\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-SearchPathExcluded-0: build\n"
+"X-Poedit-SearchPathExcluded-1: doc\n"
+"X-Poedit-SearchPathExcluded-2: tests\n"
+
+#: FlatCAMApp.py:794 FlatCAMApp.py:826 FlatCAMDB.py:1481 FlatCAMDB.py:1596
+#: flatcamEditors/FlatCAMGeoEditor.py:499
+#: flatcamEditors/FlatCAMGeoEditor.py:569
+#: flatcamEditors/FlatCAMGeoEditor.py:5152 flatcamGUI/PreferencesUI.py:5509
+#: flatcamGUI/PreferencesUI.py:6056 flatcamTools/ToolNCC.py:431
+#: flatcamTools/ToolNCC.py:2396 flatcamTools/ToolNCC.py:2424
+#: flatcamTools/ToolNCC.py:2694 flatcamTools/ToolNCC.py:2726
+#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:1843
+#: tclCommands/TclCommandCopperClear.py:128
+#: tclCommands/TclCommandCopperClear.py:136 tclCommands/TclCommandPaint.py:127
+msgid "Seed"
+msgstr "Seed"
+
+#: FlatCAMApp.py:800 flatcamGUI/PreferencesUI.py:5588
+#: flatcamGUI/PreferencesUI.py:7695 flatcamTools/ToolCopperThieving.py:125
+#: flatcamTools/ToolNCC.py:535 flatcamTools/ToolNCC.py:1301
+#: flatcamTools/ToolNCC.py:1629 flatcamTools/ToolNCC.py:1914
+#: flatcamTools/ToolNCC.py:1978 flatcamTools/ToolNCC.py:2962
+#: flatcamTools/ToolNCC.py:2971 tclCommands/TclCommandCopperClear.py:190
+msgid "Itself"
+msgstr "Itself"
+
+#: FlatCAMApp.py:827 flatcamGUI/PreferencesUI.py:6119
+#: flatcamTools/ToolPaint.py:486 flatcamTools/ToolPaint.py:1422
+#: tclCommands/TclCommandPaint.py:162
+msgid "All Polygons"
+msgstr "All Polygons"
+
+#: FlatCAMApp.py:1134
+msgid "FlatCAM is initializing ..."
+msgstr "FlatCAM is initializing ..."
+
+#: FlatCAMApp.py:1828
+msgid "Could not find the Language files. The App strings are missing."
+msgstr "Could not find the Language files. The App strings are missing."
+
+#: FlatCAMApp.py:1922
+msgid ""
+"FlatCAM is initializing ...\n"
+"Canvas initialization started."
+msgstr ""
+"FlatCAM is initializing ...\n"
+"Canvas initialization started."
+
+#: FlatCAMApp.py:1942
+msgid ""
+"FlatCAM is initializing ...\n"
+"Canvas initialization started.\n"
+"Canvas initialization finished in"
+msgstr ""
+"FlatCAM is initializing ...\n"
+"Canvas initialization started.\n"
+"Canvas initialization finished in"
+
+#: FlatCAMApp.py:2815 FlatCAMApp.py:9396
+msgid "New Project - Not saved"
+msgstr "New Project - Not saved"
+
+#: FlatCAMApp.py:2911
+msgid ""
+"Found old default preferences files. Please reboot the application to update."
+msgstr ""
+"Found old default preferences files. Please reboot the application to update."
+
+#: FlatCAMApp.py:2962 FlatCAMApp.py:3882 FlatCAMApp.py:3931 FlatCAMApp.py:3986
+#: FlatCAMApp.py:4061 FlatCAMApp.py:6110 FlatCAMApp.py:9480 FlatCAMApp.py:9517
+#: FlatCAMApp.py:9559 FlatCAMApp.py:9588 FlatCAMApp.py:9629 FlatCAMApp.py:9654
+#: FlatCAMApp.py:9706 FlatCAMApp.py:9741 FlatCAMApp.py:9786 FlatCAMApp.py:9827
+#: FlatCAMApp.py:9868 FlatCAMApp.py:9909 FlatCAMApp.py:9950 FlatCAMApp.py:9994
+#: FlatCAMApp.py:10050 FlatCAMApp.py:10082 FlatCAMApp.py:10114
+#: FlatCAMApp.py:10351 FlatCAMApp.py:10394 FlatCAMApp.py:10471
+#: FlatCAMApp.py:10526 FlatCAMBookmark.py:291 FlatCAMBookmark.py:333
+#: FlatCAMDB.py:663 FlatCAMDB.py:709 FlatCAMDB.py:2093 FlatCAMDB.py:2139
+#: ObjectCollection.py:127 flatcamEditors/FlatCAMExcEditor.py:1023
+#: flatcamEditors/FlatCAMExcEditor.py:1091
+#: flatcamEditors/FlatCAMTextEditor.py:223 flatcamGUI/FlatCAMGUI.py:3389
+#: flatcamGUI/FlatCAMGUI.py:3601 flatcamGUI/FlatCAMGUI.py:3812
+#: flatcamTools/ToolFilm.py:754 flatcamTools/ToolFilm.py:900
+#: flatcamTools/ToolImage.py:247 flatcamTools/ToolMove.py:269
+#: flatcamTools/ToolPcbWizard.py:301 flatcamTools/ToolPcbWizard.py:324
+#: flatcamTools/ToolQRCode.py:791 flatcamTools/ToolQRCode.py:838
+msgid "Cancelled."
+msgstr "Cancelled."
+
+#: FlatCAMApp.py:2978
+msgid "Open Config file failed."
+msgstr "Open Config file failed."
+
+#: FlatCAMApp.py:2993
+msgid "Open Script file failed."
+msgstr "Open Script file failed."
+
+#: FlatCAMApp.py:3019
+msgid "Open Excellon file failed."
+msgstr "Open Excellon file failed."
+
+#: FlatCAMApp.py:3032
+msgid "Open GCode file failed."
+msgstr "Open GCode file failed."
+
+#: FlatCAMApp.py:3045
+msgid "Open Gerber file failed."
+msgstr "Open Gerber file failed."
+
+#: FlatCAMApp.py:3422
+msgid "Select a Geometry, Gerber or Excellon Object to edit."
+msgstr "Select a Geometry, Gerber or Excellon Object to edit."
+
+#: FlatCAMApp.py:3437
+msgid ""
+"Simultaneous editing of tools geometry in a MultiGeo Geometry is not "
+"possible.\n"
+"Edit only one geometry at a time."
+msgstr ""
+"Simultaneous editing of tools geometry in a MultiGeo Geometry is not "
+"possible.\n"
+"Edit only one geometry at a time."
+
+#: FlatCAMApp.py:3495
+msgid "Editor is activated ..."
+msgstr "Editor is activated ..."
+
+#: FlatCAMApp.py:3516
+msgid "Do you want to save the edited object?"
+msgstr "Do you want to save the edited object?"
+
+#: FlatCAMApp.py:3517 flatcamGUI/FlatCAMGUI.py:2273
+msgid "Close Editor"
+msgstr "Close Editor"
+
+#: FlatCAMApp.py:3520 FlatCAMApp.py:5162 FlatCAMApp.py:8024 FlatCAMApp.py:8050
+#: FlatCAMApp.py:9300 FlatCAMTranslation.py:108 FlatCAMTranslation.py:199
+#: flatcamGUI/FlatCAMGUI.py:2479
+msgid "Yes"
+msgstr "Yes"
+
+#: FlatCAMApp.py:3521 FlatCAMApp.py:5163 FlatCAMApp.py:8025 FlatCAMApp.py:8051
+#: FlatCAMApp.py:9301 FlatCAMTranslation.py:109 FlatCAMTranslation.py:200
+#: flatcamGUI/FlatCAMGUI.py:2480 flatcamGUI/PreferencesUI.py:5443
+#: flatcamGUI/PreferencesUI.py:5989 flatcamTools/ToolNCC.py:182
+#: flatcamTools/ToolPaint.py:166
+msgid "No"
+msgstr "No"
+
+#: FlatCAMApp.py:3522 FlatCAMApp.py:5164 FlatCAMApp.py:6048 FlatCAMApp.py:7000
+#: FlatCAMApp.py:9302 FlatCAMDB.py:128 FlatCAMDB.py:1683
+#: flatcamGUI/FlatCAMGUI.py:1332
+msgid "Cancel"
+msgstr "Cancel"
+
+#: FlatCAMApp.py:3554
+msgid "Object empty after edit."
+msgstr "Object empty after edit."
+
+#: FlatCAMApp.py:3558 FlatCAMApp.py:3579 FlatCAMApp.py:3601
+msgid "Editor exited. Editor content saved."
+msgstr "Editor exited. Editor content saved."
+
+#: FlatCAMApp.py:3605 FlatCAMApp.py:3628 FlatCAMApp.py:3646
+msgid "Select a Gerber, Geometry or Excellon Object to update."
+msgstr "Select a Gerber, Geometry or Excellon Object to update."
+
+#: FlatCAMApp.py:3608
+msgid "is updated, returning to App..."
+msgstr "is updated, returning to App..."
+
+#: FlatCAMApp.py:3615
+msgid "Editor exited. Editor content was not saved."
+msgstr "Editor exited. Editor content was not saved."
+
+#: FlatCAMApp.py:3808 FlatCAMApp.py:3939 FlatCAMApp.py:5011
+msgid "Could not load defaults file."
+msgstr "Could not load defaults file."
+
+#: FlatCAMApp.py:3820 FlatCAMApp.py:3947 FlatCAMApp.py:5020
+msgid "Failed to parse defaults file."
+msgstr "Failed to parse defaults file."
+
+#: FlatCAMApp.py:3890 FlatCAMApp.py:5112
+msgid "Could not load factory defaults file."
+msgstr "Could not load factory defaults file."
+
+#: FlatCAMApp.py:3898 FlatCAMApp.py:5122
+msgid "Failed to parse factory defaults file."
+msgstr "Failed to parse factory defaults file."
+
+#: FlatCAMApp.py:3906
+msgid "Preferences default values are restored."
+msgstr "Preferences default values are restored."
+
+#: FlatCAMApp.py:3921 FlatCAMApp.py:3925
+msgid "Import FlatCAM Preferences"
+msgstr "Import FlatCAM Preferences"
+
+#: FlatCAMApp.py:3955
+msgid "Imported Defaults from"
+msgstr "Imported Defaults from"
+
+#: FlatCAMApp.py:3975 FlatCAMApp.py:3980
+msgid "Export FlatCAM Preferences"
+msgstr "Export FlatCAM Preferences"
+
+#: FlatCAMApp.py:3995 FlatCAMApp.py:4069 FlatCAMApp.py:10770
+#: FlatCAMApp.py:10818 FlatCAMApp.py:10943 FlatCAMApp.py:11079
+#: FlatCAMBookmark.py:299 FlatCAMDB.py:671 FlatCAMDB.py:2101
+#: flatcamEditors/FlatCAMTextEditor.py:276 flatcamObjects/FlatCAMCNCJob.py:958
+#: flatcamTools/ToolFilm.py:1031 flatcamTools/ToolFilm.py:1212
+#: flatcamTools/ToolSolderPaste.py:1534
+msgid ""
+"Permission denied, saving not possible.\n"
+"Most likely another app is holding the file open and not accessible."
+msgstr ""
+"Permission denied, saving not possible.\n"
+"Most likely another app is holding the file open and not accessible."
+
+#: FlatCAMApp.py:4007
+msgid "Could not load preferences file."
+msgstr "Could not load preferences file."
+
+#: FlatCAMApp.py:4026 FlatCAMApp.py:4093 FlatCAMApp.py:5039
+msgid "Failed to write defaults to file."
+msgstr "Failed to write defaults to file."
+
+#: FlatCAMApp.py:4031
+msgid "Exported preferences to"
+msgstr "Exported preferences to"
+
+#: FlatCAMApp.py:4051 FlatCAMApp.py:4056
+msgid "Save to file"
+msgstr "Save to file"
+
+#: FlatCAMApp.py:4080
+msgid "Could not load the file."
+msgstr "Could not load the file."
+
+#: FlatCAMApp.py:4096
+msgid "Exported file to"
+msgstr "Exported file to"
+
+#: FlatCAMApp.py:4179
+msgid "Failed to open recent files file for writing."
+msgstr "Failed to open recent files file for writing."
+
+#: FlatCAMApp.py:4190
+msgid "Failed to open recent projects file for writing."
+msgstr "Failed to open recent projects file for writing."
+
+#: FlatCAMApp.py:4275 FlatCAMApp.py:11275 FlatCAMApp.py:11334
+#: FlatCAMApp.py:11462 FlatCAMApp.py:12193
+#: flatcamEditors/FlatCAMGrbEditor.py:4231
+#: flatcamObjects/FlatCAMGeometry.py:1671 flatcamParsers/ParseExcellon.py:897
+#: flatcamTools/ToolPcbWizard.py:433
+msgid "An internal error has occurred. See shell.\n"
+msgstr "An internal error has occurred. See shell.\n"
+
+#: FlatCAMApp.py:4276
+#, python-brace-format
+msgid ""
+"Object ({kind}) failed because: {error} \n"
+"\n"
+msgstr ""
+"Object ({kind}) failed because: {error} \n"
+"\n"
+
+#: FlatCAMApp.py:4291
+msgid "Converting units to "
+msgstr "Converting units to "
+
+#: FlatCAMApp.py:4404
+msgid "CREATE A NEW FLATCAM TCL SCRIPT"
+msgstr "CREATE A NEW FLATCAM TCL SCRIPT"
+
+#: FlatCAMApp.py:4405
+msgid "TCL Tutorial is here"
+msgstr "TCL Tutorial is here"
+
+#: FlatCAMApp.py:4407
+msgid "FlatCAM commands list"
+msgstr "FlatCAM commands list"
+
+#: FlatCAMApp.py:4458 FlatCAMApp.py:4464 FlatCAMApp.py:4470 FlatCAMApp.py:4476
+#: FlatCAMApp.py:4482 FlatCAMApp.py:4488
+msgid "created/selected"
+msgstr "created/selected"
+
+#: FlatCAMApp.py:4503 FlatCAMApp.py:7086 flatcamObjects/FlatCAMObj.py:248
+#: flatcamObjects/FlatCAMObj.py:279 flatcamObjects/FlatCAMObj.py:295
+#: flatcamObjects/FlatCAMObj.py:375 flatcamTools/ToolCopperThieving.py:1481
+#: flatcamTools/ToolFiducials.py:809 flatcamTools/ToolMove.py:229
+#: flatcamTools/ToolQRCode.py:728
+msgid "Plotting"
+msgstr "Plotting"
+
+#: FlatCAMApp.py:4566 flatcamGUI/FlatCAMGUI.py:530
+msgid "About FlatCAM"
+msgstr "About FlatCAM"
+
+#: FlatCAMApp.py:4592
+msgid "2D Computer-Aided Printed Circuit Board Manufacturing"
+msgstr "2D Computer-Aided Printed Circuit Board Manufacturing"
+
+#: FlatCAMApp.py:4593
+msgid "Development"
+msgstr "Development"
+
+#: FlatCAMApp.py:4594
+msgid "DOWNLOAD"
+msgstr "DOWNLOAD"
+
+#: FlatCAMApp.py:4595
+msgid "Issue tracker"
+msgstr "Issue tracker"
+
+#: FlatCAMApp.py:4599 FlatCAMApp.py:4941 flatcamGUI/GUIElements.py:2583
+msgid "Close"
+msgstr "Close"
+
+#: FlatCAMApp.py:4614
+msgid "Licensed under the MIT license"
+msgstr "Licensed under the MIT license"
+
+#: FlatCAMApp.py:4623
+msgid ""
+"Permission is hereby granted, free of charge, to any person obtaining a "
+"copy\n"
+"of this software and associated documentation files (the \"Software\"), to "
+"deal\n"
+"in the Software without restriction, including without limitation the "
+"rights\n"
+"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
+"copies of the Software, and to permit persons to whom the Software is\n"
+"furnished to do so, subject to the following conditions:\n"
+"\n"
+"The above copyright notice and this permission notice shall be included in\n"
+"all copies or substantial portions of the Software.\n"
+"\n"
+"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS "
+"OR\n"
+"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
+"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
+"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING "
+"FROM,\n"
+"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
+"THE SOFTWARE."
+msgstr ""
+"Permission is hereby granted, free of charge, to any person obtaining a "
+"copy\n"
+"of this software and associated documentation files (the \"Software\"), to "
+"deal\n"
+"in the Software without restriction, including without limitation the "
+"rights\n"
+"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
+"copies of the Software, and to permit persons to whom the Software is\n"
+"furnished to do so, subject to the following conditions:\n"
+"\n"
+"The above copyright notice and this permission notice shall be included in\n"
+"all copies or substantial portions of the Software.\n"
+"\n"
+"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS "
+"OR\n"
+"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
+"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
+"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING "
+"FROM,\n"
+"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
+"THE SOFTWARE."
+
+#: FlatCAMApp.py:4645
+msgid ""
+"Some of the icons used are from the following sources:<br><div>Icons by <a "
+"href=\"https://www.flaticon.com/authors/freepik\" title=\"Freepik\">Freepik</"
+"a> from <a href=\"https://www.flaticon.com/\"             title=\"Flaticon"
+"\">www.flaticon.com</a></div><div>Icons by <a target=\"_blank\" href="
+"\"https://icons8.com\">Icons8</a></div>Icons by <a href=\"http://www."
+"onlinewebfonts.com\">oNline Web Fonts</a>"
+msgstr ""
+"Some of the icons used are from the following sources:<br><div>Icons by <a "
+"href=\"https://www.flaticon.com/authors/freepik\" title=\"Freepik\">Freepik</"
+"a> from <a href=\"https://www.flaticon.com/\"             title=\"Flaticon"
+"\">www.flaticon.com</a></div><div>Icons by <a target=\"_blank\" href="
+"\"https://icons8.com\">Icons8</a></div>Icons by <a href=\"http://www."
+"onlinewebfonts.com\">oNline Web Fonts</a>"
+
+#: FlatCAMApp.py:4678
+msgid "Splash"
+msgstr "Splash"
+
+#: FlatCAMApp.py:4684
+msgid "Programmers"
+msgstr "Programmers"
+
+#: FlatCAMApp.py:4690
+msgid "Translators"
+msgstr "Translators"
+
+#: FlatCAMApp.py:4696
+msgid "License"
+msgstr "License"
+
+#: FlatCAMApp.py:4702
+msgid "Attributions"
+msgstr "Attributions"
+
+#: FlatCAMApp.py:4725
+msgid "Programmer"
+msgstr "Programmer"
+
+#: FlatCAMApp.py:4726
+msgid "Status"
+msgstr "Status"
+
+#: FlatCAMApp.py:4727 FlatCAMApp.py:4805
+msgid "E-mail"
+msgstr "E-mail"
+
+#: FlatCAMApp.py:4735
+msgid "BETA Maintainer >= 2019"
+msgstr "BETA Maintainer >= 2019"
+
+#: FlatCAMApp.py:4802
+msgid "Language"
+msgstr "Language"
+
+#: FlatCAMApp.py:4803
+msgid "Translator"
+msgstr "Translator"
+
+#: FlatCAMApp.py:4804
+msgid "Corrections"
+msgstr "Corrections"
+
+#: FlatCAMApp.py:4913 FlatCAMApp.py:4921 FlatCAMApp.py:8069
+#: flatcamGUI/FlatCAMGUI.py:512
+msgid "Bookmarks Manager"
+msgstr "Bookmarks Manager"
+
+#: FlatCAMApp.py:4932
+msgid ""
+"This entry will resolve to another website if:\n"
+"\n"
+"1. FlatCAM.org website is down\n"
+"2. Someone forked FlatCAM project and wants to point\n"
+"to his own website\n"
+"\n"
+"If you can't get any informations about FlatCAM beta\n"
+"use the YouTube channel link from the Help menu."
+msgstr ""
+"This entry will resolve to another website if:\n"
+"\n"
+"1. FlatCAM.org website is down\n"
+"2. Someone forked FlatCAM project and wants to point\n"
+"to his own website\n"
+"\n"
+"If you can't get any informations about FlatCAM beta\n"
+"use the YouTube channel link from the Help menu."
+
+#: FlatCAMApp.py:4939
+msgid "Alternative website"
+msgstr "Alternative website"
+
+#: FlatCAMApp.py:5043 FlatCAMApp.py:8033
+msgid "Preferences saved."
+msgstr "Preferences saved."
+
+#: FlatCAMApp.py:5138
+msgid "Failed to write factory defaults to file."
+msgstr "Failed to write factory defaults to file."
+
+#: FlatCAMApp.py:5142
+msgid "Factory defaults saved."
+msgstr "Factory defaults saved."
+
+#: FlatCAMApp.py:5152 flatcamGUI/FlatCAMGUI.py:4178
+msgid "Application is saving the project. Please wait ..."
+msgstr "Application is saving the project. Please wait ..."
+
+#: FlatCAMApp.py:5157 FlatCAMTranslation.py:194
+msgid ""
+"There are files/objects modified in FlatCAM. \n"
+"Do you want to Save the project?"
+msgstr ""
+"There are files/objects modified in FlatCAM. \n"
+"Do you want to Save the project?"
+
+#: FlatCAMApp.py:5160 FlatCAMApp.py:9298 FlatCAMTranslation.py:197
+msgid "Save changes"
+msgstr "Save changes"
+
+#: FlatCAMApp.py:5416
+msgid "Selected Excellon file extensions registered with FlatCAM."
+msgstr "Selected Excellon file extensions registered with FlatCAM."
+
+#: FlatCAMApp.py:5438
+msgid "Selected GCode file extensions registered with FlatCAM."
+msgstr "Selected GCode file extensions registered with FlatCAM."
+
+#: FlatCAMApp.py:5460
+msgid "Selected Gerber file extensions registered with FlatCAM."
+msgstr "Selected Gerber file extensions registered with FlatCAM."
+
+#: FlatCAMApp.py:5648 FlatCAMApp.py:5707 FlatCAMApp.py:5735
+msgid "At least two objects are required for join. Objects currently selected"
+msgstr "At least two objects are required for join. Objects currently selected"
+
+#: FlatCAMApp.py:5657
+msgid ""
+"Failed join. The Geometry objects are of different types.\n"
+"At least one is MultiGeo type and the other is SingleGeo type. A possibility "
+"is to convert from one to another and retry joining \n"
+"but in the case of converting from MultiGeo to SingleGeo, informations may "
+"be lost and the result may not be what was expected. \n"
+"Check the generated GCODE."
+msgstr ""
+"Failed join. The Geometry objects are of different types.\n"
+"At least one is MultiGeo type and the other is SingleGeo type. A possibility "
+"is to convert from one to another and retry joining \n"
+"but in the case of converting from MultiGeo to SingleGeo, informations may "
+"be lost and the result may not be what was expected. \n"
+"Check the generated GCODE."
+
+#: FlatCAMApp.py:5669 FlatCAMApp.py:5679
+msgid "Geometry merging finished"
+msgstr "Geometry merging finished"
+
+#: FlatCAMApp.py:5702
+msgid "Failed. Excellon joining works only on Excellon objects."
+msgstr "Failed. Excellon joining works only on Excellon objects."
+
+#: FlatCAMApp.py:5712
+msgid "Excellon merging finished"
+msgstr "Excellon merging finished"
+
+#: FlatCAMApp.py:5730
+msgid "Failed. Gerber joining works only on Gerber objects."
+msgstr "Failed. Gerber joining works only on Gerber objects."
+
+#: FlatCAMApp.py:5740
+msgid "Gerber merging finished"
+msgstr "Gerber merging finished"
+
+#: FlatCAMApp.py:5760 FlatCAMApp.py:5795
+msgid "Failed. Select a Geometry Object and try again."
+msgstr "Failed. Select a Geometry Object and try again."
+
+#: FlatCAMApp.py:5764 FlatCAMApp.py:5800
+msgid "Expected a GeometryObject, got"
+msgstr "Expected a GeometryObject, got"
+
+#: FlatCAMApp.py:5777
+msgid "A Geometry object was converted to MultiGeo type."
+msgstr "A Geometry object was converted to MultiGeo type."
+
+#: FlatCAMApp.py:5815
+msgid "A Geometry object was converted to SingleGeo type."
+msgstr "A Geometry object was converted to SingleGeo type."
+
+#: FlatCAMApp.py:6042
+msgid "Toggle Units"
+msgstr "Toggle Units"
+
+#: FlatCAMApp.py:6044
+msgid ""
+"Changing the units of the project\n"
+"will scale all objects.\n"
+"\n"
+"Do you want to continue?"
+msgstr ""
+"Changing the units of the project\n"
+"will scale all objects.\n"
+"\n"
+"Do you want to continue?"
+
+#: FlatCAMApp.py:6047 FlatCAMApp.py:6922 FlatCAMApp.py:6999 FlatCAMApp.py:9673
+#: FlatCAMApp.py:9687 FlatCAMApp.py:10020 FlatCAMApp.py:10030
+msgid "Ok"
+msgstr "Ok"
+
+#: FlatCAMApp.py:6096
+msgid "Converted units to"
+msgstr "Converted units to"
+
+#: FlatCAMApp.py:6737
+msgid "Detachable Tabs"
+msgstr "Detachable Tabs"
+
+#: FlatCAMApp.py:6811 FlatCAMApp.py:6855 FlatCAMApp.py:6883 FlatCAMApp.py:7816
+#: FlatCAMApp.py:7884 FlatCAMApp.py:7988
+msgid "Preferences"
+msgstr "Preferences"
+
+#: FlatCAMApp.py:6817
+msgid "Preferences applied."
+msgstr "Preferences applied."
+
+#: FlatCAMApp.py:6888
+msgid "Preferences closed without saving."
+msgstr "Preferences closed without saving."
+
+#: FlatCAMApp.py:6911 flatcamTools/ToolNCC.py:932 flatcamTools/ToolNCC.py:1426
+#: flatcamTools/ToolPaint.py:858 flatcamTools/ToolSolderPaste.py:568
+#: flatcamTools/ToolSolderPaste.py:893
+msgid "Please enter a tool diameter with non-zero value, in Float format."
+msgstr "Please enter a tool diameter with non-zero value, in Float format."
+
+#: FlatCAMApp.py:6915 flatcamTools/ToolNCC.py:936 flatcamTools/ToolPaint.py:862
+#: flatcamTools/ToolSolderPaste.py:572
+msgid "Adding Tool cancelled"
+msgstr "Adding Tool cancelled"
+
+#: FlatCAMApp.py:6918
+msgid ""
+"Adding Tool works only when Advanced is checked.\n"
+"Go to Preferences -> General - Show Advanced Options."
+msgstr ""
+"Adding Tool works only when Advanced is checked.\n"
+"Go to Preferences -> General - Show Advanced Options."
+
+#: FlatCAMApp.py:6994
+msgid "Delete objects"
+msgstr "Delete objects"
+
+#: FlatCAMApp.py:6997
+msgid ""
+"Are you sure you want to permanently delete\n"
+"the selected objects?"
+msgstr ""
+"Are you sure you want to permanently delete\n"
+"the selected objects?"
+
+#: FlatCAMApp.py:7035
+msgid "Object(s) deleted"
+msgstr "Object(s) deleted"
+
+#: FlatCAMApp.py:7039 FlatCAMApp.py:7194 flatcamTools/ToolDblSided.py:818
+msgid "Failed. No object(s) selected..."
+msgstr "Failed. No object(s) selected..."
+
+#: FlatCAMApp.py:7041
+msgid "Save the work in Editor and try again ..."
+msgstr "Save the work in Editor and try again ..."
+
+#: FlatCAMApp.py:7070
+msgid "Object deleted"
+msgstr "Object deleted"
+
+#: FlatCAMApp.py:7097
+msgid "Click to set the origin ..."
+msgstr "Click to set the origin ..."
+
+#: FlatCAMApp.py:7119
+msgid "Setting Origin..."
+msgstr "Setting Origin..."
+
+#: FlatCAMApp.py:7132 FlatCAMApp.py:7234
+msgid "Origin set"
+msgstr "Origin set"
+
+#: FlatCAMApp.py:7149
+msgid "Origin coordinates specified but incomplete."
+msgstr "Origin coordinates specified but incomplete."
+
+#: FlatCAMApp.py:7190
+msgid "Moving to Origin..."
+msgstr "Moving to Origin..."
+
+#: FlatCAMApp.py:7271
+msgid "Jump to ..."
+msgstr "Jump to ..."
+
+#: FlatCAMApp.py:7272
+msgid "Enter the coordinates in format X,Y:"
+msgstr "Enter the coordinates in format X,Y:"
+
+#: FlatCAMApp.py:7282
+msgid "Wrong coordinates. Enter coordinates in format: X,Y"
+msgstr "Wrong coordinates. Enter coordinates in format: X,Y"
+
+#: FlatCAMApp.py:7360 FlatCAMApp.py:7509
+#: flatcamEditors/FlatCAMExcEditor.py:3624
+#: flatcamEditors/FlatCAMExcEditor.py:3632
+#: flatcamEditors/FlatCAMGeoEditor.py:4349
+#: flatcamEditors/FlatCAMGeoEditor.py:4363
+#: flatcamEditors/FlatCAMGrbEditor.py:1085
+#: flatcamEditors/FlatCAMGrbEditor.py:1202
+#: flatcamEditors/FlatCAMGrbEditor.py:1488
+#: flatcamEditors/FlatCAMGrbEditor.py:1757
+#: flatcamEditors/FlatCAMGrbEditor.py:4489
+#: flatcamEditors/FlatCAMGrbEditor.py:4504 flatcamGUI/FlatCAMGUI.py:3370
+#: flatcamGUI/FlatCAMGUI.py:3382 flatcamTools/ToolAlignObjects.py:393
+#: flatcamTools/ToolAlignObjects.py:415
+msgid "Done."
+msgstr "Done."
+
+#: FlatCAMApp.py:7375 FlatCAMApp.py:9669 FlatCAMApp.py:9764 FlatCAMApp.py:9805
+#: FlatCAMApp.py:9846 FlatCAMApp.py:9887 FlatCAMApp.py:9928 FlatCAMApp.py:9972
+#: FlatCAMApp.py:10016 FlatCAMApp.py:10504 FlatCAMApp.py:10508
+#: flatcamTools/ToolProperties.py:116
+msgid "No object selected."
+msgstr "No object selected."
+
+#: FlatCAMApp.py:7394
+msgid "Bottom-Left"
+msgstr "Bottom-Left"
+
+#: FlatCAMApp.py:7395 flatcamGUI/PreferencesUI.py:8111
+#: flatcamTools/ToolCalibration.py:159
+msgid "Top-Left"
+msgstr "Top-Left"
+
+#: FlatCAMApp.py:7396 flatcamGUI/PreferencesUI.py:8112
+#: flatcamTools/ToolCalibration.py:160
+msgid "Bottom-Right"
+msgstr "Bottom-Right"
+
+#: FlatCAMApp.py:7397
+msgid "Top-Right"
+msgstr "Top-Right"
+
+#: FlatCAMApp.py:7398 flatcamGUI/ObjectUI.py:2625
+msgid "Center"
+msgstr "Center"
+
+#: FlatCAMApp.py:7418
+msgid "Locate ..."
+msgstr "Locate ..."
+
+#: FlatCAMApp.py:7679 FlatCAMApp.py:7756
+msgid "No object is selected. Select an object and try again."
+msgstr "No object is selected. Select an object and try again."
+
+#: FlatCAMApp.py:7782
+msgid ""
+"Aborting. The current task will be gracefully closed as soon as possible..."
+msgstr ""
+"Aborting. The current task will be gracefully closed as soon as possible..."
+
+#: FlatCAMApp.py:7788
+msgid "The current task was gracefully closed on user request..."
+msgstr "The current task was gracefully closed on user request..."
+
+#: FlatCAMApp.py:7881
+msgid "Preferences edited but not saved."
+msgstr "Preferences edited but not saved."
+
+#: FlatCAMApp.py:7898 FlatCAMApp.py:7926 FlatCAMApp.py:7953 FlatCAMApp.py:7972
+#: FlatCAMApp.py:8039 FlatCAMDB.py:738 FlatCAMDB.py:913 FlatCAMDB.py:2168
+#: FlatCAMDB.py:2378 flatcamObjects/FlatCAMGeometry.py:862
+#: flatcamTools/ToolNCC.py:3958 flatcamTools/ToolNCC.py:4042
+#: flatcamTools/ToolPaint.py:3548 flatcamTools/ToolPaint.py:3633
+msgid "Tools Database"
+msgstr "Tools Database"
+
+#: FlatCAMApp.py:7950
+msgid "Tools in Tools Database edited but not saved."
+msgstr "Tools in Tools Database edited but not saved."
+
+#: FlatCAMApp.py:7976 flatcamTools/ToolNCC.py:3965
+#: flatcamTools/ToolPaint.py:3555
+msgid "Tool from DB added in Tool Table."
+msgstr "Tool from DB added in Tool Table."
+
+#: FlatCAMApp.py:7978
+msgid "Adding tool from DB is not allowed for this object."
+msgstr "Adding tool from DB is not allowed for this object."
+
+#: FlatCAMApp.py:8019
+msgid ""
+"One or more values are changed.\n"
+"Do you want to save the Preferences?"
+msgstr ""
+"One or more values are changed.\n"
+"Do you want to save the Preferences?"
+
+#: FlatCAMApp.py:8021 flatcamGUI/FlatCAMGUI.py:291
+msgid "Save Preferences"
+msgstr "Save Preferences"
+
+#: FlatCAMApp.py:8045
+msgid ""
+"One or more Tools are edited.\n"
+"Do you want to update the Tools Database?"
+msgstr ""
+"One or more Tools are edited.\n"
+"Do you want to update the Tools Database?"
+
+#: FlatCAMApp.py:8047
+msgid "Save Tools Database"
+msgstr "Save Tools Database"
+
+#: FlatCAMApp.py:8066 FlatCAMApp.py:10254 flatcamObjects/FlatCAMCNCJob.py:562
+msgid "Code Editor"
+msgstr "Code Editor"
+
+#: FlatCAMApp.py:8088
+msgid "No object selected to Flip on Y axis."
+msgstr "No object selected to Flip on Y axis."
+
+#: FlatCAMApp.py:8114
+msgid "Flip on Y axis done."
+msgstr "Flip on Y axis done."
+
+#: FlatCAMApp.py:8116 FlatCAMApp.py:8164
+#: flatcamEditors/FlatCAMGrbEditor.py:5893
+msgid "Flip action was not executed."
+msgstr "Flip action was not executed."
+
+#: FlatCAMApp.py:8136
+msgid "No object selected to Flip on X axis."
+msgstr "No object selected to Flip on X axis."
+
+#: FlatCAMApp.py:8162
+msgid "Flip on X axis done."
+msgstr "Flip on X axis done."
+
+#: FlatCAMApp.py:8184
+msgid "No object selected to Rotate."
+msgstr "No object selected to Rotate."
+
+#: FlatCAMApp.py:8187 FlatCAMApp.py:8240 FlatCAMApp.py:8279
+msgid "Transform"
+msgstr "Transform"
+
+#: FlatCAMApp.py:8187 FlatCAMApp.py:8240 FlatCAMApp.py:8279
+msgid "Enter the Angle value:"
+msgstr "Enter the Angle value:"
+
+#: FlatCAMApp.py:8218
+msgid "Rotation done."
+msgstr "Rotation done."
+
+#: FlatCAMApp.py:8220
+msgid "Rotation movement was not executed."
+msgstr "Rotation movement was not executed."
+
+#: FlatCAMApp.py:8238
+msgid "No object selected to Skew/Shear on X axis."
+msgstr "No object selected to Skew/Shear on X axis."
+
+#: FlatCAMApp.py:8260
+msgid "Skew on X axis done."
+msgstr "Skew on X axis done."
+
+#: FlatCAMApp.py:8277
+msgid "No object selected to Skew/Shear on Y axis."
+msgstr "No object selected to Skew/Shear on Y axis."
+
+#: FlatCAMApp.py:8299
+msgid "Skew on Y axis done."
+msgstr "Skew on Y axis done."
+
+#: FlatCAMApp.py:8450 FlatCAMApp.py:8497 flatcamGUI/FlatCAMGUI.py:488
+#: flatcamGUI/FlatCAMGUI.py:1713
+msgid "Select All"
+msgstr "Select All"
+
+#: FlatCAMApp.py:8454 FlatCAMApp.py:8501 flatcamGUI/FlatCAMGUI.py:490
+msgid "Deselect All"
+msgstr "Deselect All"
+
+#: FlatCAMApp.py:8517
+msgid "All objects are selected."
+msgstr "All objects are selected."
+
+#: FlatCAMApp.py:8527
+msgid "Objects selection is cleared."
+msgstr "Objects selection is cleared."
+
+#: FlatCAMApp.py:8547 flatcamGUI/FlatCAMGUI.py:1706
+msgid "Grid On/Off"
+msgstr "Grid On/Off"
+
+#: FlatCAMApp.py:8559 flatcamEditors/FlatCAMGeoEditor.py:939
+#: flatcamEditors/FlatCAMGrbEditor.py:2580
+#: flatcamEditors/FlatCAMGrbEditor.py:5475 flatcamGUI/ObjectUI.py:1594
+#: flatcamTools/ToolDblSided.py:192 flatcamTools/ToolDblSided.py:425
+#: flatcamTools/ToolNCC.py:294 flatcamTools/ToolNCC.py:631
+#: flatcamTools/ToolPaint.py:277 flatcamTools/ToolPaint.py:676
+#: flatcamTools/ToolSolderPaste.py:122 flatcamTools/ToolSolderPaste.py:597
+#: flatcamTools/ToolTransform.py:478
+msgid "Add"
+msgstr "Add"
+
+#: FlatCAMApp.py:8561 flatcamEditors/FlatCAMGrbEditor.py:2585
+#: flatcamEditors/FlatCAMGrbEditor.py:2733 flatcamGUI/FlatCAMGUI.py:736
+#: flatcamGUI/FlatCAMGUI.py:1059 flatcamGUI/FlatCAMGUI.py:2126
+#: flatcamGUI/FlatCAMGUI.py:2269 flatcamGUI/FlatCAMGUI.py:2733
+#: flatcamGUI/ObjectUI.py:1622 flatcamObjects/FlatCAMGeometry.py:480
+#: flatcamTools/ToolNCC.py:316 flatcamTools/ToolNCC.py:637
+#: flatcamTools/ToolPaint.py:299 flatcamTools/ToolPaint.py:682
+#: flatcamTools/ToolSolderPaste.py:128 flatcamTools/ToolSolderPaste.py:600
+msgid "Delete"
+msgstr "Delete"
+
+#: FlatCAMApp.py:8577
+msgid "New Grid ..."
+msgstr "New Grid ..."
+
+#: FlatCAMApp.py:8578
+msgid "Enter a Grid Value:"
+msgstr "Enter a Grid Value:"
+
+#: FlatCAMApp.py:8586 FlatCAMApp.py:8613
+msgid "Please enter a grid value with non-zero value, in Float format."
+msgstr "Please enter a grid value with non-zero value, in Float format."
+
+#: FlatCAMApp.py:8592
+msgid "New Grid added"
+msgstr "New Grid added"
+
+#: FlatCAMApp.py:8595
+msgid "Grid already exists"
+msgstr "Grid already exists"
+
+#: FlatCAMApp.py:8598
+msgid "Adding New Grid cancelled"
+msgstr "Adding New Grid cancelled"
+
+#: FlatCAMApp.py:8620
+msgid " Grid Value does not exist"
+msgstr " Grid Value does not exist"
+
+#: FlatCAMApp.py:8623
+msgid "Grid Value deleted"
+msgstr "Grid Value deleted"
+
+#: FlatCAMApp.py:8626
+msgid "Delete Grid value cancelled"
+msgstr "Delete Grid value cancelled"
+
+#: FlatCAMApp.py:8632
+msgid "Key Shortcut List"
+msgstr "Key Shortcut List"
+
+#: FlatCAMApp.py:8666
+msgid " No object selected to copy it's name"
+msgstr " No object selected to copy it's name"
+
+#: FlatCAMApp.py:8670
+msgid "Name copied on clipboard ..."
+msgstr "Name copied on clipboard ..."
+
+#: FlatCAMApp.py:8883 flatcamEditors/FlatCAMGrbEditor.py:4421
+msgid "Coordinates copied to clipboard."
+msgstr "Coordinates copied to clipboard."
+
+#: FlatCAMApp.py:9122 FlatCAMApp.py:9128 FlatCAMApp.py:9134 FlatCAMApp.py:9140
+#: ObjectCollection.py:923 ObjectCollection.py:929 ObjectCollection.py:935
+#: ObjectCollection.py:941 ObjectCollection.py:947 ObjectCollection.py:953
+msgid "selected"
+msgstr "selected"
+
+#: FlatCAMApp.py:9295
+msgid ""
+"There are files/objects opened in FlatCAM.\n"
+"Creating a New project will delete them.\n"
+"Do you want to Save the project?"
+msgstr ""
+"There are files/objects opened in FlatCAM.\n"
+"Creating a New project will delete them.\n"
+"Do you want to Save the project?"
+
+#: FlatCAMApp.py:9316
+msgid "New Project created"
+msgstr "New Project created"
+
+#: FlatCAMApp.py:9464 FlatCAMApp.py:9468 flatcamGUI/FlatCAMGUI.py:821
+#: flatcamGUI/FlatCAMGUI.py:2504
+msgid "Open Gerber"
+msgstr "Open Gerber"
+
+#: FlatCAMApp.py:9473 FlatCAMApp.py:9510 FlatCAMApp.py:9552 FlatCAMApp.py:9622
+#: FlatCAMApp.py:10373 FlatCAMApp.py:11545 FlatCAMApp.py:11606
+msgid ""
+"Canvas initialization started.\n"
+"Canvas initialization finished in"
+msgstr ""
+"Canvas initialization started.\n"
+"Canvas initialization finished in"
+
+#: FlatCAMApp.py:9475
+msgid "Opening Gerber file."
+msgstr "Opening Gerber file."
+
+#: FlatCAMApp.py:9502 FlatCAMApp.py:9506 flatcamGUI/FlatCAMGUI.py:823
+#: flatcamGUI/FlatCAMGUI.py:2506
+msgid "Open Excellon"
+msgstr "Open Excellon"
+
+#: FlatCAMApp.py:9512
+msgid "Opening Excellon file."
+msgstr "Opening Excellon file."
+
+#: FlatCAMApp.py:9543 FlatCAMApp.py:9547
+msgid "Open G-Code"
+msgstr "Open G-Code"
+
+#: FlatCAMApp.py:9554
+msgid "Opening G-Code file."
+msgstr "Opening G-Code file."
+
+#: FlatCAMApp.py:9577 FlatCAMApp.py:9580 flatcamGUI/FlatCAMGUI.py:1715
+msgid "Open Project"
+msgstr "Open Project"
+
+#: FlatCAMApp.py:9613 FlatCAMApp.py:9617
+msgid "Open HPGL2"
+msgstr "Open HPGL2"
+
+#: FlatCAMApp.py:9624
+msgid "Opening HPGL2 file."
+msgstr "Opening HPGL2 file."
+
+#: FlatCAMApp.py:9647 FlatCAMApp.py:9650
+msgid "Open Configuration File"
+msgstr "Open Configuration File"
+
+#: FlatCAMApp.py:9670 FlatCAMApp.py:10017
+msgid "Please Select a Geometry object to export"
+msgstr "Please Select a Geometry object to export"
+
+#: FlatCAMApp.py:9684
+msgid "Only Geometry, Gerber and CNCJob objects can be used."
+msgstr "Only Geometry, Gerber and CNCJob objects can be used."
+
+#: FlatCAMApp.py:9697 FlatCAMApp.py:9701 flatcamTools/ToolQRCode.py:829
+#: flatcamTools/ToolQRCode.py:833
+msgid "Export SVG"
+msgstr "Export SVG"
+
+#: FlatCAMApp.py:9726
+msgid "Data must be a 3D array with last dimension 3 or 4"
+msgstr "Data must be a 3D array with last dimension 3 or 4"
+
+#: FlatCAMApp.py:9732 FlatCAMApp.py:9736
+msgid "Export PNG Image"
+msgstr "Export PNG Image"
+
+#: FlatCAMApp.py:9769 FlatCAMApp.py:9977
+msgid "Failed. Only Gerber objects can be saved as Gerber files..."
+msgstr "Failed. Only Gerber objects can be saved as Gerber files..."
+
+#: FlatCAMApp.py:9781
+msgid "Save Gerber source file"
+msgstr "Save Gerber source file"
+
+#: FlatCAMApp.py:9810
+msgid "Failed. Only Script objects can be saved as TCL Script files..."
+msgstr "Failed. Only Script objects can be saved as TCL Script files..."
+
+#: FlatCAMApp.py:9822
+msgid "Save Script source file"
+msgstr "Save Script source file"
+
+#: FlatCAMApp.py:9851
+msgid "Failed. Only Document objects can be saved as Document files..."
+msgstr "Failed. Only Document objects can be saved as Document files..."
+
+#: FlatCAMApp.py:9863
+msgid "Save Document source file"
+msgstr "Save Document source file"
+
+#: FlatCAMApp.py:9892 FlatCAMApp.py:9933 FlatCAMApp.py:10856
+msgid "Failed. Only Excellon objects can be saved as Excellon files..."
+msgstr "Failed. Only Excellon objects can be saved as Excellon files..."
+
+#: FlatCAMApp.py:9900 FlatCAMApp.py:9904
+msgid "Save Excellon source file"
+msgstr "Save Excellon source file"
+
+#: FlatCAMApp.py:9941 FlatCAMApp.py:9945
+msgid "Export Excellon"
+msgstr "Export Excellon"
+
+#: FlatCAMApp.py:9985 FlatCAMApp.py:9989
+msgid "Export Gerber"
+msgstr "Export Gerber"
+
+#: FlatCAMApp.py:10027
+msgid "Only Geometry objects can be used."
+msgstr "Only Geometry objects can be used."
+
+#: FlatCAMApp.py:10041 FlatCAMApp.py:10045
+msgid "Export DXF"
+msgstr "Export DXF"
+
+#: FlatCAMApp.py:10070 FlatCAMApp.py:10073
+msgid "Import SVG"
+msgstr "Import SVG"
+
+#: FlatCAMApp.py:10101 FlatCAMApp.py:10105
+msgid "Import DXF"
+msgstr "Import DXF"
+
+#: FlatCAMApp.py:10156
+msgid "Viewing the source code of the selected object."
+msgstr "Viewing the source code of the selected object."
+
+#: FlatCAMApp.py:10157 flatcamObjects/FlatCAMCNCJob.py:548
+#: flatcamObjects/FlatCAMScript.py:133
+msgid "Loading..."
+msgstr "Loading..."
+
+#: FlatCAMApp.py:10163 FlatCAMApp.py:10167
+msgid "Select an Gerber or Excellon file to view it's source file."
+msgstr "Select an Gerber or Excellon file to view it's source file."
+
+#: FlatCAMApp.py:10181
+msgid "Source Editor"
+msgstr "Source Editor"
+
+#: FlatCAMApp.py:10221 FlatCAMApp.py:10228
+msgid "There is no selected object for which to see it's source file code."
+msgstr "There is no selected object for which to see it's source file code."
+
+#: FlatCAMApp.py:10240
+msgid "Failed to load the source code for the selected object"
+msgstr "Failed to load the source code for the selected object"
+
+#: FlatCAMApp.py:10276
+msgid "Go to Line ..."
+msgstr "Go to Line ..."
+
+#: FlatCAMApp.py:10277
+msgid "Line:"
+msgstr "Line:"
+
+#: FlatCAMApp.py:10306
+msgid "New TCL script file created in Code Editor."
+msgstr "New TCL script file created in Code Editor."
+
+#: FlatCAMApp.py:10345 FlatCAMApp.py:10347
+msgid "Open TCL script"
+msgstr "Open TCL script"
+
+#: FlatCAMApp.py:10375
+msgid "Executing ScriptObject file."
+msgstr "Executing ScriptObject file."
+
+#: FlatCAMApp.py:10383 FlatCAMApp.py:10386
+msgid "Run TCL script"
+msgstr "Run TCL script"
+
+#: FlatCAMApp.py:10409
+msgid "TCL script file opened in Code Editor and executed."
+msgstr "TCL script file opened in Code Editor and executed."
+
+#: FlatCAMApp.py:10460 FlatCAMApp.py:10466
+msgid "Save Project As ..."
+msgstr "Save Project As ..."
+
+#: FlatCAMApp.py:10462 flatcamGUI/FlatCAMGUI.py:1119
+#: flatcamGUI/FlatCAMGUI.py:2161
+msgid "Project"
+msgstr "Project"
+
+#: FlatCAMApp.py:10501
+msgid "FlatCAM objects print"
+msgstr "FlatCAM objects print"
+
+#: FlatCAMApp.py:10514 FlatCAMApp.py:10521
+msgid "Save Object as PDF ..."
+msgstr "Save Object as PDF ..."
+
+#: FlatCAMApp.py:10530
+msgid "Printing PDF ... Please wait."
+msgstr "Printing PDF ... Please wait."
+
+#: FlatCAMApp.py:10709
+msgid "PDF file saved to"
+msgstr "PDF file saved to"
+
+#: FlatCAMApp.py:10734
+msgid "Exporting SVG"
+msgstr "Exporting SVG"
+
+#: FlatCAMApp.py:10777
+msgid "SVG file exported to"
+msgstr "SVG file exported to"
+
+#: FlatCAMApp.py:10803
+msgid ""
+"Save cancelled because source file is empty. Try to export the Gerber file."
+msgstr ""
+"Save cancelled because source file is empty. Try to export the Gerber file."
+
+#: FlatCAMApp.py:10950
+msgid "Excellon file exported to"
+msgstr "Excellon file exported to"
+
+#: FlatCAMApp.py:10959
+msgid "Exporting Excellon"
+msgstr "Exporting Excellon"
+
+#: FlatCAMApp.py:10964 FlatCAMApp.py:10971
+msgid "Could not export Excellon file."
+msgstr "Could not export Excellon file."
+
+#: FlatCAMApp.py:11086
+msgid "Gerber file exported to"
+msgstr "Gerber file exported to"
+
+#: FlatCAMApp.py:11094
+msgid "Exporting Gerber"
+msgstr "Exporting Gerber"
+
+#: FlatCAMApp.py:11099 FlatCAMApp.py:11106
+msgid "Could not export Gerber file."
+msgstr "Could not export Gerber file."
+
+#: FlatCAMApp.py:11141
+msgid "DXF file exported to"
+msgstr "DXF file exported to"
+
+#: FlatCAMApp.py:11147
+msgid "Exporting DXF"
+msgstr "Exporting DXF"
+
+#: FlatCAMApp.py:11152 FlatCAMApp.py:11159
+msgid "Could not export DXF file."
+msgstr "Could not export DXF file."
+
+#: FlatCAMApp.py:11182 FlatCAMApp.py:11224 flatcamTools/ToolImage.py:277
+msgid ""
+"Not supported type is picked as parameter. Only Geometry and Gerber are "
+"supported"
+msgstr ""
+"Not supported type is picked as parameter. Only Geometry and Gerber are "
+"supported"
+
+#: FlatCAMApp.py:11192
+msgid "Importing SVG"
+msgstr "Importing SVG"
+
+#: FlatCAMApp.py:11203 FlatCAMApp.py:11243 FlatCAMApp.py:11301
+#: FlatCAMApp.py:11366 FlatCAMApp.py:11430 FlatCAMApp.py:11495
+#: FlatCAMApp.py:11532 flatcamTools/ToolImage.py:297
+#: flatcamTools/ToolPDF.py:225
+msgid "Opened"
+msgstr "Opened"
+
+#: FlatCAMApp.py:11233
+msgid "Importing DXF"
+msgstr "Importing DXF"
+
+#: FlatCAMApp.py:11267 FlatCAMApp.py:11454
+msgid "Failed to open file"
+msgstr "Failed to open file"
+
+#: FlatCAMApp.py:11270 FlatCAMApp.py:11457
+msgid "Failed to parse file"
+msgstr "Failed to parse file"
+
+#: FlatCAMApp.py:11282
+msgid "Object is not Gerber file or empty. Aborting object creation."
+msgstr "Object is not Gerber file or empty. Aborting object creation."
+
+#: FlatCAMApp.py:11287
+msgid "Opening Gerber"
+msgstr "Opening Gerber"
+
+#: FlatCAMApp.py:11294
+msgid " Open Gerber failed. Probable not a Gerber file."
+msgstr " Open Gerber failed. Probable not a Gerber file."
+
+#: FlatCAMApp.py:11325 flatcamTools/ToolPcbWizard.py:425
+msgid "This is not Excellon file."
+msgstr "This is not Excellon file."
+
+#: FlatCAMApp.py:11329
+msgid "Cannot open file"
+msgstr "Cannot open file"
+
+#: FlatCAMApp.py:11348 flatcamTools/ToolPDF.py:275
+#: flatcamTools/ToolPcbWizard.py:447
+msgid "No geometry found in file"
+msgstr "No geometry found in file"
+
+#: FlatCAMApp.py:11351
+msgid "Opening Excellon."
+msgstr "Opening Excellon."
+
+#: FlatCAMApp.py:11358
+msgid "Open Excellon file failed. Probable not an Excellon file."
+msgstr "Open Excellon file failed. Probable not an Excellon file."
+
+#: FlatCAMApp.py:11390
+msgid "Reading GCode file"
+msgstr "Reading GCode file"
+
+#: FlatCAMApp.py:11397
+msgid "Failed to open"
+msgstr "Failed to open"
+
+#: FlatCAMApp.py:11405
+msgid "This is not GCODE"
+msgstr "This is not GCODE"
+
+#: FlatCAMApp.py:11410
+msgid "Opening G-Code."
+msgstr "Opening G-Code."
+
+#: FlatCAMApp.py:11419
+msgid ""
+"Failed to create CNCJob Object. Probable not a GCode file. Try to load it "
+"from File menu.\n"
+" Attempting to create a FlatCAM CNCJob Object from G-Code file failed during "
+"processing"
+msgstr ""
+"Failed to create CNCJob Object. Probable not a GCode file. Try to load it "
+"from File menu.\n"
+" Attempting to create a FlatCAM CNCJob Object from G-Code file failed during "
+"processing"
+
+#: FlatCAMApp.py:11476
+msgid "Object is not HPGL2 file or empty. Aborting object creation."
+msgstr "Object is not HPGL2 file or empty. Aborting object creation."
+
+#: FlatCAMApp.py:11481
+msgid "Opening HPGL2"
+msgstr "Opening HPGL2"
+
+#: FlatCAMApp.py:11488
+msgid " Open HPGL2 failed. Probable not a HPGL2 file."
+msgstr " Open HPGL2 failed. Probable not a HPGL2 file."
+
+#: FlatCAMApp.py:11508
+msgid "Opening TCL Script..."
+msgstr "Opening TCL Script..."
+
+#: FlatCAMApp.py:11516
+msgid "TCL script file opened in Code Editor."
+msgstr "TCL script file opened in Code Editor."
+
+#: FlatCAMApp.py:11519
+msgid "Failed to open TCL Script."
+msgstr "Failed to open TCL Script."
+
+#: FlatCAMApp.py:11547
+msgid "Opening FlatCAM Config file."
+msgstr "Opening FlatCAM Config file."
+
+#: FlatCAMApp.py:11575
+msgid "Failed to open config file"
+msgstr "Failed to open config file"
+
+#: FlatCAMApp.py:11603
+msgid "Loading Project ... Please Wait ..."
+msgstr "Loading Project ... Please Wait ..."
+
+#: FlatCAMApp.py:11608
+msgid "Opening FlatCAM Project file."
+msgstr "Opening FlatCAM Project file."
+
+#: FlatCAMApp.py:11618 FlatCAMApp.py:11636
+msgid "Failed to open project file"
+msgstr "Failed to open project file"
+
+#: FlatCAMApp.py:11673
+msgid "Loading Project ... restoring"
+msgstr "Loading Project ... restoring"
+
+#: FlatCAMApp.py:11683
+msgid "Project loaded from"
+msgstr "Project loaded from"
+
+#: FlatCAMApp.py:11752
+msgid "Redrawing all objects"
+msgstr "Redrawing all objects"
+
+#: FlatCAMApp.py:11840
+msgid "Failed to load recent item list."
+msgstr "Failed to load recent item list."
+
+#: FlatCAMApp.py:11847
+msgid "Failed to parse recent item list."
+msgstr "Failed to parse recent item list."
+
+#: FlatCAMApp.py:11857
+msgid "Failed to load recent projects item list."
+msgstr "Failed to load recent projects item list."
+
+#: FlatCAMApp.py:11864
+msgid "Failed to parse recent project item list."
+msgstr "Failed to parse recent project item list."
+
+#: FlatCAMApp.py:11925
+msgid "Clear Recent projects"
+msgstr "Clear Recent projects"
+
+#: FlatCAMApp.py:11949
+msgid "Clear Recent files"
+msgstr "Clear Recent files"
+
+#: FlatCAMApp.py:11971 flatcamGUI/FlatCAMGUI.py:1348
+msgid "<b>Shortcut Key List</b>"
+msgstr "<b>Shortcut Key List</b>"
+
+#: FlatCAMApp.py:12051
+msgid "Selected Tab - Choose an Item from Project Tab"
+msgstr "Selected Tab - Choose an Item from Project Tab"
+
+#: FlatCAMApp.py:12052
+msgid "Details"
+msgstr "Details"
+
+#: FlatCAMApp.py:12054
+msgid "The normal flow when working in FlatCAM is the following:"
+msgstr "The normal flow when working in FlatCAM is the following:"
+
+#: FlatCAMApp.py:12055
+msgid ""
+"Load/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into "
+"FlatCAM using either the toolbars, key shortcuts or even dragging and "
+"dropping the files on the GUI."
+msgstr ""
+"Load/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into "
+"FlatCAM using either the toolbars, key shortcuts or even dragging and "
+"dropping the files on the GUI."
+
+#: FlatCAMApp.py:12058
+msgid ""
+"You can also load a FlatCAM project by double clicking on the project file, "
+"drag and drop of the file into the FLATCAM GUI or through the menu (or "
+"toolbar) actions offered within the app."
+msgstr ""
+"You can also load a FlatCAM project by double clicking on the project file, "
+"drag and drop of the file into the FLATCAM GUI or through the menu (or "
+"toolbar) actions offered within the app."
+
+#: FlatCAMApp.py:12061
+msgid ""
+"Once an object is available in the Project Tab, by selecting it and then "
+"focusing on SELECTED TAB (more simpler is to double click the object name in "
+"the Project Tab, SELECTED TAB will be updated with the object properties "
+"according to its kind: Gerber, Excellon, Geometry or CNCJob object."
+msgstr ""
+"Once an object is available in the Project Tab, by selecting it and then "
+"focusing on SELECTED TAB (more simpler is to double click the object name in "
+"the Project Tab, SELECTED TAB will be updated with the object properties "
+"according to its kind: Gerber, Excellon, Geometry or CNCJob object."
+
+#: FlatCAMApp.py:12065
+msgid ""
+"If the selection of the object is done on the canvas by single click "
+"instead, and the SELECTED TAB is in focus, again the object properties will "
+"be displayed into the Selected Tab. Alternatively, double clicking on the "
+"object on the canvas will bring the SELECTED TAB and populate it even if it "
+"was out of focus."
+msgstr ""
+"If the selection of the object is done on the canvas by single click "
+"instead, and the SELECTED TAB is in focus, again the object properties will "
+"be displayed into the Selected Tab. Alternatively, double clicking on the "
+"object on the canvas will bring the SELECTED TAB and populate it even if it "
+"was out of focus."
+
+#: FlatCAMApp.py:12069
+msgid ""
+"You can change the parameters in this screen and the flow direction is like "
+"this:"
+msgstr ""
+"You can change the parameters in this screen and the flow direction is like "
+"this:"
+
+#: FlatCAMApp.py:12070
+msgid ""
+"Gerber/Excellon Object --> Change Parameter --> Generate Geometry --> "
+"Geometry Object --> Add tools (change param in Selected Tab) --> Generate "
+"CNCJob --> CNCJob Object --> Verify GCode (through Edit CNC Code) and/or "
+"append/prepend to GCode (again, done in SELECTED TAB) --> Save GCode."
+msgstr ""
+"Gerber/Excellon Object --> Change Parameter --> Generate Geometry --> "
+"Geometry Object --> Add tools (change param in Selected Tab) --> Generate "
+"CNCJob --> CNCJob Object --> Verify GCode (through Edit CNC Code) and/or "
+"append/prepend to GCode (again, done in SELECTED TAB) --> Save GCode."
+
+#: FlatCAMApp.py:12074
+msgid ""
+"A list of key shortcuts is available through an menu entry in Help --> "
+"Shortcuts List or through its own key shortcut: <b>F3</b>."
+msgstr ""
+"A list of key shortcuts is available through an menu entry in Help --> "
+"Shortcuts List or through its own key shortcut: <b>F3</b>."
+
+#: FlatCAMApp.py:12138
+msgid "Failed checking for latest version. Could not connect."
+msgstr "Failed checking for latest version. Could not connect."
+
+#: FlatCAMApp.py:12145
+msgid "Could not parse information about latest version."
+msgstr "Could not parse information about latest version."
+
+#: FlatCAMApp.py:12155
+msgid "FlatCAM is up to date!"
+msgstr "FlatCAM is up to date!"
+
+#: FlatCAMApp.py:12160
+msgid "Newer Version Available"
+msgstr "Newer Version Available"
+
+#: FlatCAMApp.py:12162
+msgid "There is a newer version of FlatCAM available for download:"
+msgstr "There is a newer version of FlatCAM available for download:"
+
+#: FlatCAMApp.py:12166
+msgid "info"
+msgstr "info"
+
+#: FlatCAMApp.py:12194
+msgid ""
+"OpenGL canvas initialization failed. HW or HW configuration not supported."
+"Change the graphic engine to Legacy(2D) in Edit -> Preferences -> General "
+"tab.\n"
+"\n"
+msgstr ""
+"OpenGL canvas initialization failed. HW or HW configuration not supported."
+"Change the graphic engine to Legacy(2D) in Edit -> Preferences -> General "
+"tab.\n"
+"\n"
+
+#: FlatCAMApp.py:12273
+msgid "All plots disabled."
+msgstr "All plots disabled."
+
+#: FlatCAMApp.py:12280
+msgid "All non selected plots disabled."
+msgstr "All non selected plots disabled."
+
+#: FlatCAMApp.py:12287
+msgid "All plots enabled."
+msgstr "All plots enabled."
+
+#: FlatCAMApp.py:12293
+msgid "Selected plots enabled..."
+msgstr "Selected plots enabled..."
+
+#: FlatCAMApp.py:12301
+msgid "Selected plots disabled..."
+msgstr "Selected plots disabled..."
+
+#: FlatCAMApp.py:12334
+msgid "Enabling plots ..."
+msgstr "Enabling plots ..."
+
+#: FlatCAMApp.py:12386
+msgid "Disabling plots ..."
+msgstr "Disabling plots ..."
+
+#: FlatCAMApp.py:12409
+msgid "Working ..."
+msgstr "Working ..."
+
+#: FlatCAMApp.py:12464 flatcamGUI/FlatCAMGUI.py:688
+msgid "Red"
+msgstr "Red"
+
+#: FlatCAMApp.py:12466 flatcamGUI/FlatCAMGUI.py:691
+msgid "Blue"
+msgstr "Blue"
+
+#: FlatCAMApp.py:12469 flatcamGUI/FlatCAMGUI.py:694
+msgid "Yellow"
+msgstr "Yellow"
+
+#: FlatCAMApp.py:12471 flatcamGUI/FlatCAMGUI.py:697
+msgid "Green"
+msgstr "Green"
+
+#: FlatCAMApp.py:12473 flatcamGUI/FlatCAMGUI.py:700
+msgid "Purple"
+msgstr "Purple"
+
+#: FlatCAMApp.py:12475 flatcamGUI/FlatCAMGUI.py:703
+msgid "Brown"
+msgstr "Brown"
+
+#: FlatCAMApp.py:12477 FlatCAMApp.py:12533 flatcamGUI/FlatCAMGUI.py:706
+msgid "White"
+msgstr "White"
+
+#: FlatCAMApp.py:12479 flatcamGUI/FlatCAMGUI.py:709
+msgid "Black"
+msgstr "Black"
+
+#: FlatCAMApp.py:12482 flatcamGUI/FlatCAMGUI.py:714
+msgid "Custom"
+msgstr "Custom"
+
+#: FlatCAMApp.py:12492 flatcamGUI/FlatCAMGUI.py:722
+msgid "Default"
+msgstr "Default"
+
+#: FlatCAMApp.py:12516 flatcamGUI/FlatCAMGUI.py:719
+msgid "Opacity"
+msgstr "Opacity"
+
+#: FlatCAMApp.py:12518
+msgid "Set alpha level ..."
+msgstr "Set alpha level ..."
+
+#: FlatCAMApp.py:12518 flatcamGUI/PreferencesUI.py:6900
+#: flatcamGUI/PreferencesUI.py:8230 flatcamGUI/PreferencesUI.py:8444
+#: flatcamTools/ToolExtractDrills.py:164 flatcamTools/ToolExtractDrills.py:285
+#: flatcamTools/ToolPunchGerber.py:192 flatcamTools/ToolPunchGerber.py:308
+#: flatcamTools/ToolTransform.py:357
+msgid "Value"
+msgstr "Value"
+
+#: FlatCAMApp.py:12594
+msgid "Saving FlatCAM Project"
+msgstr "Saving FlatCAM Project"
+
+#: FlatCAMApp.py:12615 FlatCAMApp.py:12651
+msgid "Project saved to"
+msgstr "Project saved to"
+
+#: FlatCAMApp.py:12622
+msgid "The object is used by another application."
+msgstr "The object is used by another application."
+
+#: FlatCAMApp.py:12636
+msgid "Failed to verify project file"
+msgstr "Failed to verify project file"
+
+#: FlatCAMApp.py:12636 FlatCAMApp.py:12644 FlatCAMApp.py:12654
+msgid "Retry to save it."
+msgstr "Retry to save it."
+
+#: FlatCAMApp.py:12644 FlatCAMApp.py:12654
+msgid "Failed to parse saved project file"
+msgstr "Failed to parse saved project file"
+
+#: FlatCAMBookmark.py:57 FlatCAMBookmark.py:84
+msgid "Title"
+msgstr "Title"
+
+#: FlatCAMBookmark.py:58 FlatCAMBookmark.py:88
+msgid "Web Link"
+msgstr "Web Link"
+
+#: FlatCAMBookmark.py:62
+msgid ""
+"Index.\n"
+"The rows in gray color will populate the Bookmarks menu.\n"
+"The number of gray colored rows is set in Preferences."
+msgstr ""
+"Index.\n"
+"The rows in gray color will populate the Bookmarks menu.\n"
+"The number of gray colored rows is set in Preferences."
+
+#: FlatCAMBookmark.py:66
+msgid ""
+"Description of the link that is set as an menu action.\n"
+"Try to keep it short because it is installed as a menu item."
+msgstr ""
+"Description of the link that is set as an menu action.\n"
+"Try to keep it short because it is installed as a menu item."
+
+#: FlatCAMBookmark.py:69
+msgid "Web Link. E.g: https://your_website.org "
+msgstr "Web Link. E.g: https://your_website.org "
+
+#: FlatCAMBookmark.py:78
+msgid "New Bookmark"
+msgstr "New Bookmark"
+
+#: FlatCAMBookmark.py:97
+msgid "Add Entry"
+msgstr "Add Entry"
+
+#: FlatCAMBookmark.py:98
+msgid "Remove Entry"
+msgstr "Remove Entry"
+
+#: FlatCAMBookmark.py:99
+msgid "Export List"
+msgstr "Export List"
+
+#: FlatCAMBookmark.py:100
+msgid "Import List"
+msgstr "Import List"
+
+#: FlatCAMBookmark.py:181
+msgid "Title entry is empty."
+msgstr "Title entry is empty."
+
+#: FlatCAMBookmark.py:190
+msgid "Web link entry is empty."
+msgstr "Web link entry is empty."
+
+#: FlatCAMBookmark.py:198
+msgid "Either the Title or the Weblink already in the table."
+msgstr "Either the Title or the Weblink already in the table."
+
+#: FlatCAMBookmark.py:218
+msgid "Bookmark added."
+msgstr "Bookmark added."
+
+#: FlatCAMBookmark.py:235
+msgid "This bookmark can not be removed"
+msgstr "This bookmark can not be removed"
+
+#: FlatCAMBookmark.py:266
+msgid "Bookmark removed."
+msgstr "Bookmark removed."
+
+#: FlatCAMBookmark.py:281
+msgid "Export FlatCAM Bookmarks"
+msgstr "Export FlatCAM Bookmarks"
+
+#: FlatCAMBookmark.py:284 flatcamGUI/FlatCAMGUI.py:509
+msgid "Bookmarks"
+msgstr "Bookmarks"
+
+#: FlatCAMBookmark.py:310 FlatCAMBookmark.py:340
+msgid "Could not load bookmarks file."
+msgstr "Could not load bookmarks file."
+
+#: FlatCAMBookmark.py:320
+msgid "Failed to write bookmarks to file."
+msgstr "Failed to write bookmarks to file."
+
+#: FlatCAMBookmark.py:322
+msgid "Exported bookmarks to"
+msgstr "Exported bookmarks to"
+
+#: FlatCAMBookmark.py:328
+msgid "Import FlatCAM Bookmarks"
+msgstr "Import FlatCAM Bookmarks"
+
+#: FlatCAMBookmark.py:347
+msgid "Imported Bookmarks from"
+msgstr "Imported Bookmarks from"
+
+#: FlatCAMCommon.py:29
+msgid "The user requested a graceful exit of the current task."
+msgstr "The user requested a graceful exit of the current task."
+
+#: FlatCAMDB.py:86
+msgid "Add Geometry Tool in DB"
+msgstr "Add Geometry Tool in DB"
+
+#: FlatCAMDB.py:88 FlatCAMDB.py:1643
+msgid ""
+"Add a new tool in the Tools Database.\n"
+"It will be used in the Geometry UI.\n"
+"You can edit it after it is added."
+msgstr ""
+"Add a new tool in the Tools Database.\n"
+"It will be used in the Geometry UI.\n"
+"You can edit it after it is added."
+
+#: FlatCAMDB.py:102 FlatCAMDB.py:1657
+msgid "Delete Tool from DB"
+msgstr "Delete Tool from DB"
+
+#: FlatCAMDB.py:104 FlatCAMDB.py:1659
+msgid "Remove a selection of tools in the Tools Database."
+msgstr "Remove a selection of tools in the Tools Database."
+
+#: FlatCAMDB.py:108 FlatCAMDB.py:1663
+msgid "Export DB"
+msgstr "Export DB"
+
+#: FlatCAMDB.py:110 FlatCAMDB.py:1665
+msgid "Save the Tools Database to a custom text file."
+msgstr "Save the Tools Database to a custom text file."
+
+#: FlatCAMDB.py:114 FlatCAMDB.py:1669
+msgid "Import DB"
+msgstr "Import DB"
+
+#: FlatCAMDB.py:116 FlatCAMDB.py:1671
+msgid "Load the Tools Database information's from a custom text file."
+msgstr "Load the Tools Database information's from a custom text file."
+
+#: FlatCAMDB.py:120 FlatCAMDB.py:1675
+msgid "Add Tool from Tools DB"
+msgstr "Add Tool from Tools DB"
+
+#: FlatCAMDB.py:122 FlatCAMDB.py:1677
+msgid ""
+"Add a new tool in the Tools Table of the\n"
+"active Geometry object after selecting a tool\n"
+"in the Tools Database."
+msgstr ""
+"Add a new tool in the Tools Table of the\n"
+"active Geometry object after selecting a tool\n"
+"in the Tools Database."
+
+#: FlatCAMDB.py:158 FlatCAMDB.py:833 FlatCAMDB.py:1087
+msgid "Tool Name"
+msgstr "Tool Name"
+
+#: FlatCAMDB.py:159 FlatCAMDB.py:835 FlatCAMDB.py:1100
+#: flatcamEditors/FlatCAMExcEditor.py:1604 flatcamGUI/ObjectUI.py:1344
+#: flatcamGUI/ObjectUI.py:1582 flatcamGUI/PreferencesUI.py:5971
+#: flatcamTools/ToolNCC.py:278 flatcamTools/ToolNCC.py:287
+#: flatcamTools/ToolPaint.py:261
+msgid "Tool Dia"
+msgstr "Tool Dia"
+
+#: FlatCAMDB.py:160 FlatCAMDB.py:837 FlatCAMDB.py:1281
+#: flatcamGUI/ObjectUI.py:1557
+msgid "Tool Offset"
+msgstr "Tool Offset"
+
+#: FlatCAMDB.py:161 FlatCAMDB.py:839 FlatCAMDB.py:1298
+msgid "Custom Offset"
+msgstr "Custom Offset"
+
+#: FlatCAMDB.py:162 FlatCAMDB.py:841 FlatCAMDB.py:1265
+#: flatcamGUI/ObjectUI.py:308 flatcamGUI/PreferencesUI.py:2397
+#: flatcamGUI/PreferencesUI.py:5332 flatcamGUI/PreferencesUI.py:5901
+#: flatcamGUI/PreferencesUI.py:5911 flatcamTools/ToolNCC.py:213
+#: flatcamTools/ToolNCC.py:227 flatcamTools/ToolPaint.py:196
+msgid "Tool Type"
+msgstr "Tool Type"
+
+#: FlatCAMDB.py:163 FlatCAMDB.py:843 FlatCAMDB.py:1113
+msgid "Tool Shape"
+msgstr "Tool Shape"
+
+#: FlatCAMDB.py:164 FlatCAMDB.py:846 FlatCAMDB.py:1129
+#: flatcamGUI/ObjectUI.py:349 flatcamGUI/ObjectUI.py:899
+#: flatcamGUI/ObjectUI.py:1702 flatcamGUI/ObjectUI.py:2255
+#: flatcamGUI/PreferencesUI.py:2437 flatcamGUI/PreferencesUI.py:3311
+#: flatcamGUI/PreferencesUI.py:4241 flatcamGUI/PreferencesUI.py:5377
+#: flatcamGUI/PreferencesUI.py:5666 flatcamGUI/PreferencesUI.py:5944
+#: flatcamGUI/PreferencesUI.py:5952 flatcamGUI/PreferencesUI.py:6635
+#: flatcamTools/ToolCalculators.py:114 flatcamTools/ToolCutOut.py:138
+#: flatcamTools/ToolNCC.py:260 flatcamTools/ToolNCC.py:268
+#: flatcamTools/ToolPaint.py:243
+msgid "Cut Z"
+msgstr "Cut Z"
+
+#: FlatCAMDB.py:165 FlatCAMDB.py:848 FlatCAMDB.py:1143
+msgid "MultiDepth"
+msgstr "MultiDepth"
+
+#: FlatCAMDB.py:166 FlatCAMDB.py:850 FlatCAMDB.py:1156
+msgid "DPP"
+msgstr "DPP"
+
+#: FlatCAMDB.py:167 FlatCAMDB.py:852 FlatCAMDB.py:1312
+msgid "V-Dia"
+msgstr "V-Dia"
+
+#: FlatCAMDB.py:168 FlatCAMDB.py:854 FlatCAMDB.py:1326
+msgid "V-Angle"
+msgstr "V-Angle"
+
+#: FlatCAMDB.py:169 FlatCAMDB.py:856 FlatCAMDB.py:1170
+#: flatcamGUI/ObjectUI.py:945 flatcamGUI/ObjectUI.py:1749
+#: flatcamGUI/PreferencesUI.py:3352 flatcamGUI/PreferencesUI.py:4294
+#: flatcamGUI/PreferencesUI.py:8041 flatcamObjects/FlatCAMExcellon.py:1316
+#: flatcamObjects/FlatCAMGeometry.py:1552 flatcamTools/ToolCalibration.py:74
+msgid "Travel Z"
+msgstr "Travel Z"
+
+#: FlatCAMDB.py:170 FlatCAMDB.py:858
+msgid "FR"
+msgstr "FR"
+
+#: FlatCAMDB.py:171 FlatCAMDB.py:860
+msgid "FR Z"
+msgstr "FR Z"
+
+#: FlatCAMDB.py:172 FlatCAMDB.py:862 FlatCAMDB.py:1340
+msgid "FR Rapids"
+msgstr "FR Rapids"
+
+#: FlatCAMDB.py:173 FlatCAMDB.py:864 FlatCAMDB.py:1213
+#: flatcamGUI/PreferencesUI.py:3440
+msgid "Spindle Speed"
+msgstr "Spindle Speed"
+
+#: FlatCAMDB.py:174 FlatCAMDB.py:866 FlatCAMDB.py:1228
+#: flatcamGUI/ObjectUI.py:1063 flatcamGUI/ObjectUI.py:1856
+msgid "Dwell"
+msgstr "Dwell"
+
+#: FlatCAMDB.py:175 FlatCAMDB.py:868 FlatCAMDB.py:1241
+msgid "Dwelltime"
+msgstr "Dwelltime"
+
+#: FlatCAMDB.py:176 FlatCAMDB.py:870 flatcamGUI/ObjectUI.py:2013
+#: flatcamGUI/PreferencesUI.py:3475 flatcamGUI/PreferencesUI.py:4447
+#: flatcamGUI/PreferencesUI.py:7148 flatcamTools/ToolSolderPaste.py:335
+msgid "Preprocessor"
+msgstr "Preprocessor"
+
+#: FlatCAMDB.py:177 FlatCAMDB.py:872 FlatCAMDB.py:1356
+msgid "ExtraCut"
+msgstr "ExtraCut"
+
+#: FlatCAMDB.py:178 FlatCAMDB.py:874 FlatCAMDB.py:1371
+msgid "E-Cut Length"
+msgstr "E-Cut Length"
+
+#: FlatCAMDB.py:179 FlatCAMDB.py:876
+msgid "Toolchange"
+msgstr "Toolchange"
+
+#: FlatCAMDB.py:180 FlatCAMDB.py:878
+msgid "Toolchange XY"
+msgstr "Toolchange XY"
+
+#: FlatCAMDB.py:181 FlatCAMDB.py:880 flatcamGUI/PreferencesUI.py:3378
+#: flatcamGUI/PreferencesUI.py:4324 flatcamGUI/PreferencesUI.py:8078
+#: flatcamTools/ToolCalibration.py:111
+msgid "Toolchange Z"
+msgstr "Toolchange Z"
+
+#: FlatCAMDB.py:182 FlatCAMDB.py:882 flatcamGUI/ObjectUI.py:1192
+#: flatcamGUI/PreferencesUI.py:3586 flatcamGUI/PreferencesUI.py:4493
+msgid "Start Z"
+msgstr "Start Z"
+
+#: FlatCAMDB.py:183 FlatCAMDB.py:885
+msgid "End Z"
+msgstr "End Z"
+
+#: FlatCAMDB.py:187
+msgid "Tool Index."
+msgstr "Tool Index."
+
+#: FlatCAMDB.py:189 FlatCAMDB.py:1089
+msgid ""
+"Tool name.\n"
+"This is not used in the app, it's function\n"
+"is to serve as a note for the user."
+msgstr ""
+"Tool name.\n"
+"This is not used in the app, it's function\n"
+"is to serve as a note for the user."
+
+#: FlatCAMDB.py:193 FlatCAMDB.py:1102
+msgid "Tool Diameter."
+msgstr "Tool Diameter."
+
+#: FlatCAMDB.py:195 FlatCAMDB.py:1283
+msgid ""
+"Tool Offset.\n"
+"Can be of a few types:\n"
+"Path = zero offset\n"
+"In = offset inside by half of tool diameter\n"
+"Out = offset outside by half of tool diameter\n"
+"Custom = custom offset using the Custom Offset value"
+msgstr ""
+"Tool Offset.\n"
+"Can be of a few types:\n"
+"Path = zero offset\n"
+"In = offset inside by half of tool diameter\n"
+"Out = offset outside by half of tool diameter\n"
+"Custom = custom offset using the Custom Offset value"
+
+#: FlatCAMDB.py:202 FlatCAMDB.py:1300
+msgid ""
+"Custom Offset.\n"
+"A value to be used as offset from the current path."
+msgstr ""
+"Custom Offset.\n"
+"A value to be used as offset from the current path."
+
+#: FlatCAMDB.py:205 FlatCAMDB.py:1267
+msgid ""
+"Tool Type.\n"
+"Can be:\n"
+"Iso = isolation cut\n"
+"Rough = rough cut, low feedrate, multiple passes\n"
+"Finish = finishing cut, high feedrate"
+msgstr ""
+"Tool Type.\n"
+"Can be:\n"
+"Iso = isolation cut\n"
+"Rough = rough cut, low feedrate, multiple passes\n"
+"Finish = finishing cut, high feedrate"
+
+#: FlatCAMDB.py:211 FlatCAMDB.py:1115
+msgid ""
+"Tool Shape. \n"
+"Can be:\n"
+"C1 ... C4 = circular tool with x flutes\n"
+"B = ball tip milling tool\n"
+"V = v-shape milling tool"
+msgstr ""
+"Tool Shape. \n"
+"Can be:\n"
+"C1 ... C4 = circular tool with x flutes\n"
+"B = ball tip milling tool\n"
+"V = v-shape milling tool"
+
+#: FlatCAMDB.py:217 FlatCAMDB.py:1131
+msgid ""
+"Cutting Depth.\n"
+"The depth at which to cut into material."
+msgstr ""
+"Cutting Depth.\n"
+"The depth at which to cut into material."
+
+#: FlatCAMDB.py:220 FlatCAMDB.py:1145
+msgid ""
+"Multi Depth.\n"
+"Selecting this will allow cutting in multiple passes,\n"
+"each pass adding a DPP parameter depth."
+msgstr ""
+"Multi Depth.\n"
+"Selecting this will allow cutting in multiple passes,\n"
+"each pass adding a DPP parameter depth."
+
+#: FlatCAMDB.py:224 FlatCAMDB.py:1158
+msgid ""
+"DPP. Depth per Pass.\n"
+"The value used to cut into material on each pass."
+msgstr ""
+"DPP. Depth per Pass.\n"
+"The value used to cut into material on each pass."
+
+#: FlatCAMDB.py:227 FlatCAMDB.py:1314
+msgid ""
+"V-Dia.\n"
+"Diameter of the tip for V-Shape Tools."
+msgstr ""
+"V-Dia.\n"
+"Diameter of the tip for V-Shape Tools."
+
+#: FlatCAMDB.py:230 FlatCAMDB.py:1328
+msgid ""
+"V-Agle.\n"
+"Angle at the tip for the V-Shape Tools."
+msgstr ""
+"V-Agle.\n"
+"Angle at the tip for the V-Shape Tools."
+
+#: FlatCAMDB.py:233 FlatCAMDB.py:1172
+msgid ""
+"Clearance Height.\n"
+"Height at which the milling bit will travel between cuts,\n"
+"above the surface of the material, avoiding all fixtures."
+msgstr ""
+"Clearance Height.\n"
+"Height at which the milling bit will travel between cuts,\n"
+"above the surface of the material, avoiding all fixtures."
+
+#: FlatCAMDB.py:237
+msgid ""
+"FR. Feedrate\n"
+"The speed on XY plane used while cutting into material."
+msgstr ""
+"FR. Feedrate\n"
+"The speed on XY plane used while cutting into material."
+
+#: FlatCAMDB.py:240
+msgid ""
+"FR Z. Feedrate Z\n"
+"The speed on Z plane."
+msgstr ""
+"FR Z. Feedrate Z\n"
+"The speed on Z plane."
+
+#: FlatCAMDB.py:243 FlatCAMDB.py:1342
+msgid ""
+"FR Rapids. Feedrate Rapids\n"
+"Speed used while moving as fast as possible.\n"
+"This is used only by some devices that can't use\n"
+"the G0 g-code command. Mostly 3D printers."
+msgstr ""
+"FR Rapids. Feedrate Rapids\n"
+"Speed used while moving as fast as possible.\n"
+"This is used only by some devices that can't use\n"
+"the G0 g-code command. Mostly 3D printers."
+
+#: FlatCAMDB.py:248 FlatCAMDB.py:1215
+msgid ""
+"Spindle Speed.\n"
+"If it's left empty it will not be used.\n"
+"The speed of the spindle in RPM."
+msgstr ""
+"Spindle Speed.\n"
+"If it's left empty it will not be used.\n"
+"The speed of the spindle in RPM."
+
+#: FlatCAMDB.py:252 FlatCAMDB.py:1230
+msgid ""
+"Dwell.\n"
+"Check this if a delay is needed to allow\n"
+"the spindle motor to reach it's set speed."
+msgstr ""
+"Dwell.\n"
+"Check this if a delay is needed to allow\n"
+"the spindle motor to reach it's set speed."
+
+#: FlatCAMDB.py:256 FlatCAMDB.py:1243
+msgid ""
+"Dwell Time.\n"
+"A delay used to allow the motor spindle reach it's set speed."
+msgstr ""
+"Dwell Time.\n"
+"A delay used to allow the motor spindle reach it's set speed."
+
+#: FlatCAMDB.py:259
+msgid ""
+"Preprocessor.\n"
+"A selection of files that will alter the generated G-code\n"
+"to fit for a number of use cases."
+msgstr ""
+"Preprocessor.\n"
+"A selection of files that will alter the generated G-code\n"
+"to fit for a number of use cases."
+
+#: FlatCAMDB.py:263 FlatCAMDB.py:1358
+msgid ""
+"Extra Cut.\n"
+"If checked, after a isolation is finished an extra cut\n"
+"will be added where the start and end of isolation meet\n"
+"such as that this point is covered by this extra cut to\n"
+"ensure a complete isolation."
+msgstr ""
+"Extra Cut.\n"
+"If checked, after a isolation is finished an extra cut\n"
+"will be added where the start and end of isolation meet\n"
+"such as that this point is covered by this extra cut to\n"
+"ensure a complete isolation."
+
+#: FlatCAMDB.py:269 FlatCAMDB.py:1373
+msgid ""
+"Extra Cut length.\n"
+"If checked, after a isolation is finished an extra cut\n"
+"will be added where the start and end of isolation meet\n"
+"such as that this point is covered by this extra cut to\n"
+"ensure a complete isolation. This is the length of\n"
+"the extra cut."
+msgstr ""
+"Extra Cut length.\n"
+"If checked, after a isolation is finished an extra cut\n"
+"will be added where the start and end of isolation meet\n"
+"such as that this point is covered by this extra cut to\n"
+"ensure a complete isolation. This is the length of\n"
+"the extra cut."
+
+#: FlatCAMDB.py:276
+msgid ""
+"Toolchange.\n"
+"It will create a toolchange event.\n"
+"The kind of toolchange is determined by\n"
+"the preprocessor file."
+msgstr ""
+"Toolchange.\n"
+"It will create a toolchange event.\n"
+"The kind of toolchange is determined by\n"
+"the preprocessor file."
+
+#: FlatCAMDB.py:281
+msgid ""
+"Toolchange XY.\n"
+"A set of coordinates in the format (x, y).\n"
+"Will determine the cartesian position of the point\n"
+"where the tool change event take place."
+msgstr ""
+"Toolchange XY.\n"
+"A set of coordinates in the format (x, y).\n"
+"Will determine the cartesian position of the point\n"
+"where the tool change event take place."
+
+#: FlatCAMDB.py:286
+msgid ""
+"Toolchange Z.\n"
+"The position on Z plane where the tool change event take place."
+msgstr ""
+"Toolchange Z.\n"
+"The position on Z plane where the tool change event take place."
+
+#: FlatCAMDB.py:289
+msgid ""
+"Start Z.\n"
+"If it's left empty it will not be used.\n"
+"A position on Z plane to move immediately after job start."
+msgstr ""
+"Start Z.\n"
+"If it's left empty it will not be used.\n"
+"A position on Z plane to move immediately after job start."
+
+#: FlatCAMDB.py:293
+msgid ""
+"End Z.\n"
+"A position on Z plane to move immediately after job stop."
+msgstr ""
+"End Z.\n"
+"A position on Z plane to move immediately after job stop."
+
+#: FlatCAMDB.py:305 FlatCAMDB.py:682 FlatCAMDB.py:716 FlatCAMDB.py:1891
+#: FlatCAMDB.py:2112 FlatCAMDB.py:2146
+msgid "Could not load Tools DB file."
+msgstr "Could not load Tools DB file."
+
+#: FlatCAMDB.py:313 FlatCAMDB.py:724 FlatCAMDB.py:1899 FlatCAMDB.py:2154
+msgid "Failed to parse Tools DB file."
+msgstr "Failed to parse Tools DB file."
+
+#: FlatCAMDB.py:316 FlatCAMDB.py:727 FlatCAMDB.py:1902 FlatCAMDB.py:2157
+msgid "Loaded FlatCAM Tools DB from"
+msgstr "Loaded FlatCAM Tools DB from"
+
+#: FlatCAMDB.py:322 FlatCAMDB.py:1816
+msgid "Add to DB"
+msgstr "Add to DB"
+
+#: FlatCAMDB.py:324 FlatCAMDB.py:1819
+msgid "Copy from DB"
+msgstr "Copy from DB"
+
+#: FlatCAMDB.py:326 FlatCAMDB.py:1822
+msgid "Delete from DB"
+msgstr "Delete from DB"
+
+#: FlatCAMDB.py:603 FlatCAMDB.py:2029
+msgid "Tool added to DB."
+msgstr "Tool added to DB."
+
+#: FlatCAMDB.py:624 FlatCAMDB.py:2053
+msgid "Tool copied from Tools DB."
+msgstr "Tool copied from Tools DB."
+
+#: FlatCAMDB.py:642 FlatCAMDB.py:2072
+msgid "Tool removed from Tools DB."
+msgstr "Tool removed from Tools DB."
+
+#: FlatCAMDB.py:653 FlatCAMDB.py:2083
+msgid "Export Tools Database"
+msgstr "Export Tools Database"
+
+#: FlatCAMDB.py:656 FlatCAMDB.py:2086
+msgid "Tools_Database"
+msgstr "Tools_Database"
+
+#: FlatCAMDB.py:693 FlatCAMDB.py:696 FlatCAMDB.py:748 FlatCAMDB.py:2123
+#: FlatCAMDB.py:2126 FlatCAMDB.py:2178
+msgid "Failed to write Tools DB to file."
+msgstr "Failed to write Tools DB to file."
+
+#: FlatCAMDB.py:699 FlatCAMDB.py:2129
+msgid "Exported Tools DB to"
+msgstr "Exported Tools DB to"
+
+#: FlatCAMDB.py:706 FlatCAMDB.py:2136
+msgid "Import FlatCAM Tools DB"
+msgstr "Import FlatCAM Tools DB"
+
+#: FlatCAMDB.py:752 FlatCAMDB.py:2182
+msgid "Saved Tools DB."
+msgstr "Saved Tools DB."
+
+#: FlatCAMDB.py:899 FlatCAMDB.py:2365
+msgid "No Tool/row selected in the Tools Database table"
+msgstr "No Tool/row selected in the Tools Database table"
+
+#: FlatCAMDB.py:917 FlatCAMDB.py:2382
+msgid "Cancelled adding tool from DB."
+msgstr "Cancelled adding tool from DB."
+
+#: FlatCAMDB.py:1018
+msgid "Basic Geo Parameters"
+msgstr "Basic Geo Parameters"
+
+#: FlatCAMDB.py:1030
+msgid "Advanced Geo Parameters"
+msgstr "Advanced Geo Parameters"
+
+#: FlatCAMDB.py:1042
+msgid "NCC Parameters"
+msgstr "NCC Parameters"
+
+#: FlatCAMDB.py:1054
+msgid "Paint Parameters"
+msgstr "Paint Parameters"
+
+#: FlatCAMDB.py:1185 flatcamGUI/ObjectUI.py:966 flatcamGUI/ObjectUI.py:1768
+#: flatcamGUI/PreferencesUI.py:4378 flatcamGUI/PreferencesUI.py:7059
+#: flatcamTools/ToolSolderPaste.py:253
+msgid "Feedrate X-Y"
+msgstr "Feedrate X-Y"
+
+#: FlatCAMDB.py:1187
+msgid ""
+"Feedrate X-Y. Feedrate\n"
+"The speed on XY plane used while cutting into material."
+msgstr ""
+"Feedrate X-Y. Feedrate\n"
+"The speed on XY plane used while cutting into material."
+
+#: FlatCAMDB.py:1199 flatcamGUI/ObjectUI.py:981 flatcamGUI/ObjectUI.py:1782
+#: flatcamGUI/PreferencesUI.py:3425 flatcamGUI/PreferencesUI.py:4393
+#: flatcamGUI/PreferencesUI.py:7072 flatcamTools/ToolSolderPaste.py:265
+msgid "Feedrate Z"
+msgstr "Feedrate Z"
+
+#: FlatCAMDB.py:1201
+msgid ""
+"Feedrate Z\n"
+"The speed on Z plane."
+msgstr ""
+"Feedrate Z\n"
+"The speed on Z plane."
+
+#: FlatCAMDB.py:1399 flatcamGUI/ObjectUI.py:844
+#: flatcamGUI/PreferencesUI.py:3264 flatcamTools/ToolNCC.py:341
+msgid "Operation"
+msgstr "Operation"
+
+#: FlatCAMDB.py:1401 flatcamTools/ToolNCC.py:343
+msgid ""
+"The 'Operation' can be:\n"
+"- Isolation -> will ensure that the non-copper clearing is always complete.\n"
+"If it's not successful then the non-copper clearing will fail, too.\n"
+"- Clear -> the regular non-copper clearing."
+msgstr ""
+"The 'Operation' can be:\n"
+"- Isolation -> will ensure that the non-copper clearing is always complete.\n"
+"If it's not successful then the non-copper clearing will fail, too.\n"
+"- Clear -> the regular non-copper clearing."
+
+#: FlatCAMDB.py:1408 flatcamEditors/FlatCAMGrbEditor.py:2739
+#: flatcamGUI/GUIElements.py:2577 flatcamTools/ToolNCC.py:350
+msgid "Clear"
+msgstr "Clear"
+
+#: FlatCAMDB.py:1409 flatcamTools/ToolNCC.py:351 flatcamTools/ToolNCC.py:1618
+msgid "Isolation"
+msgstr "Isolation"
+
+#: FlatCAMDB.py:1417 flatcamGUI/ObjectUI.py:408 flatcamGUI/ObjectUI.py:866
+#: flatcamGUI/PreferencesUI.py:2257 flatcamGUI/PreferencesUI.py:3280
+#: flatcamGUI/PreferencesUI.py:4665 flatcamGUI/PreferencesUI.py:5416
+#: flatcamTools/ToolNCC.py:359
+msgid "Milling Type"
+msgstr "Milling Type"
+
+#: FlatCAMDB.py:1419 FlatCAMDB.py:1427 flatcamGUI/PreferencesUI.py:5418
+#: flatcamGUI/PreferencesUI.py:5426 flatcamTools/ToolNCC.py:361
+#: flatcamTools/ToolNCC.py:369
+msgid ""
+"Milling type when the selected tool is of type: 'iso_op':\n"
+"- climb / best for precision milling and to reduce tool usage\n"
+"- conventional / useful when there is no backlash compensation"
+msgstr ""
+"Milling type when the selected tool is of type: 'iso_op':\n"
+"- climb / best for precision milling and to reduce tool usage\n"
+"- conventional / useful when there is no backlash compensation"
+
+#: FlatCAMDB.py:1424 flatcamGUI/ObjectUI.py:414
+#: flatcamGUI/PreferencesUI.py:2264 flatcamGUI/PreferencesUI.py:4671
+#: flatcamGUI/PreferencesUI.py:5423 flatcamTools/ToolNCC.py:366
+msgid "Climb"
+msgstr "Climb"
+
+#: FlatCAMDB.py:1425 flatcamGUI/ObjectUI.py:415
+#: flatcamGUI/PreferencesUI.py:2265 flatcamGUI/PreferencesUI.py:4672
+#: flatcamGUI/PreferencesUI.py:5424 flatcamTools/ToolNCC.py:367
+msgid "Conventional"
+msgstr "Conventional"
+
+#: FlatCAMDB.py:1437 FlatCAMDB.py:1546 flatcamEditors/FlatCAMGeoEditor.py:451
+#: flatcamGUI/PreferencesUI.py:5461 flatcamGUI/PreferencesUI.py:6002
+#: flatcamTools/ToolNCC.py:382 flatcamTools/ToolPaint.py:329
+msgid "Overlap"
+msgstr "Overlap"
+
+#: FlatCAMDB.py:1439 flatcamGUI/PreferencesUI.py:5463
+#: flatcamTools/ToolNCC.py:384
+msgid ""
+"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"
+"Lower values = faster processing, faster execution on CNC.\n"
+"Higher values = slow processing and slow execution on CNC\n"
+"due of too many paths."
+msgstr ""
+"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"
+"Lower values = faster processing, faster execution on CNC.\n"
+"Higher values = slow processing and slow execution on CNC\n"
+"due of too many paths."
+
+#: FlatCAMDB.py:1458 FlatCAMDB.py:1567 flatcamEditors/FlatCAMGeoEditor.py:471
+#: flatcamGUI/PreferencesUI.py:5481 flatcamGUI/PreferencesUI.py:5723
+#: flatcamGUI/PreferencesUI.py:6022 flatcamGUI/PreferencesUI.py:7681
+#: flatcamGUI/PreferencesUI.py:7838 flatcamGUI/PreferencesUI.py:7923
+#: flatcamGUI/PreferencesUI.py:8570 flatcamGUI/PreferencesUI.py:8578
+#: flatcamTools/ToolCopperThieving.py:111
+#: flatcamTools/ToolCopperThieving.py:362 flatcamTools/ToolCutOut.py:190
+#: flatcamTools/ToolFiducials.py:172 flatcamTools/ToolInvertGerber.py:88
+#: flatcamTools/ToolInvertGerber.py:96 flatcamTools/ToolNCC.py:403
+#: flatcamTools/ToolPaint.py:350
+msgid "Margin"
+msgstr "Margin"
+
+#: FlatCAMDB.py:1460 flatcamGUI/PreferencesUI.py:5483
+#: flatcamGUI/PreferencesUI.py:7683 flatcamGUI/PreferencesUI.py:7925
+#: flatcamGUI/PreferencesUI.py:7989 flatcamTools/ToolCopperThieving.py:113
+#: flatcamTools/ToolFiducials.py:174 flatcamTools/ToolFiducials.py:237
+#: flatcamTools/ToolNCC.py:405
+msgid "Bounding box margin."
+msgstr "Bounding box margin."
+
+#: FlatCAMDB.py:1471 FlatCAMDB.py:1582 flatcamEditors/FlatCAMGeoEditor.py:485
+#: flatcamGUI/PreferencesUI.py:5494 flatcamGUI/PreferencesUI.py:6037
+#: flatcamGUI/PreferencesUI.py:8204 flatcamGUI/PreferencesUI.py:8417
+#: flatcamTools/ToolExtractDrills.py:128 flatcamTools/ToolNCC.py:416
+#: flatcamTools/ToolPaint.py:365 flatcamTools/ToolPunchGerber.py:139
+msgid "Method"
+msgstr "Method"
+
+#: FlatCAMDB.py:1473 flatcamGUI/PreferencesUI.py:5496
+#: flatcamTools/ToolNCC.py:418
+msgid ""
+"Algorithm for copper clearing:\n"
+"- Standard: Fixed step inwards.\n"
+"- Seed-based: Outwards from seed.\n"
+"- Line-based: Parallel lines."
+msgstr ""
+"Algorithm for copper clearing:\n"
+"- Standard: Fixed step inwards.\n"
+"- Seed-based: Outwards from seed.\n"
+"- Line-based: Parallel lines."
+
+#: FlatCAMDB.py:1481 FlatCAMDB.py:1596 flatcamEditors/FlatCAMGeoEditor.py:499
+#: flatcamGUI/PreferencesUI.py:5509 flatcamGUI/PreferencesUI.py:6056
+#: flatcamTools/ToolNCC.py:431 flatcamTools/ToolNCC.py:2390
+#: flatcamTools/ToolNCC.py:2419 flatcamTools/ToolNCC.py:2688
+#: flatcamTools/ToolNCC.py:2720 flatcamTools/ToolPaint.py:390
+#: flatcamTools/ToolPaint.py:1829 tclCommands/TclCommandCopperClear.py:126
+#: tclCommands/TclCommandCopperClear.py:134 tclCommands/TclCommandPaint.py:125
+msgid "Standard"
+msgstr "Standard"
+
+#: FlatCAMDB.py:1481 FlatCAMDB.py:1596 flatcamEditors/FlatCAMGeoEditor.py:499
+#: flatcamEditors/FlatCAMGeoEditor.py:5156 flatcamGUI/PreferencesUI.py:5509
+#: flatcamGUI/PreferencesUI.py:6056 flatcamTools/ToolNCC.py:431
+#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:699
+#: flatcamTools/ToolPaint.py:1857 tclCommands/TclCommandCopperClear.py:130
+#: tclCommands/TclCommandPaint.py:129
+msgid "Lines"
+msgstr "Lines"
+
+#: FlatCAMDB.py:1489 FlatCAMDB.py:1607 flatcamGUI/PreferencesUI.py:5516
+#: flatcamGUI/PreferencesUI.py:6063 flatcamTools/ToolNCC.py:439
+#: flatcamTools/ToolPaint.py:401
+msgid "Connect"
+msgstr "Connect"
+
+#: FlatCAMDB.py:1493 FlatCAMDB.py:1610 flatcamEditors/FlatCAMGeoEditor.py:508
+#: flatcamGUI/PreferencesUI.py:5518 flatcamGUI/PreferencesUI.py:6065
+#: flatcamTools/ToolNCC.py:443 flatcamTools/ToolPaint.py:404
+msgid ""
+"Draw lines between resulting\n"
+"segments to minimize tool lifts."
+msgstr ""
+"Draw lines between resulting\n"
+"segments to minimize tool lifts."
+
+#: FlatCAMDB.py:1499 FlatCAMDB.py:1614 flatcamGUI/PreferencesUI.py:5525
+#: flatcamGUI/PreferencesUI.py:6071 flatcamTools/ToolNCC.py:449
+#: flatcamTools/ToolPaint.py:408
+msgid "Contour"
+msgstr "Contour"
+
+#: FlatCAMDB.py:1503 FlatCAMDB.py:1617 flatcamEditors/FlatCAMGeoEditor.py:518
+#: flatcamGUI/PreferencesUI.py:5527 flatcamGUI/PreferencesUI.py:6073
+#: flatcamTools/ToolNCC.py:453 flatcamTools/ToolPaint.py:411
+msgid ""
+"Cut around the perimeter of the polygon\n"
+"to trim rough edges."
+msgstr ""
+"Cut around the perimeter of the polygon\n"
+"to trim rough edges."
+
+#: FlatCAMDB.py:1509 flatcamEditors/FlatCAMGeoEditor.py:612
+#: flatcamEditors/FlatCAMGrbEditor.py:5145 flatcamGUI/ObjectUI.py:142
+#: flatcamGUI/ObjectUI.py:1496 flatcamGUI/ObjectUI.py:2245
+#: flatcamGUI/PreferencesUI.py:5534 flatcamGUI/PreferencesUI.py:6822
+#: flatcamTools/ToolNCC.py:459 flatcamTools/ToolTransform.py:28
+msgid "Offset"
+msgstr "Offset"
+
+#: FlatCAMDB.py:1513 flatcamGUI/PreferencesUI.py:5536
+#: flatcamTools/ToolNCC.py:463
+msgid ""
+"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."
+msgstr ""
+"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."
+
+#: FlatCAMDB.py:1548 flatcamEditors/FlatCAMGeoEditor.py:453
+#: flatcamGUI/PreferencesUI.py:6004 flatcamTools/ToolPaint.py:331
+msgid ""
+"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"
+"Lower values = faster processing, faster execution on CNC.\n"
+"Higher values = slow processing and slow execution on CNC\n"
+"due of too many paths."
+msgstr ""
+"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"
+"Lower values = faster processing, faster execution on CNC.\n"
+"Higher values = slow processing and slow execution on CNC\n"
+"due of too many paths."
+
+#: FlatCAMDB.py:1569 flatcamEditors/FlatCAMGeoEditor.py:473
+#: flatcamGUI/PreferencesUI.py:6024 flatcamTools/ToolPaint.py:352
+msgid ""
+"Distance by which to avoid\n"
+"the edges of the polygon to\n"
+"be painted."
+msgstr ""
+"Distance by which to avoid\n"
+"the edges of the polygon to\n"
+"be painted."
+
+#: FlatCAMDB.py:1584 flatcamGUI/PreferencesUI.py:6039
+#: flatcamTools/ToolPaint.py:367
+msgid ""
+"Algorithm for painting:\n"
+"- Standard: Fixed step inwards.\n"
+"- Seed-based: Outwards from seed.\n"
+"- Line-based: Parallel lines.\n"
+"- Laser-lines: Active only for Gerber objects.\n"
+"Will create lines that follow the traces.\n"
+"- Combo: In case of failure a new method will be picked from the above\n"
+"in the order specified."
+msgstr ""
+"Algorithm for painting:\n"
+"- Standard: Fixed step inwards.\n"
+"- Seed-based: Outwards from seed.\n"
+"- Line-based: Parallel lines.\n"
+"- Laser-lines: Active only for Gerber objects.\n"
+"Will create lines that follow the traces.\n"
+"- Combo: In case of failure a new method will be picked from the above\n"
+"in the order specified."
+
+#: FlatCAMDB.py:1596 FlatCAMDB.py:1598 flatcamGUI/PreferencesUI.py:6056
+#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:392
+#: flatcamTools/ToolPaint.py:693 flatcamTools/ToolPaint.py:698
+#: flatcamTools/ToolPaint.py:1871 tclCommands/TclCommandPaint.py:131
+msgid "Laser_lines"
+msgstr "Laser_lines"
+
+#: FlatCAMDB.py:1596 flatcamGUI/PreferencesUI.py:6056
+#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:2022
+#: tclCommands/TclCommandPaint.py:133
+msgid "Combo"
+msgstr "Combo"
+
+#: FlatCAMDB.py:1641
+msgid "Add Tool in DB"
+msgstr "Add Tool in DB"
+
+#: FlatCAMProcess.py:172
+msgid "processes running."
+msgstr "processes running."
+
+#: FlatCAMTool.py:245 FlatCAMTool.py:252 flatcamGUI/ObjectUI.py:156
+#: flatcamGUI/ObjectUI.py:163
+msgid "Edited value is out of range"
+msgstr "Edited value is out of range"
+
+#: FlatCAMTool.py:247 FlatCAMTool.py:254 flatcamGUI/ObjectUI.py:158
+#: flatcamGUI/ObjectUI.py:165
+msgid "Edited value is within limits."
+msgstr "Edited value is within limits."
+
+#: FlatCAMTranslation.py:103
+msgid "The application will restart."
+msgstr "The application will restart."
+
+#: FlatCAMTranslation.py:105
+msgid "Are you sure do you want to change the current language to"
+msgstr "Are you sure do you want to change the current language to"
+
+#: FlatCAMTranslation.py:106
+msgid "Apply Language ..."
+msgstr "Apply Language ..."
+
+#: ObjectCollection.py:511
+#, python-brace-format
+msgid "Object renamed from <b>{old}</b> to <b>{new}</b>"
+msgstr "Object renamed from <b>{old}</b> to <b>{new}</b>"
+
+#: ObjectCollection.py:984
+msgid "Cause of error"
+msgstr "Cause of error"
+
+#: camlib.py:597
+msgid "self.solid_geometry is neither BaseGeometry or list."
+msgstr "self.solid_geometry is neither BaseGeometry or list."
+
+#: camlib.py:970
+msgid "Pass"
+msgstr "Pass"
+
+#: camlib.py:981 flatcamGUI/PreferencesUI.py:2476
+#: flatcamObjects/FlatCAMGerber.py:496 flatcamTools/ToolCopperThieving.py:1016
+#: flatcamTools/ToolCopperThieving.py:1205
+#: flatcamTools/ToolCopperThieving.py:1217 flatcamTools/ToolNCC.py:2045
+#: flatcamTools/ToolNCC.py:2153 flatcamTools/ToolNCC.py:2167
+#: flatcamTools/ToolNCC.py:3098 flatcamTools/ToolNCC.py:3203
+#: flatcamTools/ToolNCC.py:3218 flatcamTools/ToolNCC.py:3484
+#: flatcamTools/ToolNCC.py:3585 flatcamTools/ToolNCC.py:3600
+msgid "Buffering"
+msgstr "Buffering"
+
+#: camlib.py:990
+msgid "Get Exteriors"
+msgstr "Get Exteriors"
+
+#: camlib.py:993
+msgid "Get Interiors"
+msgstr "Get Interiors"
+
+#: camlib.py:2172
+msgid "Object was mirrored"
+msgstr "Object was mirrored"
+
+#: camlib.py:2174
+msgid "Failed to mirror. No object selected"
+msgstr "Failed to mirror. No object selected"
+
+#: camlib.py:2239
+msgid "Object was rotated"
+msgstr "Object was rotated"
+
+#: camlib.py:2241
+msgid "Failed to rotate. No object selected"
+msgstr "Failed to rotate. No object selected"
+
+#: camlib.py:2307
+msgid "Object was skewed"
+msgstr "Object was skewed"
+
+#: camlib.py:2309
+msgid "Failed to skew. No object selected"
+msgstr "Failed to skew. No object selected"
+
+#: camlib.py:2385
+msgid "Object was buffered"
+msgstr "Object was buffered"
+
+#: camlib.py:2387
+msgid "Failed to buffer. No object selected"
+msgstr "Failed to buffer. No object selected"
+
+#: camlib.py:2594
+msgid "There is no such parameter"
+msgstr "There is no such parameter"
+
+#: camlib.py:2654 camlib.py:2887 camlib.py:3116 camlib.py:3338
+msgid ""
+"The Cut Z parameter has positive value. It is the depth value to drill into "
+"material.\n"
+"The Cut Z parameter needs to have a negative value, assuming it is a typo "
+"therefore the app will convert the value to negative. Check the resulting "
+"CNC code (Gcode etc)."
+msgstr ""
+"The Cut Z parameter has positive value. It is the depth value to drill into "
+"material.\n"
+"The Cut Z parameter needs to have a negative value, assuming it is a typo "
+"therefore the app will convert the value to negative. Check the resulting "
+"CNC code (Gcode etc)."
+
+#: camlib.py:2662 camlib.py:2897 camlib.py:3126 camlib.py:3348 camlib.py:3634
+#: camlib.py:4020
+msgid "The Cut Z parameter is zero. There will be no cut, skipping file"
+msgstr "The Cut Z parameter is zero. There will be no cut, skipping file"
+
+#: camlib.py:2673 camlib.py:3988
+msgid ""
+"The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, "
+"y) \n"
+"but now there is only one value, not two. "
+msgstr ""
+"The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, "
+"y) \n"
+"but now there is only one value, not two. "
+
+#: camlib.py:2682 camlib.py:3585 camlib.py:3970
+msgid ""
+"The End Move X,Y field in Edit -> Preferences has to be in the format (x, y) "
+"but now there is only one value, not two."
+msgstr ""
+"The End Move X,Y field in Edit -> Preferences has to be in the format (x, y) "
+"but now there is only one value, not two."
+
+#: camlib.py:2770
+msgid "Creating a list of points to drill..."
+msgstr "Creating a list of points to drill..."
+
+#: camlib.py:2860 camlib.py:3732 camlib.py:4124
+msgid "Starting G-Code"
+msgstr "Starting G-Code"
+
+#: camlib.py:3001 camlib.py:3220 camlib.py:3384 camlib.py:3745 camlib.py:4135
+msgid "Starting G-Code for tool with diameter"
+msgstr "Starting G-Code for tool with diameter"
+
+#: camlib.py:3084 camlib.py:3302 camlib.py:3470
+msgid "G91 coordinates not implemented"
+msgstr "G91 coordinates not implemented"
+
+#: camlib.py:3090 camlib.py:3309 camlib.py:3476
+msgid "The loaded Excellon file has no drills"
+msgstr "The loaded Excellon file has no drills"
+
+#: camlib.py:3499
+msgid "Finished G-Code generation..."
+msgstr "Finished G-Code generation..."
+
+#: camlib.py:3603
+msgid ""
+"The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, "
+"y) \n"
+"but now there is only one value, not two."
+msgstr ""
+"The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, "
+"y) \n"
+"but now there is only one value, not two."
+
+#: camlib.py:3617 camlib.py:4003
+msgid ""
+"Cut_Z parameter is None or zero. Most likely a bad combinations of other "
+"parameters."
+msgstr ""
+"Cut_Z parameter is None or zero. Most likely a bad combinations of other "
+"parameters."
+
+#: camlib.py:3626 camlib.py:4012
+msgid ""
+"The Cut Z parameter has positive value. It is the depth value to cut into "
+"material.\n"
+"The Cut Z parameter needs to have a negative value, assuming it is a typo "
+"therefore the app will convert the value to negative.Check the resulting CNC "
+"code (Gcode etc)."
+msgstr ""
+"The Cut Z parameter has positive value. It is the depth value to cut into "
+"material.\n"
+"The Cut Z parameter needs to have a negative value, assuming it is a typo "
+"therefore the app will convert the value to negative.Check the resulting CNC "
+"code (Gcode etc)."
+
+#: camlib.py:3639 camlib.py:4026
+msgid "Travel Z parameter is None or zero."
+msgstr "Travel Z parameter is None or zero."
+
+#: camlib.py:3644 camlib.py:4031
+msgid ""
+"The Travel Z parameter has negative value. It is the height value to travel "
+"between cuts.\n"
+"The Z Travel parameter needs to have a positive value, assuming it is a typo "
+"therefore the app will convert the value to positive.Check the resulting CNC "
+"code (Gcode etc)."
+msgstr ""
+"The Travel Z parameter has negative value. It is the height value to travel "
+"between cuts.\n"
+"The Z Travel parameter needs to have a positive value, assuming it is a typo "
+"therefore the app will convert the value to positive.Check the resulting CNC "
+"code (Gcode etc)."
+
+#: camlib.py:3652 camlib.py:4039
+msgid "The Z Travel parameter is zero. This is dangerous, skipping file"
+msgstr "The Z Travel parameter is zero. This is dangerous, skipping file"
+
+#: camlib.py:3671 camlib.py:4062
+msgid "Indexing geometry before generating G-Code..."
+msgstr "Indexing geometry before generating G-Code..."
+
+#: camlib.py:3815 camlib.py:4204
+msgid "Finished G-Code generation"
+msgstr "Finished G-Code generation"
+
+#: camlib.py:3815
+msgid "paths traced"
+msgstr "paths traced"
+
+#: camlib.py:3865
+msgid "Expected a Geometry, got"
+msgstr "Expected a Geometry, got"
+
+#: camlib.py:3872
+msgid ""
+"Trying to generate a CNC Job from a Geometry object without solid_geometry."
+msgstr ""
+"Trying to generate a CNC Job from a Geometry object without solid_geometry."
+
+#: camlib.py:3913
+msgid ""
+"The Tool Offset value is too negative to use for the current_geometry.\n"
+"Raise the value (in module) and try again."
+msgstr ""
+"The Tool Offset value is too negative to use for the current_geometry.\n"
+"Raise the value (in module) and try again."
+
+#: camlib.py:4204
+msgid " paths traced."
+msgstr " paths traced."
+
+#: camlib.py:4232
+msgid "There is no tool data in the SolderPaste geometry."
+msgstr "There is no tool data in the SolderPaste geometry."
+
+#: camlib.py:4321
+#| msgid "Finished SolderPste G-Code generation"
+msgid "Finished SolderPaste G-Code generation"
+msgstr "Finished SolderPaste G-Code generation"
+
+#: camlib.py:4321
+msgid "paths traced."
+msgstr "paths traced."
+
+#: camlib.py:4581
+msgid "Parsing GCode file. Number of lines"
+msgstr "Parsing GCode file. Number of lines"
+
+#: camlib.py:4688
+msgid "Creating Geometry from the parsed GCode file. "
+msgstr "Creating Geometry from the parsed GCode file. "
+
+#: camlib.py:4831 camlib.py:5123 camlib.py:5234 camlib.py:5390
+msgid "G91 coordinates not implemented ..."
+msgstr "G91 coordinates not implemented ..."
+
+#: camlib.py:4963
+msgid "Unifying Geometry from parsed Geometry segments"
+msgstr "Unifying Geometry from parsed Geometry segments"
+
+#: flatcamEditors/FlatCAMExcEditor.py:50 flatcamEditors/FlatCAMExcEditor.py:74
+#: flatcamEditors/FlatCAMExcEditor.py:168
+#: flatcamEditors/FlatCAMExcEditor.py:385
+#: flatcamEditors/FlatCAMExcEditor.py:589
+#: flatcamEditors/FlatCAMGrbEditor.py:241
+#: flatcamEditors/FlatCAMGrbEditor.py:248
+msgid "Click to place ..."
+msgstr "Click to place ..."
+
+#: flatcamEditors/FlatCAMExcEditor.py:58
+msgid "To add a drill first select a tool"
+msgstr "To add a drill first select a tool"
+
+#: flatcamEditors/FlatCAMExcEditor.py:122
+msgid "Done. Drill added."
+msgstr "Done. Drill added."
+
+#: flatcamEditors/FlatCAMExcEditor.py:176
+msgid "To add an Drill Array first select a tool in Tool Table"
+msgstr "To add an Drill Array first select a tool in Tool Table"
+
+#: flatcamEditors/FlatCAMExcEditor.py:192
+#: flatcamEditors/FlatCAMExcEditor.py:415
+#: flatcamEditors/FlatCAMExcEditor.py:636
+#: flatcamEditors/FlatCAMExcEditor.py:1151
+#: flatcamEditors/FlatCAMExcEditor.py:1178
+#: flatcamEditors/FlatCAMGrbEditor.py:471
+#: flatcamEditors/FlatCAMGrbEditor.py:1935
+#: flatcamEditors/FlatCAMGrbEditor.py:1965
+msgid "Click on target location ..."
+msgstr "Click on target location ..."
+
+#: flatcamEditors/FlatCAMExcEditor.py:211
+msgid "Click on the Drill Circular Array Start position"
+msgstr "Click on the Drill Circular Array Start position"
+
+#: flatcamEditors/FlatCAMExcEditor.py:233
+#: flatcamEditors/FlatCAMExcEditor.py:677
+#: flatcamEditors/FlatCAMGrbEditor.py:516
+msgid "The value is not Float. Check for comma instead of dot separator."
+msgstr "The value is not Float. Check for comma instead of dot separator."
+
+#: flatcamEditors/FlatCAMExcEditor.py:237
+msgid "The value is mistyped. Check the value"
+msgstr "The value is mistyped. Check the value"
+
+#: flatcamEditors/FlatCAMExcEditor.py:336
+msgid "Too many drills for the selected spacing angle."
+msgstr "Too many drills for the selected spacing angle."
+
+#: flatcamEditors/FlatCAMExcEditor.py:354
+msgid "Done. Drill Array added."
+msgstr "Done. Drill Array added."
+
+#: flatcamEditors/FlatCAMExcEditor.py:394
+msgid "To add a slot first select a tool"
+msgstr "To add a slot first select a tool"
+
+#: flatcamEditors/FlatCAMExcEditor.py:454
+#: flatcamEditors/FlatCAMExcEditor.py:461
+#: flatcamEditors/FlatCAMExcEditor.py:742
+#: flatcamEditors/FlatCAMExcEditor.py:749
+msgid "Value is missing or wrong format. Add it and retry."
+msgstr "Value is missing or wrong format. Add it and retry."
+
+#: flatcamEditors/FlatCAMExcEditor.py:559
+msgid "Done. Adding Slot completed."
+msgstr "Done. Adding Slot completed."
+
+#: flatcamEditors/FlatCAMExcEditor.py:597
+msgid "To add an Slot Array first select a tool in Tool Table"
+msgstr "To add an Slot Array first select a tool in Tool Table"
+
+#: flatcamEditors/FlatCAMExcEditor.py:655
+msgid "Click on the Slot Circular Array Start position"
+msgstr "Click on the Slot Circular Array Start position"
+
+#: flatcamEditors/FlatCAMExcEditor.py:680
+#: flatcamEditors/FlatCAMGrbEditor.py:519
+msgid "The value is mistyped. Check the value."
+msgstr "The value is mistyped. Check the value."
+
+#: flatcamEditors/FlatCAMExcEditor.py:859
+msgid "Too many Slots for the selected spacing angle."
+msgstr "Too many Slots for the selected spacing angle."
+
+#: flatcamEditors/FlatCAMExcEditor.py:882
+msgid "Done. Slot Array added."
+msgstr "Done. Slot Array added."
+
+#: flatcamEditors/FlatCAMExcEditor.py:904
+msgid "Click on the Drill(s) to resize ..."
+msgstr "Click on the Drill(s) to resize ..."
+
+#: flatcamEditors/FlatCAMExcEditor.py:934
+msgid "Resize drill(s) failed. Please enter a diameter for resize."
+msgstr "Resize drill(s) failed. Please enter a diameter for resize."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1112
+msgid "Done. Drill/Slot Resize completed."
+msgstr "Done. Drill/Slot Resize completed."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1115
+msgid "Cancelled. No drills/slots selected for resize ..."
+msgstr "Cancelled. No drills/slots selected for resize ..."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1153
+#: flatcamEditors/FlatCAMGrbEditor.py:1937
+msgid "Click on reference location ..."
+msgstr "Click on reference location ..."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1210
+msgid "Done. Drill(s) Move completed."
+msgstr "Done. Drill(s) Move completed."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1318
+msgid "Done. Drill(s) copied."
+msgstr "Done. Drill(s) copied."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1557 flatcamGUI/PreferencesUI.py:3829
+msgid "Excellon Editor"
+msgstr "Excellon Editor"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1564
+#: flatcamEditors/FlatCAMGrbEditor.py:2460
+msgid "Name:"
+msgstr "Name:"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1570 flatcamGUI/ObjectUI.py:760
+#: flatcamGUI/ObjectUI.py:1464 flatcamTools/ToolNCC.py:120
+#: flatcamTools/ToolPaint.py:115 flatcamTools/ToolSolderPaste.py:74
+msgid "Tools Table"
+msgstr "Tools Table"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1572 flatcamGUI/ObjectUI.py:762
+msgid ""
+"Tools in this Excellon object\n"
+"when are used for drilling."
+msgstr ""
+"Tools in this Excellon object\n"
+"when are used for drilling."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1584
+#: flatcamEditors/FlatCAMExcEditor.py:3066 flatcamGUI/ObjectUI.py:780
+#: flatcamObjects/FlatCAMExcellon.py:1098
+#: flatcamObjects/FlatCAMExcellon.py:1188
+#: flatcamObjects/FlatCAMExcellon.py:1373 flatcamTools/ToolNCC.py:132
+#: flatcamTools/ToolPaint.py:128 flatcamTools/ToolPcbWizard.py:76
+#: flatcamTools/ToolProperties.py:416 flatcamTools/ToolProperties.py:476
+#: flatcamTools/ToolSolderPaste.py:85 tclCommands/TclCommandDrillcncjob.py:193
+msgid "Diameter"
+msgstr "Diameter"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1592
+msgid "Add/Delete Tool"
+msgstr "Add/Delete Tool"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1594
+msgid ""
+"Add/Delete a tool to the tool list\n"
+"for this Excellon object."
+msgstr ""
+"Add/Delete a tool to the tool list\n"
+"for this Excellon object."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1606 flatcamGUI/ObjectUI.py:1584
+#: flatcamGUI/PreferencesUI.py:3860
+msgid "Diameter for the new tool"
+msgstr "Diameter for the new tool"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1616
+msgid "Add Tool"
+msgstr "Add Tool"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1618
+msgid ""
+"Add a new tool to the tool list\n"
+"with the diameter specified above."
+msgstr ""
+"Add a new tool to the tool list\n"
+"with the diameter specified above."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1630
+msgid "Delete Tool"
+msgstr "Delete Tool"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1632
+msgid ""
+"Delete a tool in the tool list\n"
+"by selecting a row in the tool table."
+msgstr ""
+"Delete a tool in the tool list\n"
+"by selecting a row in the tool table."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1650 flatcamGUI/FlatCAMGUI.py:2004
+msgid "Resize Drill(s)"
+msgstr "Resize Drill(s)"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1652
+msgid "Resize a drill or a selection of drills."
+msgstr "Resize a drill or a selection of drills."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1659
+msgid "Resize Dia"
+msgstr "Resize Dia"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1661
+msgid "Diameter to resize to."
+msgstr "Diameter to resize to."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1672
+msgid "Resize"
+msgstr "Resize"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1674
+msgid "Resize drill(s)"
+msgstr "Resize drill(s)"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1699 flatcamGUI/FlatCAMGUI.py:2003
+#: flatcamGUI/FlatCAMGUI.py:2255
+msgid "Add Drill Array"
+msgstr "Add Drill Array"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1701
+msgid "Add an array of drills (linear or circular array)"
+msgstr "Add an array of drills (linear or circular array)"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1707
+msgid ""
+"Select the type of drills array to create.\n"
+"It can be Linear X(Y) or Circular"
+msgstr ""
+"Select the type of drills array to create.\n"
+"It can be Linear X(Y) or Circular"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1710
+#: flatcamEditors/FlatCAMExcEditor.py:1924
+#: flatcamEditors/FlatCAMGrbEditor.py:2772
+msgid "Linear"
+msgstr "Linear"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1711
+#: flatcamEditors/FlatCAMExcEditor.py:1925
+#: flatcamEditors/FlatCAMGrbEditor.py:2773 flatcamGUI/ObjectUI.py:315
+#: flatcamGUI/PreferencesUI.py:5340 flatcamGUI/PreferencesUI.py:5909
+#: flatcamGUI/PreferencesUI.py:7971 flatcamGUI/PreferencesUI.py:8151
+#: flatcamGUI/PreferencesUI.py:8248 flatcamGUI/PreferencesUI.py:8363
+#: flatcamGUI/PreferencesUI.py:8462 flatcamTools/ToolExtractDrills.py:78
+#: flatcamTools/ToolExtractDrills.py:201 flatcamTools/ToolFiducials.py:220
+#: flatcamTools/ToolNCC.py:221 flatcamTools/ToolPaint.py:204
+#: flatcamTools/ToolPunchGerber.py:89 flatcamTools/ToolPunchGerber.py:229
+msgid "Circular"
+msgstr "Circular"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1719 flatcamGUI/PreferencesUI.py:3871
+msgid "Nr of drills"
+msgstr "Nr of drills"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1720 flatcamGUI/PreferencesUI.py:3873
+msgid "Specify how many drills to be in the array."
+msgstr "Specify how many drills to be in the array."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1738
+#: flatcamEditors/FlatCAMExcEditor.py:1788
+#: flatcamEditors/FlatCAMExcEditor.py:1860
+#: flatcamEditors/FlatCAMExcEditor.py:1953
+#: flatcamEditors/FlatCAMExcEditor.py:2004
+#: flatcamEditors/FlatCAMGrbEditor.py:1571
+#: flatcamEditors/FlatCAMGrbEditor.py:2801
+#: flatcamEditors/FlatCAMGrbEditor.py:2850 flatcamGUI/PreferencesUI.py:3981
+msgid "Direction"
+msgstr "Direction"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1740
+#: flatcamEditors/FlatCAMExcEditor.py:1955
+#: flatcamEditors/FlatCAMGrbEditor.py:2803 flatcamGUI/PreferencesUI.py:2718
+#: flatcamGUI/PreferencesUI.py:3889 flatcamGUI/PreferencesUI.py:4037
+msgid ""
+"Direction on which the linear array is oriented:\n"
+"- 'X' - horizontal axis \n"
+"- 'Y' - vertical axis or \n"
+"- 'Angle' - a custom angle for the array inclination"
+msgstr ""
+"Direction on which the linear array is oriented:\n"
+"- 'X' - horizontal axis \n"
+"- 'Y' - vertical axis or \n"
+"- 'Angle' - a custom angle for the array inclination"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1747
+#: flatcamEditors/FlatCAMExcEditor.py:1869
+#: flatcamEditors/FlatCAMExcEditor.py:1962
+#: flatcamEditors/FlatCAMGrbEditor.py:2810 flatcamGUI/PreferencesUI.py:2724
+#: flatcamGUI/PreferencesUI.py:3895 flatcamGUI/PreferencesUI.py:3990
+#: flatcamGUI/PreferencesUI.py:4043 flatcamGUI/PreferencesUI.py:6341
+#: flatcamTools/ToolFilm.py:256
+msgid "X"
+msgstr "X"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1748
+#: flatcamEditors/FlatCAMExcEditor.py:1870
+#: flatcamEditors/FlatCAMExcEditor.py:1963
+#: flatcamEditors/FlatCAMGrbEditor.py:2811 flatcamGUI/PreferencesUI.py:2725
+#: flatcamGUI/PreferencesUI.py:3896 flatcamGUI/PreferencesUI.py:3991
+#: flatcamGUI/PreferencesUI.py:4044 flatcamGUI/PreferencesUI.py:6342
+#: flatcamTools/ToolFilm.py:257
+msgid "Y"
+msgstr "Y"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1749
+#: flatcamEditors/FlatCAMExcEditor.py:1766
+#: flatcamEditors/FlatCAMExcEditor.py:1800
+#: flatcamEditors/FlatCAMExcEditor.py:1871
+#: flatcamEditors/FlatCAMExcEditor.py:1875
+#: flatcamEditors/FlatCAMExcEditor.py:1964
+#: flatcamEditors/FlatCAMExcEditor.py:1982
+#: flatcamEditors/FlatCAMExcEditor.py:2016
+#: flatcamEditors/FlatCAMGrbEditor.py:2812
+#: flatcamEditors/FlatCAMGrbEditor.py:2829
+#: flatcamEditors/FlatCAMGrbEditor.py:2865 flatcamGUI/PreferencesUI.py:2726
+#: flatcamGUI/PreferencesUI.py:2744 flatcamGUI/PreferencesUI.py:3897
+#: flatcamGUI/PreferencesUI.py:3916 flatcamGUI/PreferencesUI.py:3992
+#: flatcamGUI/PreferencesUI.py:3997 flatcamGUI/PreferencesUI.py:4045
+#: flatcamGUI/PreferencesUI.py:4066 flatcamGUI/PreferencesUI.py:6733
+#: flatcamTools/ToolDistance.py:120 flatcamTools/ToolDistanceMin.py:69
+#: flatcamTools/ToolTransform.py:60
+msgid "Angle"
+msgstr "Angle"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1753
+#: flatcamEditors/FlatCAMExcEditor.py:1968
+#: flatcamEditors/FlatCAMGrbEditor.py:2816 flatcamGUI/PreferencesUI.py:2732
+#: flatcamGUI/PreferencesUI.py:3903 flatcamGUI/PreferencesUI.py:4051
+msgid "Pitch"
+msgstr "Pitch"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1755
+#: flatcamEditors/FlatCAMExcEditor.py:1970
+#: flatcamEditors/FlatCAMGrbEditor.py:2818 flatcamGUI/PreferencesUI.py:2734
+#: flatcamGUI/PreferencesUI.py:3905 flatcamGUI/PreferencesUI.py:4053
+msgid "Pitch = Distance between elements of the array."
+msgstr "Pitch = Distance between elements of the array."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1768
+#: flatcamEditors/FlatCAMExcEditor.py:1984
+msgid ""
+"Angle at which the linear array is placed.\n"
+"The precision is of max 2 decimals.\n"
+"Min value is: -360 degrees.\n"
+"Max value is:  360.00 degrees."
+msgstr ""
+"Angle at which the linear array is placed.\n"
+"The precision is of max 2 decimals.\n"
+"Min value is: -360 degrees.\n"
+"Max value is:  360.00 degrees."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1789
+#: flatcamEditors/FlatCAMExcEditor.py:2005
+#: flatcamEditors/FlatCAMGrbEditor.py:2852
+msgid ""
+"Direction for circular array.Can be CW = clockwise or CCW = counter "
+"clockwise."
+msgstr ""
+"Direction for circular array.Can be CW = clockwise or CCW = counter "
+"clockwise."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1796
+#: flatcamEditors/FlatCAMExcEditor.py:2012
+#: flatcamEditors/FlatCAMGrbEditor.py:2860 flatcamGUI/PreferencesUI.py:2766
+#: flatcamGUI/PreferencesUI.py:3646 flatcamGUI/PreferencesUI.py:3939
+#: flatcamGUI/PreferencesUI.py:4089 flatcamGUI/PreferencesUI.py:4581
+msgid "CW"
+msgstr "CW"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1797
+#: flatcamEditors/FlatCAMExcEditor.py:2013
+#: flatcamEditors/FlatCAMGrbEditor.py:2861 flatcamGUI/PreferencesUI.py:2767
+#: flatcamGUI/PreferencesUI.py:3647 flatcamGUI/PreferencesUI.py:3940
+#: flatcamGUI/PreferencesUI.py:4090 flatcamGUI/PreferencesUI.py:4582
+msgid "CCW"
+msgstr "CCW"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1801
+#: flatcamEditors/FlatCAMExcEditor.py:2017
+#: flatcamEditors/FlatCAMGrbEditor.py:2867 flatcamGUI/PreferencesUI.py:2746
+#: flatcamGUI/PreferencesUI.py:2775 flatcamGUI/PreferencesUI.py:3918
+#: flatcamGUI/PreferencesUI.py:3948 flatcamGUI/PreferencesUI.py:4068
+#: flatcamGUI/PreferencesUI.py:4098
+msgid "Angle at which each element in circular array is placed."
+msgstr "Angle at which each element in circular array is placed."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1835
+msgid "Slot Parameters"
+msgstr "Slot Parameters"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1837
+msgid ""
+"Parameters for adding a slot (hole with oval shape)\n"
+"either single or as an part of an array."
+msgstr ""
+"Parameters for adding a slot (hole with oval shape)\n"
+"either single or as an part of an array."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1846 flatcamGUI/PreferencesUI.py:3965
+#: flatcamTools/ToolProperties.py:559
+msgid "Length"
+msgstr "Length"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1848 flatcamGUI/PreferencesUI.py:3967
+msgid "Length = The length of the slot."
+msgstr "Length = The length of the slot."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1862 flatcamGUI/PreferencesUI.py:3983
+msgid ""
+"Direction on which the slot is oriented:\n"
+"- 'X' - horizontal axis \n"
+"- 'Y' - vertical axis or \n"
+"- 'Angle' - a custom angle for the slot inclination"
+msgstr ""
+"Direction on which the slot is oriented:\n"
+"- 'X' - horizontal axis \n"
+"- 'Y' - vertical axis or \n"
+"- 'Angle' - a custom angle for the slot inclination"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1877
+msgid ""
+"Angle at which the slot is placed.\n"
+"The precision is of max 2 decimals.\n"
+"Min value is: -360 degrees.\n"
+"Max value is:  360.00 degrees."
+msgstr ""
+"Angle at which the slot is placed.\n"
+"The precision is of max 2 decimals.\n"
+"Min value is: -360 degrees.\n"
+"Max value is:  360.00 degrees."
+
+#: flatcamEditors/FlatCAMExcEditor.py:1910
+msgid "Slot Array Parameters"
+msgstr "Slot Array Parameters"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1912
+msgid "Parameters for the array of slots (linear or circular array)"
+msgstr "Parameters for the array of slots (linear or circular array)"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1921
+msgid ""
+"Select the type of slot array to create.\n"
+"It can be Linear X(Y) or Circular"
+msgstr ""
+"Select the type of slot array to create.\n"
+"It can be Linear X(Y) or Circular"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1933 flatcamGUI/PreferencesUI.py:4022
+msgid "Nr of slots"
+msgstr "Nr of slots"
+
+#: flatcamEditors/FlatCAMExcEditor.py:1934 flatcamGUI/PreferencesUI.py:4024
+msgid "Specify how many slots to be in the array."
+msgstr "Specify how many slots to be in the array."
+
+#: flatcamEditors/FlatCAMExcEditor.py:2452
+#: flatcamObjects/FlatCAMExcellon.py:410
+msgid "Total Drills"
+msgstr "Total Drills"
+
+#: flatcamEditors/FlatCAMExcEditor.py:2484
+#: flatcamObjects/FlatCAMExcellon.py:441
+msgid "Total Slots"
+msgstr "Total Slots"
+
+#: flatcamEditors/FlatCAMExcEditor.py:2559
+#: flatcamEditors/FlatCAMGeoEditor.py:1076
+#: flatcamEditors/FlatCAMGeoEditor.py:1117
+#: flatcamEditors/FlatCAMGeoEditor.py:1145
+#: flatcamEditors/FlatCAMGeoEditor.py:1173
+#: flatcamEditors/FlatCAMGeoEditor.py:1217
+#: flatcamEditors/FlatCAMGeoEditor.py:1252
+#: flatcamEditors/FlatCAMGeoEditor.py:1280
+#: flatcamObjects/FlatCAMGeometry.py:571 flatcamObjects/FlatCAMGeometry.py:1005
+#: flatcamObjects/FlatCAMGeometry.py:1726
+#: flatcamObjects/FlatCAMGeometry.py:2370 flatcamTools/ToolNCC.py:1493
+#: flatcamTools/ToolPaint.py:1244 flatcamTools/ToolPaint.py:1415
+#: flatcamTools/ToolSolderPaste.py:883 flatcamTools/ToolSolderPaste.py:956
+msgid "Wrong value format entered, use a number."
+msgstr "Wrong value format entered, use a number."
+
+#: flatcamEditors/FlatCAMExcEditor.py:2570
+msgid ""
+"Tool already in the original or actual tool list.\n"
+"Save and reedit Excellon if you need to add this tool. "
+msgstr ""
+"Tool already in the original or actual tool list.\n"
+"Save and reedit Excellon if you need to add this tool. "
+
+#: flatcamEditors/FlatCAMExcEditor.py:2579 flatcamGUI/FlatCAMGUI.py:4009
+msgid "Added new tool with dia"
+msgstr "Added new tool with dia"
+
+#: flatcamEditors/FlatCAMExcEditor.py:2612
+msgid "Select a tool in Tool Table"
+msgstr "Select a tool in Tool Table"
+
+#: flatcamEditors/FlatCAMExcEditor.py:2642
+msgid "Deleted tool with diameter"
+msgstr "Deleted tool with diameter"
+
+#: flatcamEditors/FlatCAMExcEditor.py:2790
+msgid "Done. Tool edit completed."
+msgstr "Done. Tool edit completed."
+
+#: flatcamEditors/FlatCAMExcEditor.py:3352
+msgid "There are no Tools definitions in the file. Aborting Excellon creation."
+msgstr ""
+"There are no Tools definitions in the file. Aborting Excellon creation."
+
+#: flatcamEditors/FlatCAMExcEditor.py:3356
+msgid "An internal error has ocurred. See Shell.\n"
+msgstr "An internal error has ocurred. See Shell.\n"
+
+#: flatcamEditors/FlatCAMExcEditor.py:3361
+msgid "Creating Excellon."
+msgstr "Creating Excellon."
+
+#: flatcamEditors/FlatCAMExcEditor.py:3373
+msgid "Excellon editing finished."
+msgstr "Excellon editing finished."
+
+#: flatcamEditors/FlatCAMExcEditor.py:3390
+msgid "Cancelled. There is no Tool/Drill selected"
+msgstr "Cancelled. There is no Tool/Drill selected"
+
+#: flatcamEditors/FlatCAMExcEditor.py:4003
+msgid "Done. Drill(s) deleted."
+msgstr "Done. Drill(s) deleted."
+
+#: flatcamEditors/FlatCAMExcEditor.py:4076
+#: flatcamEditors/FlatCAMExcEditor.py:4086
+#: flatcamEditors/FlatCAMGrbEditor.py:4897
+msgid "Click on the circular array Center position"
+msgstr "Click on the circular array Center position"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:85
+msgid "Buffer distance:"
+msgstr "Buffer distance:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:86
+msgid "Buffer corner:"
+msgstr "Buffer corner:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:88
+#| msgid ""
+#| "There are 3 types of corners:\n"
+#| " - 'Round': the corner is rounded for exterior buffer.\n"
+#| " - 'Square:' the corner is met in a sharp angle for exterior buffer.\n"
+#| " - 'Beveled:' the corner is a line that directly connects the features "
+#| "meeting in the corner"
+msgid ""
+"There are 3 types of corners:\n"
+" - 'Round': the corner is rounded for exterior buffer.\n"
+" - 'Square': the corner is met in a sharp angle for exterior buffer.\n"
+" - 'Beveled': the corner is a line that directly connects the features "
+"meeting in the corner"
+msgstr ""
+"There are 3 types of corners:\n"
+" - 'Round': the corner is rounded for exterior buffer.\n"
+" - 'Square': the corner is met in a sharp angle for exterior buffer.\n"
+" - 'Beveled': the corner is a line that directly connects the features "
+"meeting in the corner"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:94
+#: flatcamEditors/FlatCAMGrbEditor.py:2628
+msgid "Round"
+msgstr "Round"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:95
+#: flatcamEditors/FlatCAMGrbEditor.py:2629 flatcamGUI/PreferencesUI.py:5606
+#: flatcamGUI/PreferencesUI.py:6130 flatcamGUI/PreferencesUI.py:7564
+#: flatcamGUI/PreferencesUI.py:8167 flatcamGUI/PreferencesUI.py:8274
+#: flatcamGUI/PreferencesUI.py:8379 flatcamGUI/PreferencesUI.py:8488
+#: flatcamTools/ToolExtractDrills.py:94 flatcamTools/ToolExtractDrills.py:227
+#: flatcamTools/ToolNCC.py:583 flatcamTools/ToolPaint.py:527
+#: flatcamTools/ToolPunchGerber.py:105 flatcamTools/ToolPunchGerber.py:255
+#: flatcamTools/ToolQRCode.py:198
+msgid "Square"
+msgstr "Square"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:96
+#: flatcamEditors/FlatCAMGrbEditor.py:2630
+msgid "Beveled"
+msgstr "Beveled"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:103
+msgid "Buffer Interior"
+msgstr "Buffer Interior"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:105
+msgid "Buffer Exterior"
+msgstr "Buffer Exterior"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:111
+msgid "Full Buffer"
+msgstr "Full Buffer"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:132
+#: flatcamEditors/FlatCAMGeoEditor.py:3017 flatcamGUI/FlatCAMGUI.py:1913
+#: flatcamGUI/PreferencesUI.py:2786
+msgid "Buffer Tool"
+msgstr "Buffer Tool"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:144
+#: flatcamEditors/FlatCAMGeoEditor.py:161
+#: flatcamEditors/FlatCAMGeoEditor.py:178
+#: flatcamEditors/FlatCAMGeoEditor.py:3036
+#: flatcamEditors/FlatCAMGeoEditor.py:3064
+#: flatcamEditors/FlatCAMGeoEditor.py:3092
+#: flatcamEditors/FlatCAMGrbEditor.py:4950
+msgid "Buffer distance value is missing or wrong format. Add it and retry."
+msgstr "Buffer distance value is missing or wrong format. Add it and retry."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:242
+msgid "Font"
+msgstr "Font"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:323 flatcamGUI/FlatCAMGUI.py:2193
+msgid "Text"
+msgstr "Text"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:349
+msgid "Text Tool"
+msgstr "Text Tool"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:405 flatcamGUI/FlatCAMGUI.py:496
+#: flatcamGUI/FlatCAMGUI.py:1143 flatcamGUI/ObjectUI.py:817
+#: flatcamGUI/ObjectUI.py:1661 flatcamObjects/FlatCAMExcellon.py:742
+#: flatcamObjects/FlatCAMExcellon.py:1084 flatcamObjects/FlatCAMGeometry.py:731
+#: flatcamTools/ToolNCC.py:331 flatcamTools/ToolNCC.py:797
+#: flatcamTools/ToolPaint.py:314 flatcamTools/ToolPaint.py:767
+msgid "Tool"
+msgstr "Tool"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:439 flatcamGUI/ObjectUI.py:363
+#: flatcamGUI/PreferencesUI.py:2205
+msgid "Tool dia"
+msgstr "Tool dia"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:441
+msgid "Diameter of the tool to be used in the operation."
+msgstr "Diameter of the tool to be used in the operation."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:487
+msgid ""
+"Algorithm to paint the polygons:\n"
+"- Standard: Fixed step inwards.\n"
+"- Seed-based: Outwards from seed.\n"
+"- Line-based: Parallel lines."
+msgstr ""
+"Algorithm to paint the polygons:\n"
+"- Standard: Fixed step inwards.\n"
+"- Seed-based: Outwards from seed.\n"
+"- Line-based: Parallel lines."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:506
+msgid "Connect:"
+msgstr "Connect:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:516
+msgid "Contour:"
+msgstr "Contour:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:529 flatcamGUI/FlatCAMGUI.py:2197
+msgid "Paint"
+msgstr "Paint"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:547 flatcamGUI/FlatCAMGUI.py:909
+#: flatcamGUI/FlatCAMGUI.py:2588 flatcamGUI/ObjectUI.py:2058
+#: flatcamTools/ToolPaint.py:43 flatcamTools/ToolPaint.py:738
+msgid "Paint Tool"
+msgstr "Paint Tool"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:583
+#: flatcamEditors/FlatCAMGeoEditor.py:1055
+#: flatcamEditors/FlatCAMGeoEditor.py:3024
+#: flatcamEditors/FlatCAMGeoEditor.py:3052
+#: flatcamEditors/FlatCAMGeoEditor.py:3080
+#: flatcamEditors/FlatCAMGeoEditor.py:4502
+#: flatcamEditors/FlatCAMGrbEditor.py:5601
+msgid "Cancelled. No shape selected."
+msgstr "Cancelled. No shape selected."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:596
+#: flatcamEditors/FlatCAMGeoEditor.py:3042
+#: flatcamEditors/FlatCAMGeoEditor.py:3070
+#: flatcamEditors/FlatCAMGeoEditor.py:3098 flatcamGUI/PreferencesUI.py:4149
+#: flatcamTools/ToolProperties.py:117 flatcamTools/ToolProperties.py:162
+msgid "Tools"
+msgstr "Tools"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:607
+#: flatcamEditors/FlatCAMGeoEditor.py:991
+#: flatcamEditors/FlatCAMGrbEditor.py:5140
+#: flatcamEditors/FlatCAMGrbEditor.py:5537 flatcamGUI/FlatCAMGUI.py:930
+#: flatcamGUI/FlatCAMGUI.py:2609 flatcamTools/ToolTransform.py:460
+msgid "Transform Tool"
+msgstr "Transform Tool"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:608
+#: flatcamEditors/FlatCAMGeoEditor.py:673
+#: flatcamEditors/FlatCAMGrbEditor.py:5141
+#: flatcamEditors/FlatCAMGrbEditor.py:5206 flatcamGUI/PreferencesUI.py:6725
+#: flatcamTools/ToolTransform.py:24 flatcamTools/ToolTransform.py:466
+msgid "Rotate"
+msgstr "Rotate"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:609
+#: flatcamEditors/FlatCAMGrbEditor.py:5142 flatcamTools/ToolTransform.py:25
+msgid "Skew/Shear"
+msgstr "Skew/Shear"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:610
+#: flatcamEditors/FlatCAMGrbEditor.py:2677
+#: flatcamEditors/FlatCAMGrbEditor.py:5143 flatcamGUI/FlatCAMGUI.py:1048
+#: flatcamGUI/FlatCAMGUI.py:2125 flatcamGUI/FlatCAMGUI.py:2240
+#: flatcamGUI/FlatCAMGUI.py:2723 flatcamGUI/ObjectUI.py:124
+#: flatcamGUI/PreferencesUI.py:6775 flatcamTools/ToolTransform.py:26
+msgid "Scale"
+msgstr "Scale"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:611
+#: flatcamEditors/FlatCAMGrbEditor.py:5144 flatcamTools/ToolTransform.py:27
+msgid "Mirror (Flip)"
+msgstr "Mirror (Flip)"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:625
+#: flatcamEditors/FlatCAMGrbEditor.py:5158 flatcamGUI/FlatCAMGUI.py:841
+#: flatcamGUI/FlatCAMGUI.py:2524
+msgid "Editor"
+msgstr "Editor"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:657
+#: flatcamEditors/FlatCAMGrbEditor.py:5190
+msgid "Angle:"
+msgstr "Angle:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:659
+#: flatcamEditors/FlatCAMGrbEditor.py:5192 flatcamGUI/PreferencesUI.py:6735
+#: flatcamTools/ToolTransform.py:62
+msgid ""
+"Angle for Rotation action, in degrees.\n"
+"Float number between -360 and 359.\n"
+"Positive numbers for CW motion.\n"
+"Negative numbers for CCW motion."
+msgstr ""
+"Angle for Rotation action, in degrees.\n"
+"Float number between -360 and 359.\n"
+"Positive numbers for CW motion.\n"
+"Negative numbers for CCW motion."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:675
+#: flatcamEditors/FlatCAMGrbEditor.py:5208
+msgid ""
+"Rotate the selected shape(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected shapes."
+msgstr ""
+"Rotate the selected shape(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected shapes."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:698
+#: flatcamEditors/FlatCAMGrbEditor.py:5231
+msgid "Angle X:"
+msgstr "Angle X:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:700
+#: flatcamEditors/FlatCAMGeoEditor.py:720
+#: flatcamEditors/FlatCAMGrbEditor.py:5233
+#: flatcamEditors/FlatCAMGrbEditor.py:5253 flatcamGUI/PreferencesUI.py:6754
+#: flatcamGUI/PreferencesUI.py:6768 flatcamTools/ToolCalibration.py:505
+#: flatcamTools/ToolCalibration.py:518
+msgid ""
+"Angle for Skew action, in degrees.\n"
+"Float number between -360 and 359."
+msgstr ""
+"Angle for Skew action, in degrees.\n"
+"Float number between -360 and 359."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:711
+#: flatcamEditors/FlatCAMGrbEditor.py:5244 flatcamTools/ToolTransform.py:467
+msgid "Skew X"
+msgstr "Skew X"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:713
+#: flatcamEditors/FlatCAMGeoEditor.py:733
+#: flatcamEditors/FlatCAMGrbEditor.py:5246
+#: flatcamEditors/FlatCAMGrbEditor.py:5266
+msgid ""
+"Skew/shear the selected shape(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected shapes."
+msgstr ""
+"Skew/shear the selected shape(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected shapes."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:718
+#: flatcamEditors/FlatCAMGrbEditor.py:5251
+msgid "Angle Y:"
+msgstr "Angle Y:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:731
+#: flatcamEditors/FlatCAMGrbEditor.py:5264 flatcamTools/ToolTransform.py:468
+msgid "Skew Y"
+msgstr "Skew Y"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:759
+#: flatcamEditors/FlatCAMGrbEditor.py:5292
+msgid "Factor X:"
+msgstr "Factor X:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:761
+#: flatcamEditors/FlatCAMGrbEditor.py:5294 flatcamTools/ToolCalibration.py:469
+msgid "Factor for Scale action over X axis."
+msgstr "Factor for Scale action over X axis."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:771
+#: flatcamEditors/FlatCAMGrbEditor.py:5304 flatcamTools/ToolTransform.py:469
+msgid "Scale X"
+msgstr "Scale X"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:773
+#: flatcamEditors/FlatCAMGeoEditor.py:792
+#: flatcamEditors/FlatCAMGrbEditor.py:5306
+#: flatcamEditors/FlatCAMGrbEditor.py:5325
+msgid ""
+"Scale the selected shape(s).\n"
+"The point of reference depends on \n"
+"the Scale reference checkbox state."
+msgstr ""
+"Scale the selected shape(s).\n"
+"The point of reference depends on \n"
+"the Scale reference checkbox state."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:778
+#: flatcamEditors/FlatCAMGrbEditor.py:5311
+msgid "Factor Y:"
+msgstr "Factor Y:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:780
+#: flatcamEditors/FlatCAMGrbEditor.py:5313 flatcamTools/ToolCalibration.py:481
+msgid "Factor for Scale action over Y axis."
+msgstr "Factor for Scale action over Y axis."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:790
+#: flatcamEditors/FlatCAMGrbEditor.py:5323 flatcamTools/ToolTransform.py:470
+msgid "Scale Y"
+msgstr "Scale Y"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:799
+#: flatcamEditors/FlatCAMGrbEditor.py:5332 flatcamGUI/PreferencesUI.py:6804
+#: flatcamTools/ToolTransform.py:189
+msgid "Link"
+msgstr "Link"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:801
+#: flatcamEditors/FlatCAMGrbEditor.py:5334
+msgid ""
+"Scale the selected shape(s)\n"
+"using the Scale Factor X for both axis."
+msgstr ""
+"Scale the selected shape(s)\n"
+"using the Scale Factor X for both axis."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:807
+#: flatcamEditors/FlatCAMGrbEditor.py:5340 flatcamGUI/PreferencesUI.py:6812
+#: flatcamTools/ToolTransform.py:196
+msgid "Scale Reference"
+msgstr "Scale Reference"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:809
+#: flatcamEditors/FlatCAMGrbEditor.py:5342
+msgid ""
+"Scale the selected shape(s)\n"
+"using the origin reference when checked,\n"
+"and the center of the biggest bounding box\n"
+"of the selected shapes when unchecked."
+msgstr ""
+"Scale the selected shape(s)\n"
+"using the origin reference when checked,\n"
+"and the center of the biggest bounding box\n"
+"of the selected shapes when unchecked."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:837
+#: flatcamEditors/FlatCAMGrbEditor.py:5371
+msgid "Value X:"
+msgstr "Value X:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:839
+#: flatcamEditors/FlatCAMGrbEditor.py:5373
+msgid "Value for Offset action on X axis."
+msgstr "Value for Offset action on X axis."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:849
+#: flatcamEditors/FlatCAMGrbEditor.py:5383 flatcamTools/ToolTransform.py:473
+msgid "Offset X"
+msgstr "Offset X"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:851
+#: flatcamEditors/FlatCAMGeoEditor.py:871
+#: flatcamEditors/FlatCAMGrbEditor.py:5385
+#: flatcamEditors/FlatCAMGrbEditor.py:5405
+msgid ""
+"Offset the selected shape(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected shapes.\n"
+msgstr ""
+"Offset the selected shape(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected shapes.\n"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:857
+#: flatcamEditors/FlatCAMGrbEditor.py:5391
+msgid "Value Y:"
+msgstr "Value Y:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:859
+#: flatcamEditors/FlatCAMGrbEditor.py:5393
+msgid "Value for Offset action on Y axis."
+msgstr "Value for Offset action on Y axis."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:869
+#: flatcamEditors/FlatCAMGrbEditor.py:5403 flatcamTools/ToolTransform.py:474
+msgid "Offset Y"
+msgstr "Offset Y"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:900
+#: flatcamEditors/FlatCAMGrbEditor.py:5434 flatcamTools/ToolTransform.py:475
+msgid "Flip on X"
+msgstr "Flip on X"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:902
+#: flatcamEditors/FlatCAMGeoEditor.py:909
+#: flatcamEditors/FlatCAMGrbEditor.py:5436
+#: flatcamEditors/FlatCAMGrbEditor.py:5443
+msgid ""
+"Flip the selected shape(s) over the X axis.\n"
+"Does not create a new shape."
+msgstr ""
+"Flip the selected shape(s) over the X axis.\n"
+"Does not create a new shape."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:907
+#: flatcamEditors/FlatCAMGrbEditor.py:5441 flatcamTools/ToolTransform.py:476
+msgid "Flip on Y"
+msgstr "Flip on Y"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:915
+#: flatcamEditors/FlatCAMGrbEditor.py:5449
+msgid "Ref Pt"
+msgstr "Ref Pt"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:917
+#: flatcamEditors/FlatCAMGrbEditor.py:5451
+msgid ""
+"Flip the selected shape(s)\n"
+"around the point in Point Entry Field.\n"
+"\n"
+"The point coordinates can be captured by\n"
+"left click on canvas together with pressing\n"
+"SHIFT key. \n"
+"Then click Add button to insert coordinates.\n"
+"Or enter the coords in format (x, y) in the\n"
+"Point Entry field and click Flip on X(Y)"
+msgstr ""
+"Flip the selected shape(s)\n"
+"around the point in Point Entry Field.\n"
+"\n"
+"The point coordinates can be captured by\n"
+"left click on canvas together with pressing\n"
+"SHIFT key. \n"
+"Then click Add button to insert coordinates.\n"
+"Or enter the coords in format (x, y) in the\n"
+"Point Entry field and click Flip on X(Y)"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:929
+#: flatcamEditors/FlatCAMGrbEditor.py:5463
+msgid "Point:"
+msgstr "Point:"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:931
+#: flatcamEditors/FlatCAMGrbEditor.py:5465 flatcamTools/ToolTransform.py:299
+msgid ""
+"Coordinates in format (x, y) used as reference for mirroring.\n"
+"The 'x' in (x, y) will be used when using Flip on X and\n"
+"the 'y' in (x, y) will be used when using Flip on Y."
+msgstr ""
+"Coordinates in format (x, y) used as reference for mirroring.\n"
+"The 'x' in (x, y) will be used when using Flip on X and\n"
+"the 'y' in (x, y) will be used when using Flip on Y."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:941
+#: flatcamEditors/FlatCAMGrbEditor.py:5477 flatcamTools/ToolTransform.py:309
+msgid ""
+"The point coordinates can be captured by\n"
+"left click on canvas together with pressing\n"
+"SHIFT key. Then click Add button to insert."
+msgstr ""
+"The point coordinates can be captured by\n"
+"left click on canvas together with pressing\n"
+"SHIFT key. Then click Add button to insert."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1304
+#: flatcamEditors/FlatCAMGrbEditor.py:5785
+msgid "No shape selected. Please Select a shape to rotate!"
+msgstr "No shape selected. Please Select a shape to rotate!"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1307
+#: flatcamEditors/FlatCAMGrbEditor.py:5788 flatcamTools/ToolTransform.py:679
+msgid "Appying Rotate"
+msgstr "Appying Rotate"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1333
+#: flatcamEditors/FlatCAMGrbEditor.py:5820
+msgid "Done. Rotate completed."
+msgstr "Done. Rotate completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1335
+msgid "Rotation action was not executed"
+msgstr "Rotation action was not executed"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1354
+#: flatcamEditors/FlatCAMGrbEditor.py:5839
+msgid "No shape selected. Please Select a shape to flip!"
+msgstr "No shape selected. Please Select a shape to flip!"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1357
+#: flatcamEditors/FlatCAMGrbEditor.py:5842 flatcamTools/ToolTransform.py:728
+msgid "Applying Flip"
+msgstr "Applying Flip"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1386
+#: flatcamEditors/FlatCAMGrbEditor.py:5880 flatcamTools/ToolTransform.py:769
+msgid "Flip on the Y axis done"
+msgstr "Flip on the Y axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1390
+#: flatcamEditors/FlatCAMGrbEditor.py:5889 flatcamTools/ToolTransform.py:778
+msgid "Flip on the X axis done"
+msgstr "Flip on the X axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1398
+msgid "Flip action was not executed"
+msgstr "Flip action was not executed"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1416
+#: flatcamEditors/FlatCAMGrbEditor.py:5909
+msgid "No shape selected. Please Select a shape to shear/skew!"
+msgstr "No shape selected. Please Select a shape to shear/skew!"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1419
+#: flatcamEditors/FlatCAMGrbEditor.py:5912 flatcamTools/ToolTransform.py:801
+msgid "Applying Skew"
+msgstr "Applying Skew"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1442
+#: flatcamEditors/FlatCAMGrbEditor.py:5946
+msgid "Skew on the X axis done"
+msgstr "Skew on the X axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1444
+#: flatcamEditors/FlatCAMGrbEditor.py:5948
+msgid "Skew on the Y axis done"
+msgstr "Skew on the Y axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1447
+msgid "Skew action was not executed"
+msgstr "Skew action was not executed"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1469
+#: flatcamEditors/FlatCAMGrbEditor.py:5970
+msgid "No shape selected. Please Select a shape to scale!"
+msgstr "No shape selected. Please Select a shape to scale!"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1472
+#: flatcamEditors/FlatCAMGrbEditor.py:5973 flatcamTools/ToolTransform.py:847
+msgid "Applying Scale"
+msgstr "Applying Scale"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1504
+#: flatcamEditors/FlatCAMGrbEditor.py:6010
+msgid "Scale on the X axis done"
+msgstr "Scale on the X axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1506
+#: flatcamEditors/FlatCAMGrbEditor.py:6012
+msgid "Scale on the Y axis done"
+msgstr "Scale on the Y axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1508
+msgid "Scale action was not executed"
+msgstr "Scale action was not executed"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1523
+#: flatcamEditors/FlatCAMGrbEditor.py:6029
+msgid "No shape selected. Please Select a shape to offset!"
+msgstr "No shape selected. Please Select a shape to offset!"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1526
+#: flatcamEditors/FlatCAMGrbEditor.py:6032 flatcamTools/ToolTransform.py:897
+msgid "Applying Offset"
+msgstr "Applying Offset"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1536
+#: flatcamEditors/FlatCAMGrbEditor.py:6053
+msgid "Offset on the X axis done"
+msgstr "Offset on the X axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1538
+#: flatcamEditors/FlatCAMGrbEditor.py:6055
+msgid "Offset on the Y axis done"
+msgstr "Offset on the Y axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1541
+msgid "Offset action was not executed"
+msgstr "Offset action was not executed"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1545
+#: flatcamEditors/FlatCAMGrbEditor.py:6062
+msgid "Rotate ..."
+msgstr "Rotate ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1546
+#: flatcamEditors/FlatCAMGeoEditor.py:1601
+#: flatcamEditors/FlatCAMGeoEditor.py:1618
+#: flatcamEditors/FlatCAMGrbEditor.py:6063
+#: flatcamEditors/FlatCAMGrbEditor.py:6112
+#: flatcamEditors/FlatCAMGrbEditor.py:6127
+msgid "Enter an Angle Value (degrees)"
+msgstr "Enter an Angle Value (degrees)"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1555
+#: flatcamEditors/FlatCAMGrbEditor.py:6071
+msgid "Geometry shape rotate done"
+msgstr "Geometry shape rotate done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1559
+#: flatcamEditors/FlatCAMGrbEditor.py:6074
+msgid "Geometry shape rotate cancelled"
+msgstr "Geometry shape rotate cancelled"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1564
+#: flatcamEditors/FlatCAMGrbEditor.py:6079
+msgid "Offset on X axis ..."
+msgstr "Offset on X axis ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1565
+#: flatcamEditors/FlatCAMGeoEditor.py:1584
+#: flatcamEditors/FlatCAMGrbEditor.py:6080
+#: flatcamEditors/FlatCAMGrbEditor.py:6097
+msgid "Enter a distance Value"
+msgstr "Enter a distance Value"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1574
+#: flatcamEditors/FlatCAMGrbEditor.py:6088
+msgid "Geometry shape offset on X axis done"
+msgstr "Geometry shape offset on X axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1578
+#: flatcamEditors/FlatCAMGrbEditor.py:6091
+msgid "Geometry shape offset X cancelled"
+msgstr "Geometry shape offset X cancelled"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1583
+#: flatcamEditors/FlatCAMGrbEditor.py:6096
+msgid "Offset on Y axis ..."
+msgstr "Offset on Y axis ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1593
+#: flatcamEditors/FlatCAMGrbEditor.py:6105
+msgid "Geometry shape offset on Y axis done"
+msgstr "Geometry shape offset on Y axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1597
+msgid "Geometry shape offset on Y axis canceled"
+msgstr "Geometry shape offset on Y axis canceled"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1600
+#: flatcamEditors/FlatCAMGrbEditor.py:6111
+msgid "Skew on X axis ..."
+msgstr "Skew on X axis ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1610
+#: flatcamEditors/FlatCAMGrbEditor.py:6120
+msgid "Geometry shape skew on X axis done"
+msgstr "Geometry shape skew on X axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1614
+msgid "Geometry shape skew on X axis canceled"
+msgstr "Geometry shape skew on X axis canceled"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1617
+#: flatcamEditors/FlatCAMGrbEditor.py:6126
+msgid "Skew on Y axis ..."
+msgstr "Skew on Y axis ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1627
+#: flatcamEditors/FlatCAMGrbEditor.py:6135
+msgid "Geometry shape skew on Y axis done"
+msgstr "Geometry shape skew on Y axis done"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:1631
+msgid "Geometry shape skew on Y axis canceled"
+msgstr "Geometry shape skew on Y axis canceled"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2008
+#: flatcamEditors/FlatCAMGeoEditor.py:2079
+#: flatcamEditors/FlatCAMGrbEditor.py:1435
+#: flatcamEditors/FlatCAMGrbEditor.py:1513
+msgid "Click on Center point ..."
+msgstr "Click on Center point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2021
+#: flatcamEditors/FlatCAMGrbEditor.py:1445
+msgid "Click on Perimeter point to complete ..."
+msgstr "Click on Perimeter point to complete ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2053
+msgid "Done. Adding Circle completed."
+msgstr "Done. Adding Circle completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2107
+#: flatcamEditors/FlatCAMGrbEditor.py:1546
+msgid "Click on Start point ..."
+msgstr "Click on Start point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2109
+#: flatcamEditors/FlatCAMGrbEditor.py:1548
+msgid "Click on Point3 ..."
+msgstr "Click on Point3 ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2111
+#: flatcamEditors/FlatCAMGrbEditor.py:1550
+msgid "Click on Stop point ..."
+msgstr "Click on Stop point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2116
+#: flatcamEditors/FlatCAMGrbEditor.py:1555
+msgid "Click on Stop point to complete ..."
+msgstr "Click on Stop point to complete ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2118
+#: flatcamEditors/FlatCAMGrbEditor.py:1557
+msgid "Click on Point2 to complete ..."
+msgstr "Click on Point2 to complete ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2120
+#: flatcamEditors/FlatCAMGrbEditor.py:1559
+msgid "Click on Center point to complete ..."
+msgstr "Click on Center point to complete ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2132
+#, python-format
+msgid "Direction: %s"
+msgstr "Direction: %s"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2146
+#: flatcamEditors/FlatCAMGrbEditor.py:1585
+msgid "Mode: Start -> Stop -> Center. Click on Start point ..."
+msgstr "Mode: Start -> Stop -> Center. Click on Start point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2149
+#: flatcamEditors/FlatCAMGrbEditor.py:1588
+msgid "Mode: Point1 -> Point3 -> Point2. Click on Point1 ..."
+msgstr "Mode: Point1 -> Point3 -> Point2. Click on Point1 ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2152
+#: flatcamEditors/FlatCAMGrbEditor.py:1591
+msgid "Mode: Center -> Start -> Stop. Click on Center point ..."
+msgstr "Mode: Center -> Start -> Stop. Click on Center point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2293
+msgid "Done. Arc completed."
+msgstr "Done. Arc completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2324
+#: flatcamEditors/FlatCAMGeoEditor.py:2397
+msgid "Click on 1st corner ..."
+msgstr "Click on 1st corner ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2336
+msgid "Click on opposite corner to complete ..."
+msgstr "Click on opposite corner to complete ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2366
+msgid "Done. Rectangle completed."
+msgstr "Done. Rectangle completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2410 flatcamTools/ToolNCC.py:1728
+#: flatcamTools/ToolPaint.py:1623
+msgid "Click on next Point or click right mouse button to complete ..."
+msgstr "Click on next Point or click right mouse button to complete ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2441
+msgid "Done. Polygon completed."
+msgstr "Done. Polygon completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2455
+#: flatcamEditors/FlatCAMGeoEditor.py:2520
+#: flatcamEditors/FlatCAMGrbEditor.py:1111
+#: flatcamEditors/FlatCAMGrbEditor.py:1322
+msgid "Backtracked one point ..."
+msgstr "Backtracked one point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2498
+msgid "Done. Path completed."
+msgstr "Done. Path completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2657
+msgid "No shape selected. Select a shape to explode"
+msgstr "No shape selected. Select a shape to explode"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2690
+msgid "Done. Polygons exploded into lines."
+msgstr "Done. Polygons exploded into lines."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2722
+msgid "MOVE: No shape selected. Select a shape to move"
+msgstr "MOVE: No shape selected. Select a shape to move"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2725
+#: flatcamEditors/FlatCAMGeoEditor.py:2745
+msgid " MOVE: Click on reference point ..."
+msgstr " MOVE: Click on reference point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2730
+msgid " Click on destination point ..."
+msgstr " Click on destination point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2770
+msgid "Done. Geometry(s) Move completed."
+msgstr "Done. Geometry(s) Move completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2903
+msgid "Done. Geometry(s) Copy completed."
+msgstr "Done. Geometry(s) Copy completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2934
+#: flatcamEditors/FlatCAMGrbEditor.py:897
+msgid "Click on 1st point ..."
+msgstr "Click on 1st point ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2958
+msgid ""
+"Font not supported. Only Regular, Bold, Italic and BoldItalic are supported. "
+"Error"
+msgstr ""
+"Font not supported. Only Regular, Bold, Italic and BoldItalic are supported. "
+"Error"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2966
+msgid "No text to add."
+msgstr "No text to add."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:2976
+msgid " Done. Adding Text completed."
+msgstr " Done. Adding Text completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3013
+msgid "Create buffer geometry ..."
+msgstr "Create buffer geometry ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3048
+#: flatcamEditors/FlatCAMGrbEditor.py:4994
+msgid "Done. Buffer Tool completed."
+msgstr "Done. Buffer Tool completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3076
+msgid "Done. Buffer Int Tool completed."
+msgstr "Done. Buffer Int Tool completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3104
+msgid "Done. Buffer Ext Tool completed."
+msgstr "Done. Buffer Ext Tool completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3153
+#: flatcamEditors/FlatCAMGrbEditor.py:2151
+msgid "Select a shape to act as deletion area ..."
+msgstr "Select a shape to act as deletion area ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3155
+#: flatcamEditors/FlatCAMGeoEditor.py:3181
+#: flatcamEditors/FlatCAMGeoEditor.py:3187
+#: flatcamEditors/FlatCAMGrbEditor.py:2153
+msgid "Click to pick-up the erase shape..."
+msgstr "Click to pick-up the erase shape..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3191
+#: flatcamEditors/FlatCAMGrbEditor.py:2212
+msgid "Click to erase ..."
+msgstr "Click to erase ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3220
+#: flatcamEditors/FlatCAMGrbEditor.py:2245
+msgid "Done. Eraser tool action completed."
+msgstr "Done. Eraser tool action completed."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3270
+msgid "Create Paint geometry ..."
+msgstr "Create Paint geometry ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3283
+#: flatcamEditors/FlatCAMGrbEditor.py:2408
+msgid "Shape transformations ..."
+msgstr "Shape transformations ..."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3339 flatcamGUI/PreferencesUI.py:4636
+msgid "Geometry Editor"
+msgstr "Geometry Editor"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3345
+#: flatcamEditors/FlatCAMGrbEditor.py:2486
+#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:262
+#: flatcamGUI/ObjectUI.py:1496 flatcamGUI/ObjectUI.py:2245
+#: flatcamTools/ToolCutOut.py:95
+msgid "Type"
+msgstr "Type"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3345 flatcamGUI/ObjectUI.py:217
+#: flatcamGUI/ObjectUI.py:741 flatcamGUI/ObjectUI.py:1432
+#: flatcamGUI/ObjectUI.py:2154 flatcamGUI/ObjectUI.py:2458
+#: flatcamGUI/ObjectUI.py:2525 flatcamTools/ToolCalibration.py:234
+#: flatcamTools/ToolFiducials.py:73
+msgid "Name"
+msgstr "Name"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3587
+msgid "Ring"
+msgstr "Ring"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3589
+msgid "Line"
+msgstr "Line"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3591 flatcamGUI/FlatCAMGUI.py:2187
+#: flatcamGUI/PreferencesUI.py:5607 flatcamGUI/PreferencesUI.py:6131
+#: flatcamTools/ToolNCC.py:584 flatcamTools/ToolPaint.py:528
+msgid "Polygon"
+msgstr "Polygon"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3593
+msgid "Multi-Line"
+msgstr "Multi-Line"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3595
+msgid "Multi-Polygon"
+msgstr "Multi-Polygon"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:3602
+msgid "Geo Elem"
+msgstr "Geo Elem"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4076
+msgid "Editing MultiGeo Geometry, tool"
+msgstr "Editing MultiGeo Geometry, tool"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4078
+msgid "with diameter"
+msgstr "with diameter"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4509 flatcamGUI/FlatCAMGUI.py:3695
+#: flatcamGUI/FlatCAMGUI.py:3741 flatcamGUI/FlatCAMGUI.py:3759
+#: flatcamGUI/FlatCAMGUI.py:3899 flatcamGUI/FlatCAMGUI.py:3938
+#: flatcamGUI/FlatCAMGUI.py:3950 flatcamGUI/FlatCAMGUI.py:3967
+msgid "Click on target point."
+msgstr "Click on target point."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4823
+#: flatcamEditors/FlatCAMGeoEditor.py:4858
+msgid "A selection of at least 2 geo items is required to do Intersection."
+msgstr "A selection of at least 2 geo items is required to do Intersection."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4944
+#: flatcamEditors/FlatCAMGeoEditor.py:5048
+msgid ""
+"Negative buffer value is not accepted. Use Buffer interior to generate an "
+"'inside' shape"
+msgstr ""
+"Negative buffer value is not accepted. Use Buffer interior to generate an "
+"'inside' shape"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4954
+#: flatcamEditors/FlatCAMGeoEditor.py:5007
+#: flatcamEditors/FlatCAMGeoEditor.py:5057
+msgid "Nothing selected for buffering."
+msgstr "Nothing selected for buffering."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4959
+#: flatcamEditors/FlatCAMGeoEditor.py:5011
+#: flatcamEditors/FlatCAMGeoEditor.py:5062
+msgid "Invalid distance for buffering."
+msgstr "Invalid distance for buffering."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4983
+#: flatcamEditors/FlatCAMGeoEditor.py:5082
+msgid "Failed, the result is empty. Choose a different buffer value."
+msgstr "Failed, the result is empty. Choose a different buffer value."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:4994
+msgid "Full buffer geometry created."
+msgstr "Full buffer geometry created."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5000
+msgid "Negative buffer value is not accepted."
+msgstr "Negative buffer value is not accepted."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5031
+msgid "Failed, the result is empty. Choose a smaller buffer value."
+msgstr "Failed, the result is empty. Choose a smaller buffer value."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5041
+msgid "Interior buffer geometry created."
+msgstr "Interior buffer geometry created."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5092
+msgid "Exterior buffer geometry created."
+msgstr "Exterior buffer geometry created."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5098
+#, python-format
+msgid "Could not do Paint. Overlap value has to be less than 100%%."
+msgstr "Could not do Paint. Overlap value has to be less than 100%%."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5105
+msgid "Nothing selected for painting."
+msgstr "Nothing selected for painting."
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5111
+msgid "Invalid value for"
+msgstr "Invalid value for"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5170
+msgid ""
+"Could not do Paint. Try a different combination of parameters. Or a "
+"different method of Paint"
+msgstr ""
+"Could not do Paint. Try a different combination of parameters. Or a "
+"different method of Paint"
+
+#: flatcamEditors/FlatCAMGeoEditor.py:5181
+msgid "Paint done."
+msgstr "Paint done."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:211
+msgid "To add an Pad first select a aperture in Aperture Table"
+msgstr "To add an Pad first select a aperture in Aperture Table"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:218
+#: flatcamEditors/FlatCAMGrbEditor.py:418
+msgid "Aperture size is zero. It needs to be greater than zero."
+msgstr "Aperture size is zero. It needs to be greater than zero."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:371
+#: flatcamEditors/FlatCAMGrbEditor.py:684
+msgid ""
+"Incompatible aperture type. Select an aperture with type 'C', 'R' or 'O'."
+msgstr ""
+"Incompatible aperture type. Select an aperture with type 'C', 'R' or 'O'."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:383
+msgid "Done. Adding Pad completed."
+msgstr "Done. Adding Pad completed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:410
+msgid "To add an Pad Array first select a aperture in Aperture Table"
+msgstr "To add an Pad Array first select a aperture in Aperture Table"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:490
+msgid "Click on the Pad Circular Array Start position"
+msgstr "Click on the Pad Circular Array Start position"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:710
+msgid "Too many Pads for the selected spacing angle."
+msgstr "Too many Pads for the selected spacing angle."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:733
+msgid "Done. Pad Array added."
+msgstr "Done. Pad Array added."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:758
+msgid "Select shape(s) and then click ..."
+msgstr "Select shape(s) and then click ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:770
+msgid "Failed. Nothing selected."
+msgstr "Failed. Nothing selected."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:786
+msgid ""
+"Failed. Poligonize works only on geometries belonging to the same aperture."
+msgstr ""
+"Failed. Poligonize works only on geometries belonging to the same aperture."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:840
+msgid "Done. Poligonize completed."
+msgstr "Done. Poligonize completed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:895
+#: flatcamEditors/FlatCAMGrbEditor.py:1128
+#: flatcamEditors/FlatCAMGrbEditor.py:1152
+msgid "Corner Mode 1: 45 degrees ..."
+msgstr "Corner Mode 1: 45 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:907
+#: flatcamEditors/FlatCAMGrbEditor.py:1237
+msgid "Click on next Point or click Right mouse button to complete ..."
+msgstr "Click on next Point or click Right mouse button to complete ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1116
+#: flatcamEditors/FlatCAMGrbEditor.py:1149
+msgid "Corner Mode 2: Reverse 45 degrees ..."
+msgstr "Corner Mode 2: Reverse 45 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1119
+#: flatcamEditors/FlatCAMGrbEditor.py:1146
+msgid "Corner Mode 3: 90 degrees ..."
+msgstr "Corner Mode 3: 90 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1122
+#: flatcamEditors/FlatCAMGrbEditor.py:1143
+msgid "Corner Mode 4: Reverse 90 degrees ..."
+msgstr "Corner Mode 4: Reverse 90 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1125
+#: flatcamEditors/FlatCAMGrbEditor.py:1140
+msgid "Corner Mode 5: Free angle ..."
+msgstr "Corner Mode 5: Free angle ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1182
+#: flatcamEditors/FlatCAMGrbEditor.py:1358
+#: flatcamEditors/FlatCAMGrbEditor.py:1397
+msgid "Track Mode 1: 45 degrees ..."
+msgstr "Track Mode 1: 45 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1338
+#: flatcamEditors/FlatCAMGrbEditor.py:1392
+msgid "Track Mode 2: Reverse 45 degrees ..."
+msgstr "Track Mode 2: Reverse 45 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1343
+#: flatcamEditors/FlatCAMGrbEditor.py:1387
+msgid "Track Mode 3: 90 degrees ..."
+msgstr "Track Mode 3: 90 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1348
+#: flatcamEditors/FlatCAMGrbEditor.py:1382
+msgid "Track Mode 4: Reverse 90 degrees ..."
+msgstr "Track Mode 4: Reverse 90 degrees ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1353
+#: flatcamEditors/FlatCAMGrbEditor.py:1377
+msgid "Track Mode 5: Free angle ..."
+msgstr "Track Mode 5: Free angle ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1778
+msgid "Scale the selected Gerber apertures ..."
+msgstr "Scale the selected Gerber apertures ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1820
+msgid "Buffer the selected apertures ..."
+msgstr "Buffer the selected apertures ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1862
+msgid "Mark polygon areas in the edited Gerber ..."
+msgstr "Mark polygon areas in the edited Gerber ..."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:1928
+msgid "Nothing selected to move"
+msgstr "Nothing selected to move"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2053
+msgid "Done. Apertures Move completed."
+msgstr "Done. Apertures Move completed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2135
+msgid "Done. Apertures copied."
+msgstr "Done. Apertures copied."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2453 flatcamGUI/FlatCAMGUI.py:2218
+#: flatcamGUI/PreferencesUI.py:2623
+msgid "Gerber Editor"
+msgstr "Gerber Editor"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2473 flatcamGUI/ObjectUI.py:227
+#: flatcamTools/ToolProperties.py:159
+msgid "Apertures"
+msgstr "Apertures"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2475 flatcamGUI/ObjectUI.py:229
+msgid "Apertures Table for the Gerber Object."
+msgstr "Apertures Table for the Gerber Object."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2486
+#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:262
+msgid "Code"
+msgstr "Code"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2486
+#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:262
+#: flatcamGUI/PreferencesUI.py:1184 flatcamGUI/PreferencesUI.py:7776
+#: flatcamGUI/PreferencesUI.py:7805 flatcamGUI/PreferencesUI.py:7907
+#: flatcamTools/ToolCopperThieving.py:261
+#: flatcamTools/ToolCopperThieving.py:301 flatcamTools/ToolFiducials.py:156
+msgid "Size"
+msgstr "Size"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2486
+#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:262
+msgid "Dim"
+msgstr "Dim"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2490 flatcamGUI/ObjectUI.py:266
+msgid "Index"
+msgstr "Index"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2492
+#: flatcamEditors/FlatCAMGrbEditor.py:2521 flatcamGUI/ObjectUI.py:268
+msgid "Aperture Code"
+msgstr "Aperture Code"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2494 flatcamGUI/ObjectUI.py:270
+msgid "Type of aperture: circular, rectangle, macros etc"
+msgstr "Type of aperture: circular, rectangle, macros etc"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2496 flatcamGUI/ObjectUI.py:272
+msgid "Aperture Size:"
+msgstr "Aperture Size:"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2498 flatcamGUI/ObjectUI.py:274
+msgid ""
+"Aperture Dimensions:\n"
+" - (width, height) for R, O type.\n"
+" - (dia, nVertices) for P type"
+msgstr ""
+"Aperture Dimensions:\n"
+" - (width, height) for R, O type.\n"
+" - (dia, nVertices) for P type"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2522 flatcamGUI/PreferencesUI.py:2654
+msgid "Code for the new aperture"
+msgstr "Code for the new aperture"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2531
+msgid "Aperture Size"
+msgstr "Aperture Size"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2533
+msgid ""
+"Size for the new aperture.\n"
+"If aperture type is 'R' or 'O' then\n"
+"this value is automatically\n"
+"calculated as:\n"
+"sqrt(width**2 + height**2)"
+msgstr ""
+"Size for the new aperture.\n"
+"If aperture type is 'R' or 'O' then\n"
+"this value is automatically\n"
+"calculated as:\n"
+"sqrt(width**2 + height**2)"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2547
+msgid "Aperture Type"
+msgstr "Aperture Type"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2549
+msgid ""
+"Select the type of new aperture. Can be:\n"
+"C = circular\n"
+"R = rectangular\n"
+"O = oblong"
+msgstr ""
+"Select the type of new aperture. Can be:\n"
+"C = circular\n"
+"R = rectangular\n"
+"O = oblong"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2560
+msgid "Aperture Dim"
+msgstr "Aperture Dim"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2562
+msgid ""
+"Dimensions for the new aperture.\n"
+"Active only for rectangular apertures (type R).\n"
+"The format is (width, height)"
+msgstr ""
+"Dimensions for the new aperture.\n"
+"Active only for rectangular apertures (type R).\n"
+"The format is (width, height)"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2571
+msgid "Add/Delete Aperture"
+msgstr "Add/Delete Aperture"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2573
+msgid "Add/Delete an aperture in the aperture table"
+msgstr "Add/Delete an aperture in the aperture table"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2582
+msgid "Add a new aperture to the aperture list."
+msgstr "Add a new aperture to the aperture list."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2587
+msgid "Delete a aperture in the aperture list"
+msgstr "Delete a aperture in the aperture list"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2604
+msgid "Buffer Aperture"
+msgstr "Buffer Aperture"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2606
+msgid "Buffer a aperture in the aperture list"
+msgstr "Buffer a aperture in the aperture list"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2619 flatcamGUI/PreferencesUI.py:2790
+msgid "Buffer distance"
+msgstr "Buffer distance"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2620
+msgid "Buffer corner"
+msgstr "Buffer corner"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2622
+#| msgid ""
+#| "There are 3 types of corners:\n"
+#| " - 'Round': the corner is rounded.\n"
+#| " - 'Square:' the corner is met in a sharp angle.\n"
+#| " - 'Beveled:' the corner is a line that directly connects the features "
+#| "meeting in the corner"
+msgid ""
+"There are 3 types of corners:\n"
+" - 'Round': the corner is rounded.\n"
+" - 'Square': the corner is met in a sharp angle.\n"
+" - 'Beveled': the corner is a line that directly connects the features "
+"meeting in the corner"
+msgstr ""
+"There are 3 types of corners:\n"
+" - 'Round': the corner is rounded.\n"
+" - 'Square': the corner is met in a sharp angle.\n"
+" - 'Beveled': the corner is a line that directly connects the features "
+"meeting in the corner"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2637 flatcamGUI/FlatCAMGUI.py:1046
+#: flatcamGUI/FlatCAMGUI.py:2123 flatcamGUI/FlatCAMGUI.py:2195
+#: flatcamGUI/FlatCAMGUI.py:2238 flatcamGUI/FlatCAMGUI.py:2721
+#: flatcamGUI/PreferencesUI.py:6880 flatcamTools/ToolTransform.py:29
+msgid "Buffer"
+msgstr "Buffer"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2652
+msgid "Scale Aperture"
+msgstr "Scale Aperture"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2654
+msgid "Scale a aperture in the aperture list"
+msgstr "Scale a aperture in the aperture list"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2662 flatcamGUI/PreferencesUI.py:2805
+msgid "Scale factor"
+msgstr "Scale factor"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2664
+msgid ""
+"The factor by which to scale the selected aperture.\n"
+"Values can be between 0.0000 and 999.9999"
+msgstr ""
+"The factor by which to scale the selected aperture.\n"
+"Values can be between 0.0000 and 999.9999"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2692
+msgid "Mark polygons"
+msgstr "Mark polygons"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2694
+msgid "Mark the polygon areas."
+msgstr "Mark the polygon areas."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2702
+msgid "Area UPPER threshold"
+msgstr "Area UPPER threshold"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2704
+msgid ""
+"The threshold value, all areas less than this are marked.\n"
+"Can have a value between 0.0000 and 9999.9999"
+msgstr ""
+"The threshold value, all areas less than this are marked.\n"
+"Can have a value between 0.0000 and 9999.9999"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2711
+msgid "Area LOWER threshold"
+msgstr "Area LOWER threshold"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2713
+msgid ""
+"The threshold value, all areas more than this are marked.\n"
+"Can have a value between 0.0000 and 9999.9999"
+msgstr ""
+"The threshold value, all areas more than this are marked.\n"
+"Can have a value between 0.0000 and 9999.9999"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2727
+msgid "Mark"
+msgstr "Mark"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2729
+msgid "Mark the polygons that fit within limits."
+msgstr "Mark the polygons that fit within limits."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2735
+msgid "Delete all the marked polygons."
+msgstr "Delete all the marked polygons."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2741
+msgid "Clear all the markings."
+msgstr "Clear all the markings."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2761 flatcamGUI/FlatCAMGUI.py:1031
+#: flatcamGUI/FlatCAMGUI.py:2123 flatcamGUI/FlatCAMGUI.py:2706
+msgid "Add Pad Array"
+msgstr "Add Pad Array"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2763
+msgid "Add an array of pads (linear or circular array)"
+msgstr "Add an array of pads (linear or circular array)"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2769
+msgid ""
+"Select the type of pads array to create.\n"
+"It can be Linear X(Y) or Circular"
+msgstr ""
+"Select the type of pads array to create.\n"
+"It can be Linear X(Y) or Circular"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2780 flatcamGUI/PreferencesUI.py:2691
+msgid "Nr of pads"
+msgstr "Nr of pads"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2782 flatcamGUI/PreferencesUI.py:2693
+msgid "Specify how many pads to be in the array."
+msgstr "Specify how many pads to be in the array."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:2831
+msgid ""
+"Angle at which the linear array is placed.\n"
+"The precision is of max 2 decimals.\n"
+"Min value is: -359.99 degrees.\n"
+"Max value is:  360.00 degrees."
+msgstr ""
+"Angle at which the linear array is placed.\n"
+"The precision is of max 2 decimals.\n"
+"Min value is: -359.99 degrees.\n"
+"Max value is:  360.00 degrees."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3321
+#: flatcamEditors/FlatCAMGrbEditor.py:3325
+msgid "Aperture code value is missing or wrong format. Add it and retry."
+msgstr "Aperture code value is missing or wrong format. Add it and retry."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3361
+msgid ""
+"Aperture dimensions value is missing or wrong format. Add it in format "
+"(width, height) and retry."
+msgstr ""
+"Aperture dimensions value is missing or wrong format. Add it in format "
+"(width, height) and retry."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3374
+msgid "Aperture size value is missing or wrong format. Add it and retry."
+msgstr "Aperture size value is missing or wrong format. Add it and retry."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3385
+msgid "Aperture already in the aperture table."
+msgstr "Aperture already in the aperture table."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3393
+msgid "Added new aperture with code"
+msgstr "Added new aperture with code"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3422
+msgid " Select an aperture in Aperture Table"
+msgstr " Select an aperture in Aperture Table"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3430
+msgid "Select an aperture in Aperture Table -->"
+msgstr "Select an aperture in Aperture Table -->"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3453
+msgid "Deleted aperture with code"
+msgstr "Deleted aperture with code"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:3950
+msgid "Loading Gerber into Editor"
+msgstr "Loading Gerber into Editor"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4078
+msgid "Setting up the UI"
+msgstr "Setting up the UI"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4079
+msgid "Adding geometry finished. Preparing the GUI"
+msgstr "Adding geometry finished. Preparing the GUI"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4088
+msgid "Finished loading the Gerber object into the editor."
+msgstr "Finished loading the Gerber object into the editor."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4228
+msgid ""
+"There are no Aperture definitions in the file. Aborting Gerber creation."
+msgstr ""
+"There are no Aperture definitions in the file. Aborting Gerber creation."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4238
+msgid "Creating Gerber."
+msgstr "Creating Gerber."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4247
+msgid "Done. Gerber editing finished."
+msgstr "Done. Gerber editing finished."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4265
+msgid "Cancelled. No aperture is selected"
+msgstr "Cancelled. No aperture is selected"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4826
+msgid "Failed. No aperture geometry is selected."
+msgstr "Failed. No aperture geometry is selected."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4835
+#: flatcamEditors/FlatCAMGrbEditor.py:5106
+msgid "Done. Apertures geometry deleted."
+msgstr "Done. Apertures geometry deleted."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4978
+msgid "No aperture to buffer. Select at least one aperture and try again."
+msgstr "No aperture to buffer. Select at least one aperture and try again."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:4990
+msgid "Failed."
+msgstr "Failed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:5009
+msgid "Scale factor value is missing or wrong format. Add it and retry."
+msgstr "Scale factor value is missing or wrong format. Add it and retry."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:5041
+msgid "No aperture to scale. Select at least one aperture and try again."
+msgstr "No aperture to scale. Select at least one aperture and try again."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:5057
+msgid "Done. Scale Tool completed."
+msgstr "Done. Scale Tool completed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:5095
+msgid "Polygons marked."
+msgstr "Polygons marked."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:5098
+msgid "No polygons were marked. None fit within the limits."
+msgstr "No polygons were marked. None fit within the limits."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:5822
+msgid "Rotation action was not executed."
+msgstr "Rotation action was not executed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:5950
+msgid "Skew action was not executed."
+msgstr "Skew action was not executed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:6015
+msgid "Scale action was not executed."
+msgstr "Scale action was not executed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:6058
+msgid "Offset action was not executed."
+msgstr "Offset action was not executed."
+
+#: flatcamEditors/FlatCAMGrbEditor.py:6108
+msgid "Geometry shape offset Y cancelled"
+msgstr "Geometry shape offset Y cancelled"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:6123
+msgid "Geometry shape skew X cancelled"
+msgstr "Geometry shape skew X cancelled"
+
+#: flatcamEditors/FlatCAMGrbEditor.py:6138
+msgid "Geometry shape skew Y cancelled"
+msgstr "Geometry shape skew Y cancelled"
+
+#: flatcamEditors/FlatCAMTextEditor.py:74
+msgid "Print Preview"
+msgstr "Print Preview"
+
+#: flatcamEditors/FlatCAMTextEditor.py:75
+msgid "Open a OS standard Preview Print window."
+msgstr "Open a OS standard Preview Print window."
+
+#: flatcamEditors/FlatCAMTextEditor.py:78
+msgid "Print Code"
+msgstr "Print Code"
+
+#: flatcamEditors/FlatCAMTextEditor.py:79
+msgid "Open a OS standard Print window."
+msgstr "Open a OS standard Print window."
+
+#: flatcamEditors/FlatCAMTextEditor.py:81
+msgid "Find in Code"
+msgstr "Find in Code"
+
+#: flatcamEditors/FlatCAMTextEditor.py:82
+msgid "Will search and highlight in yellow the string in the Find box."
+msgstr "Will search and highlight in yellow the string in the Find box."
+
+#: flatcamEditors/FlatCAMTextEditor.py:86
+msgid "Find box. Enter here the strings to be searched in the text."
+msgstr "Find box. Enter here the strings to be searched in the text."
+
+#: flatcamEditors/FlatCAMTextEditor.py:88
+msgid "Replace With"
+msgstr "Replace With"
+
+#: flatcamEditors/FlatCAMTextEditor.py:89
+msgid ""
+"Will replace the string from the Find box with the one in the Replace box."
+msgstr ""
+"Will replace the string from the Find box with the one in the Replace box."
+
+#: flatcamEditors/FlatCAMTextEditor.py:93
+msgid "String to replace the one in the Find box throughout the text."
+msgstr "String to replace the one in the Find box throughout the text."
+
+#: flatcamEditors/FlatCAMTextEditor.py:95 flatcamGUI/ObjectUI.py:485
+#: flatcamGUI/ObjectUI.py:2138 flatcamGUI/PreferencesUI.py:2250
+#: flatcamGUI/PreferencesUI.py:4712
+msgid "All"
+msgstr "All"
+
+#: flatcamEditors/FlatCAMTextEditor.py:96
+msgid ""
+"When checked it will replace all instances in the 'Find' box\n"
+"with the text in the 'Replace' box.."
+msgstr ""
+"When checked it will replace all instances in the 'Find' box\n"
+"with the text in the 'Replace' box.."
+
+#: flatcamEditors/FlatCAMTextEditor.py:99
+msgid "Copy All"
+msgstr "Copy All"
+
+#: flatcamEditors/FlatCAMTextEditor.py:100
+msgid "Will copy all the text in the Code Editor to the clipboard."
+msgstr "Will copy all the text in the Code Editor to the clipboard."
+
+#: flatcamEditors/FlatCAMTextEditor.py:103
+msgid "Open Code"
+msgstr "Open Code"
+
+#: flatcamEditors/FlatCAMTextEditor.py:104
+msgid "Will open a text file in the editor."
+msgstr "Will open a text file in the editor."
+
+#: flatcamEditors/FlatCAMTextEditor.py:106
+msgid "Save Code"
+msgstr "Save Code"
+
+#: flatcamEditors/FlatCAMTextEditor.py:107
+msgid "Will save the text in the editor into a file."
+msgstr "Will save the text in the editor into a file."
+
+#: flatcamEditors/FlatCAMTextEditor.py:109
+msgid "Run Code"
+msgstr "Run Code"
+
+#: flatcamEditors/FlatCAMTextEditor.py:110
+msgid "Will run the TCL commands found in the text file, one by one."
+msgstr "Will run the TCL commands found in the text file, one by one."
+
+#: flatcamEditors/FlatCAMTextEditor.py:184
+msgid "Open file"
+msgstr "Open file"
+
+#: flatcamEditors/FlatCAMTextEditor.py:215
+#: flatcamEditors/FlatCAMTextEditor.py:220
+msgid "Export Code ..."
+msgstr "Export Code ..."
+
+#: flatcamEditors/FlatCAMTextEditor.py:272 flatcamObjects/FlatCAMCNCJob.py:954
+#: flatcamTools/ToolSolderPaste.py:1530
+msgid "No such file or directory"
+msgstr "No such file or directory"
+
+#: flatcamEditors/FlatCAMTextEditor.py:284 flatcamObjects/FlatCAMCNCJob.py:968
+msgid "Saved to"
+msgstr "Saved to"
+
+#: flatcamEditors/FlatCAMTextEditor.py:334
+msgid "Code Editor content copied to clipboard ..."
+msgstr "Code Editor content copied to clipboard ..."
+
+#: flatcamGUI/FlatCAMGUI.py:66 flatcamGUI/FlatCAMGUI.py:68
+#: flatcamGUI/FlatCAMGUI.py:2148
+msgid "Toggle Panel"
+msgstr "Toggle Panel"
+
+#: flatcamGUI/FlatCAMGUI.py:78
+msgid "File"
+msgstr "File"
+
+#: flatcamGUI/FlatCAMGUI.py:83
+msgid "&New Project ...\tCtrl+N"
+msgstr "&New Project ...\tCtrl+N"
+
+#: flatcamGUI/FlatCAMGUI.py:85
+msgid "Will create a new, blank project"
+msgstr "Will create a new, blank project"
+
+#: flatcamGUI/FlatCAMGUI.py:90
+msgid "&New"
+msgstr "&New"
+
+#: flatcamGUI/FlatCAMGUI.py:94
+msgid "Geometry\tN"
+msgstr "Geometry\tN"
+
+#: flatcamGUI/FlatCAMGUI.py:96
+msgid "Will create a new, empty Geometry Object."
+msgstr "Will create a new, empty Geometry Object."
+
+#: flatcamGUI/FlatCAMGUI.py:99
+msgid "Gerber\tB"
+msgstr "Gerber\tB"
+
+#: flatcamGUI/FlatCAMGUI.py:101
+msgid "Will create a new, empty Gerber Object."
+msgstr "Will create a new, empty Gerber Object."
+
+#: flatcamGUI/FlatCAMGUI.py:104
+msgid "Excellon\tL"
+msgstr "Excellon\tL"
+
+#: flatcamGUI/FlatCAMGUI.py:106
+msgid "Will create a new, empty Excellon Object."
+msgstr "Will create a new, empty Excellon Object."
+
+#: flatcamGUI/FlatCAMGUI.py:111
+msgid "Document\tD"
+msgstr "Document\tD"
+
+#: flatcamGUI/FlatCAMGUI.py:113
+msgid "Will create a new, empty Document Object."
+msgstr "Will create a new, empty Document Object."
+
+#: flatcamGUI/FlatCAMGUI.py:117 flatcamGUI/FlatCAMGUI.py:4327
+#: flatcamTools/ToolPcbWizard.py:62 flatcamTools/ToolPcbWizard.py:69
+msgid "Open"
+msgstr "Open"
+
+#: flatcamGUI/FlatCAMGUI.py:122
+msgid "Open &Project ..."
+msgstr "Open &Project ..."
+
+#: flatcamGUI/FlatCAMGUI.py:128 flatcamGUI/FlatCAMGUI.py:4337
+msgid "Open &Gerber ...\tCtrl+G"
+msgstr "Open &Gerber ...\tCtrl+G"
+
+#: flatcamGUI/FlatCAMGUI.py:133 flatcamGUI/FlatCAMGUI.py:4342
+msgid "Open &Excellon ...\tCtrl+E"
+msgstr "Open &Excellon ...\tCtrl+E"
+
+#: flatcamGUI/FlatCAMGUI.py:138 flatcamGUI/FlatCAMGUI.py:4347
+msgid "Open G-&Code ..."
+msgstr "Open G-&Code ..."
+
+#: flatcamGUI/FlatCAMGUI.py:145
+msgid "Open Config ..."
+msgstr "Open Config ..."
+
+#: flatcamGUI/FlatCAMGUI.py:150
+msgid "Recent projects"
+msgstr "Recent projects"
+
+#: flatcamGUI/FlatCAMGUI.py:152
+msgid "Recent files"
+msgstr "Recent files"
+
+#: flatcamGUI/FlatCAMGUI.py:155 flatcamGUI/FlatCAMGUI.py:738
+#: flatcamGUI/FlatCAMGUI.py:1324
+msgid "Save"
+msgstr "Save"
+
+#: flatcamGUI/FlatCAMGUI.py:159
+msgid "&Save Project ...\tCtrl+S"
+msgstr "&Save Project ...\tCtrl+S"
+
+#: flatcamGUI/FlatCAMGUI.py:164
+msgid "Save Project &As ...\tCtrl+Shift+S"
+msgstr "Save Project &As ...\tCtrl+Shift+S"
+
+#: flatcamGUI/FlatCAMGUI.py:179
+msgid "Scripting"
+msgstr "Scripting"
+
+#: flatcamGUI/FlatCAMGUI.py:183 flatcamGUI/FlatCAMGUI.py:888
+#: flatcamGUI/FlatCAMGUI.py:2567
+msgid "New Script ..."
+msgstr "New Script ..."
+
+#: flatcamGUI/FlatCAMGUI.py:185 flatcamGUI/FlatCAMGUI.py:890
+#: flatcamGUI/FlatCAMGUI.py:2569
+msgid "Open Script ..."
+msgstr "Open Script ..."
+
+#: flatcamGUI/FlatCAMGUI.py:187 flatcamGUI/FlatCAMGUI.py:892
+#: flatcamGUI/FlatCAMGUI.py:2571 flatcamGUI/FlatCAMGUI.py:4316
+msgid "Run Script ..."
+msgstr "Run Script ..."
+
+#: flatcamGUI/FlatCAMGUI.py:189 flatcamGUI/FlatCAMGUI.py:4318
+msgid ""
+"Will run the opened Tcl Script thus\n"
+"enabling the automation of certain\n"
+"functions of FlatCAM."
+msgstr ""
+"Will run the opened Tcl Script thus\n"
+"enabling the automation of certain\n"
+"functions of FlatCAM."
+
+#: flatcamGUI/FlatCAMGUI.py:203
+msgid "Import"
+msgstr "Import"
+
+#: flatcamGUI/FlatCAMGUI.py:205
+msgid "&SVG as Geometry Object ..."
+msgstr "&SVG as Geometry Object ..."
+
+#: flatcamGUI/FlatCAMGUI.py:208
+msgid "&SVG as Gerber Object ..."
+msgstr "&SVG as Gerber Object ..."
+
+#: flatcamGUI/FlatCAMGUI.py:213
+msgid "&DXF as Geometry Object ..."
+msgstr "&DXF as Geometry Object ..."
+
+#: flatcamGUI/FlatCAMGUI.py:216
+msgid "&DXF as Gerber Object ..."
+msgstr "&DXF as Gerber Object ..."
+
+#: flatcamGUI/FlatCAMGUI.py:220
+msgid "HPGL2 as Geometry Object ..."
+msgstr "HPGL2 as Geometry Object ..."
+
+#: flatcamGUI/FlatCAMGUI.py:226
+msgid "Export"
+msgstr "Export"
+
+#: flatcamGUI/FlatCAMGUI.py:230
+msgid "Export &SVG ..."
+msgstr "Export &SVG ..."
+
+#: flatcamGUI/FlatCAMGUI.py:234
+msgid "Export DXF ..."
+msgstr "Export DXF ..."
+
+#: flatcamGUI/FlatCAMGUI.py:240
+msgid "Export &PNG ..."
+msgstr "Export &PNG ..."
+
+#: flatcamGUI/FlatCAMGUI.py:242
+msgid ""
+"Will export an image in PNG format,\n"
+"the saved image will contain the visual \n"
+"information currently in FlatCAM Plot Area."
+msgstr ""
+"Will export an image in PNG format,\n"
+"the saved image will contain the visual \n"
+"information currently in FlatCAM Plot Area."
+
+#: flatcamGUI/FlatCAMGUI.py:251
+msgid "Export &Excellon ..."
+msgstr "Export &Excellon ..."
+
+#: flatcamGUI/FlatCAMGUI.py:253
+msgid ""
+"Will export an Excellon Object as Excellon file,\n"
+"the coordinates format, the file units and zeros\n"
+"are set in Preferences -> Excellon Export."
+msgstr ""
+"Will export an Excellon Object as Excellon file,\n"
+"the coordinates format, the file units and zeros\n"
+"are set in Preferences -> Excellon Export."
+
+#: flatcamGUI/FlatCAMGUI.py:260
+msgid "Export &Gerber ..."
+msgstr "Export &Gerber ..."
+
+#: flatcamGUI/FlatCAMGUI.py:262
+msgid ""
+"Will export an Gerber Object as Gerber file,\n"
+"the coordinates format, the file units and zeros\n"
+"are set in Preferences -> Gerber Export."
+msgstr ""
+"Will export an Gerber Object as Gerber file,\n"
+"the coordinates format, the file units and zeros\n"
+"are set in Preferences -> Gerber Export."
+
+#: flatcamGUI/FlatCAMGUI.py:272
+msgid "Backup"
+msgstr "Backup"
+
+#: flatcamGUI/FlatCAMGUI.py:277
+msgid "Import Preferences from file ..."
+msgstr "Import Preferences from file ..."
+
+#: flatcamGUI/FlatCAMGUI.py:283
+msgid "Export Preferences to file ..."
+msgstr "Export Preferences to file ..."
+
+#: flatcamGUI/FlatCAMGUI.py:297 flatcamGUI/FlatCAMGUI.py:1715
+msgid "Print (PDF)"
+msgstr "Print (PDF)"
+
+#: flatcamGUI/FlatCAMGUI.py:305
+msgid "E&xit"
+msgstr "E&xit"
+
+#: flatcamGUI/FlatCAMGUI.py:313 flatcamGUI/FlatCAMGUI.py:732
+#: flatcamGUI/FlatCAMGUI.py:2271
+msgid "Edit"
+msgstr "Edit"
+
+#: flatcamGUI/FlatCAMGUI.py:317
+msgid "Edit Object\tE"
+msgstr "Edit Object\tE"
+
+#: flatcamGUI/FlatCAMGUI.py:319
+msgid "Close Editor\tCtrl+S"
+msgstr "Close Editor\tCtrl+S"
+
+#: flatcamGUI/FlatCAMGUI.py:328
+msgid "Conversion"
+msgstr "Conversion"
+
+#: flatcamGUI/FlatCAMGUI.py:330
+msgid "&Join Geo/Gerber/Exc -> Geo"
+msgstr "&Join Geo/Gerber/Exc -> Geo"
+
+#: flatcamGUI/FlatCAMGUI.py:332
+msgid ""
+"Merge a selection of objects, which can be of type:\n"
+"- Gerber\n"
+"- Excellon\n"
+"- Geometry\n"
+"into a new combo Geometry object."
+msgstr ""
+"Merge a selection of objects, which can be of type:\n"
+"- Gerber\n"
+"- Excellon\n"
+"- Geometry\n"
+"into a new combo Geometry object."
+
+#: flatcamGUI/FlatCAMGUI.py:339
+msgid "Join Excellon(s) -> Excellon"
+msgstr "Join Excellon(s) -> Excellon"
+
+#: flatcamGUI/FlatCAMGUI.py:341
+msgid "Merge a selection of Excellon objects into a new combo Excellon object."
+msgstr ""
+"Merge a selection of Excellon objects into a new combo Excellon object."
+
+#: flatcamGUI/FlatCAMGUI.py:344
+msgid "Join Gerber(s) -> Gerber"
+msgstr "Join Gerber(s) -> Gerber"
+
+#: flatcamGUI/FlatCAMGUI.py:346
+msgid "Merge a selection of Gerber objects into a new combo Gerber object."
+msgstr "Merge a selection of Gerber objects into a new combo Gerber object."
+
+#: flatcamGUI/FlatCAMGUI.py:351
+msgid "Convert Single to MultiGeo"
+msgstr "Convert Single to MultiGeo"
+
+#: flatcamGUI/FlatCAMGUI.py:353
+msgid ""
+"Will convert a Geometry object from single_geometry type\n"
+"to a multi_geometry type."
+msgstr ""
+"Will convert a Geometry object from single_geometry type\n"
+"to a multi_geometry type."
+
+#: flatcamGUI/FlatCAMGUI.py:357
+msgid "Convert Multi to SingleGeo"
+msgstr "Convert Multi to SingleGeo"
+
+#: flatcamGUI/FlatCAMGUI.py:359
+msgid ""
+"Will convert a Geometry object from multi_geometry type\n"
+"to a single_geometry type."
+msgstr ""
+"Will convert a Geometry object from multi_geometry type\n"
+"to a single_geometry type."
+
+#: flatcamGUI/FlatCAMGUI.py:366
+msgid "Convert Any to Geo"
+msgstr "Convert Any to Geo"
+
+#: flatcamGUI/FlatCAMGUI.py:369
+msgid "Convert Any to Gerber"
+msgstr "Convert Any to Gerber"
+
+#: flatcamGUI/FlatCAMGUI.py:375
+msgid "&Copy\tCtrl+C"
+msgstr "&Copy\tCtrl+C"
+
+#: flatcamGUI/FlatCAMGUI.py:380
+msgid "&Delete\tDEL"
+msgstr "&Delete\tDEL"
+
+#: flatcamGUI/FlatCAMGUI.py:385
+msgid "Se&t Origin\tO"
+msgstr "Se&t Origin\tO"
+
+#: flatcamGUI/FlatCAMGUI.py:387
+msgid "Move to Origin\tShift+O"
+msgstr "Move to Origin\tShift+O"
+
+#: flatcamGUI/FlatCAMGUI.py:390
+msgid "Jump to Location\tJ"
+msgstr "Jump to Location\tJ"
+
+#: flatcamGUI/FlatCAMGUI.py:392
+msgid "Locate in Object\tShift+J"
+msgstr "Locate in Object\tShift+J"
+
+#: flatcamGUI/FlatCAMGUI.py:397
+msgid "Toggle Units\tQ"
+msgstr "Toggle Units\tQ"
+
+#: flatcamGUI/FlatCAMGUI.py:399
+msgid "&Select All\tCtrl+A"
+msgstr "&Select All\tCtrl+A"
+
+#: flatcamGUI/FlatCAMGUI.py:404
+msgid "&Preferences\tShift+P"
+msgstr "&Preferences\tShift+P"
+
+#: flatcamGUI/FlatCAMGUI.py:410 flatcamTools/ToolProperties.py:155
+msgid "Options"
+msgstr "Options"
+
+#: flatcamGUI/FlatCAMGUI.py:412
+msgid "&Rotate Selection\tShift+(R)"
+msgstr "&Rotate Selection\tShift+(R)"
+
+#: flatcamGUI/FlatCAMGUI.py:417
+msgid "&Skew on X axis\tShift+X"
+msgstr "&Skew on X axis\tShift+X"
+
+#: flatcamGUI/FlatCAMGUI.py:419
+msgid "S&kew on Y axis\tShift+Y"
+msgstr "S&kew on Y axis\tShift+Y"
+
+#: flatcamGUI/FlatCAMGUI.py:424
+msgid "Flip on &X axis\tX"
+msgstr "Flip on &X axis\tX"
+
+#: flatcamGUI/FlatCAMGUI.py:426
+msgid "Flip on &Y axis\tY"
+msgstr "Flip on &Y axis\tY"
+
+#: flatcamGUI/FlatCAMGUI.py:431
+msgid "View source\tAlt+S"
+msgstr "View source\tAlt+S"
+
+#: flatcamGUI/FlatCAMGUI.py:433
+msgid "Tools DataBase\tCtrl+D"
+msgstr "Tools DataBase\tCtrl+D"
+
+#: flatcamGUI/FlatCAMGUI.py:440 flatcamGUI/FlatCAMGUI.py:2168
+msgid "View"
+msgstr "View"
+
+#: flatcamGUI/FlatCAMGUI.py:442
+msgid "Enable all plots\tAlt+1"
+msgstr "Enable all plots\tAlt+1"
+
+#: flatcamGUI/FlatCAMGUI.py:444
+msgid "Disable all plots\tAlt+2"
+msgstr "Disable all plots\tAlt+2"
+
+#: flatcamGUI/FlatCAMGUI.py:446
+msgid "Disable non-selected\tAlt+3"
+msgstr "Disable non-selected\tAlt+3"
+
+#: flatcamGUI/FlatCAMGUI.py:450
+msgid "&Zoom Fit\tV"
+msgstr "&Zoom Fit\tV"
+
+#: flatcamGUI/FlatCAMGUI.py:452
+msgid "&Zoom In\t="
+msgstr "&Zoom In\t="
+
+#: flatcamGUI/FlatCAMGUI.py:454
+msgid "&Zoom Out\t-"
+msgstr "&Zoom Out\t-"
+
+#: flatcamGUI/FlatCAMGUI.py:459
+msgid "Redraw All\tF5"
+msgstr "Redraw All\tF5"
+
+#: flatcamGUI/FlatCAMGUI.py:463
+msgid "Toggle Code Editor\tShift+E"
+msgstr "Toggle Code Editor\tShift+E"
+
+#: flatcamGUI/FlatCAMGUI.py:466
+msgid "&Toggle FullScreen\tAlt+F10"
+msgstr "&Toggle FullScreen\tAlt+F10"
+
+#: flatcamGUI/FlatCAMGUI.py:468
+msgid "&Toggle Plot Area\tCtrl+F10"
+msgstr "&Toggle Plot Area\tCtrl+F10"
+
+#: flatcamGUI/FlatCAMGUI.py:470
+msgid "&Toggle Project/Sel/Tool\t`"
+msgstr "&Toggle Project/Sel/Tool\t`"
+
+#: flatcamGUI/FlatCAMGUI.py:474
+msgid "&Toggle Grid Snap\tG"
+msgstr "&Toggle Grid Snap\tG"
+
+#: flatcamGUI/FlatCAMGUI.py:476
+msgid "&Toggle Grid Lines\tAlt+G"
+msgstr "&Toggle Grid Lines\tAlt+G"
+
+#: flatcamGUI/FlatCAMGUI.py:478
+msgid "&Toggle Axis\tShift+G"
+msgstr "&Toggle Axis\tShift+G"
+
+#: flatcamGUI/FlatCAMGUI.py:480
+msgid "Toggle Workspace\tShift+W"
+msgstr "Toggle Workspace\tShift+W"
+
+#: flatcamGUI/FlatCAMGUI.py:485
+msgid "Objects"
+msgstr "Objects"
+
+#: flatcamGUI/FlatCAMGUI.py:499
+msgid "&Command Line\tS"
+msgstr "&Command Line\tS"
+
+#: flatcamGUI/FlatCAMGUI.py:504
+msgid "Help"
+msgstr "Help"
+
+#: flatcamGUI/FlatCAMGUI.py:506
+msgid "Online Help\tF1"
+msgstr "Online Help\tF1"
+
+#: flatcamGUI/FlatCAMGUI.py:516
+msgid "Report a bug"
+msgstr "Report a bug"
+
+#: flatcamGUI/FlatCAMGUI.py:519
+msgid "Excellon Specification"
+msgstr "Excellon Specification"
+
+#: flatcamGUI/FlatCAMGUI.py:521
+msgid "Gerber Specification"
+msgstr "Gerber Specification"
+
+#: flatcamGUI/FlatCAMGUI.py:526
+msgid "Shortcuts List\tF3"
+msgstr "Shortcuts List\tF3"
+
+#: flatcamGUI/FlatCAMGUI.py:528
+msgid "YouTube Channel\tF4"
+msgstr "YouTube Channel\tF4"
+
+#: flatcamGUI/FlatCAMGUI.py:539
+msgid "Add Circle\tO"
+msgstr "Add Circle\tO"
+
+#: flatcamGUI/FlatCAMGUI.py:542
+msgid "Add Arc\tA"
+msgstr "Add Arc\tA"
+
+#: flatcamGUI/FlatCAMGUI.py:545
+msgid "Add Rectangle\tR"
+msgstr "Add Rectangle\tR"
+
+#: flatcamGUI/FlatCAMGUI.py:548
+msgid "Add Polygon\tN"
+msgstr "Add Polygon\tN"
+
+#: flatcamGUI/FlatCAMGUI.py:551
+msgid "Add Path\tP"
+msgstr "Add Path\tP"
+
+#: flatcamGUI/FlatCAMGUI.py:554
+msgid "Add Text\tT"
+msgstr "Add Text\tT"
+
+#: flatcamGUI/FlatCAMGUI.py:557
+msgid "Polygon Union\tU"
+msgstr "Polygon Union\tU"
+
+#: flatcamGUI/FlatCAMGUI.py:559
+msgid "Polygon Intersection\tE"
+msgstr "Polygon Intersection\tE"
+
+#: flatcamGUI/FlatCAMGUI.py:561
+msgid "Polygon Subtraction\tS"
+msgstr "Polygon Subtraction\tS"
+
+#: flatcamGUI/FlatCAMGUI.py:565
+msgid "Cut Path\tX"
+msgstr "Cut Path\tX"
+
+#: flatcamGUI/FlatCAMGUI.py:569
+msgid "Copy Geom\tC"
+msgstr "Copy Geom\tC"
+
+#: flatcamGUI/FlatCAMGUI.py:571
+msgid "Delete Shape\tDEL"
+msgstr "Delete Shape\tDEL"
+
+#: flatcamGUI/FlatCAMGUI.py:575 flatcamGUI/FlatCAMGUI.py:662
+msgid "Move\tM"
+msgstr "Move\tM"
+
+#: flatcamGUI/FlatCAMGUI.py:577
+msgid "Buffer Tool\tB"
+msgstr "Buffer Tool\tB"
+
+#: flatcamGUI/FlatCAMGUI.py:580
+msgid "Paint Tool\tI"
+msgstr "Paint Tool\tI"
+
+#: flatcamGUI/FlatCAMGUI.py:583
+msgid "Transform Tool\tAlt+R"
+msgstr "Transform Tool\tAlt+R"
+
+#: flatcamGUI/FlatCAMGUI.py:587
+msgid "Toggle Corner Snap\tK"
+msgstr "Toggle Corner Snap\tK"
+
+#: flatcamGUI/FlatCAMGUI.py:593
+msgid ">Excellon Editor<"
+msgstr ">Excellon Editor<"
+
+#: flatcamGUI/FlatCAMGUI.py:597
+msgid "Add Drill Array\tA"
+msgstr "Add Drill Array\tA"
+
+#: flatcamGUI/FlatCAMGUI.py:599
+msgid "Add Drill\tD"
+msgstr "Add Drill\tD"
+
+#: flatcamGUI/FlatCAMGUI.py:603
+msgid "Add Slot Array\tQ"
+msgstr "Add Slot Array\tQ"
+
+#: flatcamGUI/FlatCAMGUI.py:605
+msgid "Add Slot\tW"
+msgstr "Add Slot\tW"
+
+#: flatcamGUI/FlatCAMGUI.py:609
+msgid "Resize Drill(S)\tR"
+msgstr "Resize Drill(S)\tR"
+
+#: flatcamGUI/FlatCAMGUI.py:612 flatcamGUI/FlatCAMGUI.py:656
+msgid "Copy\tC"
+msgstr "Copy\tC"
+
+#: flatcamGUI/FlatCAMGUI.py:614 flatcamGUI/FlatCAMGUI.py:658
+msgid "Delete\tDEL"
+msgstr "Delete\tDEL"
+
+#: flatcamGUI/FlatCAMGUI.py:619
+msgid "Move Drill(s)\tM"
+msgstr "Move Drill(s)\tM"
+
+#: flatcamGUI/FlatCAMGUI.py:624
+msgid ">Gerber Editor<"
+msgstr ">Gerber Editor<"
+
+#: flatcamGUI/FlatCAMGUI.py:628
+msgid "Add Pad\tP"
+msgstr "Add Pad\tP"
+
+#: flatcamGUI/FlatCAMGUI.py:630
+msgid "Add Pad Array\tA"
+msgstr "Add Pad Array\tA"
+
+#: flatcamGUI/FlatCAMGUI.py:632
+msgid "Add Track\tT"
+msgstr "Add Track\tT"
+
+#: flatcamGUI/FlatCAMGUI.py:634
+msgid "Add Region\tN"
+msgstr "Add Region\tN"
+
+#: flatcamGUI/FlatCAMGUI.py:638
+msgid "Poligonize\tAlt+N"
+msgstr "Poligonize\tAlt+N"
+
+#: flatcamGUI/FlatCAMGUI.py:640
+msgid "Add SemiDisc\tE"
+msgstr "Add SemiDisc\tE"
+
+#: flatcamGUI/FlatCAMGUI.py:642
+msgid "Add Disc\tD"
+msgstr "Add Disc\tD"
+
+#: flatcamGUI/FlatCAMGUI.py:644
+msgid "Buffer\tB"
+msgstr "Buffer\tB"
+
+#: flatcamGUI/FlatCAMGUI.py:646
+msgid "Scale\tS"
+msgstr "Scale\tS"
+
+#: flatcamGUI/FlatCAMGUI.py:648
+msgid "Mark Area\tAlt+A"
+msgstr "Mark Area\tAlt+A"
+
+#: flatcamGUI/FlatCAMGUI.py:650
+msgid "Eraser\tCtrl+E"
+msgstr "Eraser\tCtrl+E"
+
+#: flatcamGUI/FlatCAMGUI.py:652
+msgid "Transform\tAlt+R"
+msgstr "Transform\tAlt+R"
+
+#: flatcamGUI/FlatCAMGUI.py:679
+msgid "Enable Plot"
+msgstr "Enable Plot"
+
+#: flatcamGUI/FlatCAMGUI.py:681
+msgid "Disable Plot"
+msgstr "Disable Plot"
+
+#: flatcamGUI/FlatCAMGUI.py:685
+msgid "Set Color"
+msgstr "Set Color"
+
+#: flatcamGUI/FlatCAMGUI.py:727
+msgid "Generate CNC"
+msgstr "Generate CNC"
+
+#: flatcamGUI/FlatCAMGUI.py:729
+msgid "View Source"
+msgstr "View Source"
+
+#: flatcamGUI/FlatCAMGUI.py:734 flatcamGUI/FlatCAMGUI.py:848
+#: flatcamGUI/FlatCAMGUI.py:1057 flatcamGUI/FlatCAMGUI.py:2123
+#: flatcamGUI/FlatCAMGUI.py:2267 flatcamGUI/FlatCAMGUI.py:2532
+#: flatcamGUI/FlatCAMGUI.py:2731 flatcamGUI/ObjectUI.py:1616
+#: flatcamObjects/FlatCAMGeometry.py:477 flatcamTools/ToolPanelize.py:541
+#: flatcamTools/ToolPanelize.py:568 flatcamTools/ToolPanelize.py:667
+#: flatcamTools/ToolPanelize.py:701 flatcamTools/ToolPanelize.py:766
+msgid "Copy"
+msgstr "Copy"
+
+#: flatcamGUI/FlatCAMGUI.py:742 flatcamGUI/FlatCAMGUI.py:2280
+#: flatcamTools/ToolProperties.py:31
+msgid "Properties"
+msgstr "Properties"
+
+#: flatcamGUI/FlatCAMGUI.py:771
+msgid "File Toolbar"
+msgstr "File Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:775
+msgid "Edit Toolbar"
+msgstr "Edit Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:779
+msgid "View Toolbar"
+msgstr "View Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:783
+msgid "Shell Toolbar"
+msgstr "Shell Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:787
+msgid "Tools Toolbar"
+msgstr "Tools Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:791
+msgid "Excellon Editor Toolbar"
+msgstr "Excellon Editor Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:797
+msgid "Geometry Editor Toolbar"
+msgstr "Geometry Editor Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:801
+msgid "Gerber Editor Toolbar"
+msgstr "Gerber Editor Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:805
+msgid "Grid Toolbar"
+msgstr "Grid Toolbar"
+
+#: flatcamGUI/FlatCAMGUI.py:826 flatcamGUI/FlatCAMGUI.py:2509
+msgid "Open project"
+msgstr "Open project"
+
+#: flatcamGUI/FlatCAMGUI.py:828 flatcamGUI/FlatCAMGUI.py:2511
+msgid "Save project"
+msgstr "Save project"
+
+#: flatcamGUI/FlatCAMGUI.py:834 flatcamGUI/FlatCAMGUI.py:2517
+msgid "New Blank Geometry"
+msgstr "New Blank Geometry"
+
+#: flatcamGUI/FlatCAMGUI.py:836 flatcamGUI/FlatCAMGUI.py:2519
+msgid "New Blank Gerber"
+msgstr "New Blank Gerber"
+
+#: flatcamGUI/FlatCAMGUI.py:838 flatcamGUI/FlatCAMGUI.py:2521
+msgid "New Blank Excellon"
+msgstr "New Blank Excellon"
+
+#: flatcamGUI/FlatCAMGUI.py:843 flatcamGUI/FlatCAMGUI.py:2527
+msgid "Save Object and close the Editor"
+msgstr "Save Object and close the Editor"
+
+#: flatcamGUI/FlatCAMGUI.py:850 flatcamGUI/FlatCAMGUI.py:2534
+msgid "&Delete"
+msgstr "&Delete"
+
+#: flatcamGUI/FlatCAMGUI.py:853 flatcamGUI/FlatCAMGUI.py:1714
+#: flatcamGUI/FlatCAMGUI.py:1920 flatcamGUI/FlatCAMGUI.py:2537
+#: flatcamTools/ToolDistance.py:35 flatcamTools/ToolDistance.py:195
+msgid "Distance Tool"
+msgstr "Distance Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:855 flatcamGUI/FlatCAMGUI.py:2539
+msgid "Distance Min Tool"
+msgstr "Distance Min Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:857 flatcamGUI/FlatCAMGUI.py:1707
+#: flatcamGUI/FlatCAMGUI.py:2541
+msgid "Set Origin"
+msgstr "Set Origin"
+
+#: flatcamGUI/FlatCAMGUI.py:859
+msgid "Move to Origin"
+msgstr "Move to Origin"
+
+#: flatcamGUI/FlatCAMGUI.py:862 flatcamGUI/FlatCAMGUI.py:2543
+msgid "Jump to Location"
+msgstr "Jump to Location"
+
+#: flatcamGUI/FlatCAMGUI.py:864 flatcamGUI/FlatCAMGUI.py:1719
+#: flatcamGUI/FlatCAMGUI.py:2545
+msgid "Locate in Object"
+msgstr "Locate in Object"
+
+#: flatcamGUI/FlatCAMGUI.py:870 flatcamGUI/FlatCAMGUI.py:2551
+msgid "&Replot"
+msgstr "&Replot"
+
+#: flatcamGUI/FlatCAMGUI.py:872 flatcamGUI/FlatCAMGUI.py:2553
+msgid "&Clear plot"
+msgstr "&Clear plot"
+
+#: flatcamGUI/FlatCAMGUI.py:874 flatcamGUI/FlatCAMGUI.py:1710
+#: flatcamGUI/FlatCAMGUI.py:2555
+msgid "Zoom In"
+msgstr "Zoom In"
+
+#: flatcamGUI/FlatCAMGUI.py:876 flatcamGUI/FlatCAMGUI.py:1710
+#: flatcamGUI/FlatCAMGUI.py:2557
+msgid "Zoom Out"
+msgstr "Zoom Out"
+
+#: flatcamGUI/FlatCAMGUI.py:878 flatcamGUI/FlatCAMGUI.py:1709
+#: flatcamGUI/FlatCAMGUI.py:2170 flatcamGUI/FlatCAMGUI.py:2559
+msgid "Zoom Fit"
+msgstr "Zoom Fit"
+
+#: flatcamGUI/FlatCAMGUI.py:886 flatcamGUI/FlatCAMGUI.py:2565
+msgid "&Command Line"
+msgstr "&Command Line"
+
+#: flatcamGUI/FlatCAMGUI.py:898 flatcamGUI/FlatCAMGUI.py:2577
+msgid "2Sided Tool"
+msgstr "2Sided Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:900 flatcamGUI/FlatCAMGUI.py:1725
+#: flatcamGUI/FlatCAMGUI.py:2579
+msgid "Align Objects Tool"
+msgstr "Align Objects Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:902 flatcamGUI/FlatCAMGUI.py:1726
+#: flatcamGUI/FlatCAMGUI.py:2581 flatcamTools/ToolExtractDrills.py:393
+msgid "Extract Drills Tool"
+msgstr "Extract Drills Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:905 flatcamGUI/ObjectUI.py:595
+#: flatcamTools/ToolCutOut.py:446
+msgid "Cutout Tool"
+msgstr "Cutout Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:907 flatcamGUI/FlatCAMGUI.py:2586
+#: flatcamGUI/ObjectUI.py:573 flatcamGUI/ObjectUI.py:2076
+#: flatcamTools/ToolNCC.py:974
+msgid "NCC Tool"
+msgstr "NCC Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:913 flatcamGUI/FlatCAMGUI.py:2592
+msgid "Panel Tool"
+msgstr "Panel Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:915 flatcamGUI/FlatCAMGUI.py:2594
+#: flatcamTools/ToolFilm.py:586
+msgid "Film Tool"
+msgstr "Film Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:917 flatcamGUI/FlatCAMGUI.py:2596
+#: flatcamTools/ToolSolderPaste.py:553
+msgid "SolderPaste Tool"
+msgstr "SolderPaste Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:919 flatcamGUI/FlatCAMGUI.py:2598
+#: flatcamTools/ToolSub.py:35
+msgid "Subtract Tool"
+msgstr "Subtract Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:921 flatcamGUI/FlatCAMGUI.py:2600
+#: flatcamTools/ToolRulesCheck.py:616
+msgid "Rules Tool"
+msgstr "Rules Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:923 flatcamGUI/FlatCAMGUI.py:1728
+#: flatcamGUI/FlatCAMGUI.py:2602 flatcamTools/ToolOptimal.py:33
+#: flatcamTools/ToolOptimal.py:307
+msgid "Optimal Tool"
+msgstr "Optimal Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:928 flatcamGUI/FlatCAMGUI.py:1725
+#: flatcamGUI/FlatCAMGUI.py:2607
+msgid "Calculators Tool"
+msgstr "Calculators Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:932 flatcamGUI/FlatCAMGUI.py:1729
+#: flatcamGUI/FlatCAMGUI.py:2611 flatcamTools/ToolQRCode.py:43
+#: flatcamTools/ToolQRCode.py:382
+msgid "QRCode Tool"
+msgstr "QRCode Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:934 flatcamGUI/FlatCAMGUI.py:2613
+#: flatcamTools/ToolCopperThieving.py:39 flatcamTools/ToolCopperThieving.py:568
+msgid "Copper Thieving Tool"
+msgstr "Copper Thieving Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:937 flatcamGUI/FlatCAMGUI.py:1726
+#: flatcamGUI/FlatCAMGUI.py:2616 flatcamTools/ToolFiducials.py:33
+#: flatcamTools/ToolFiducials.py:395
+msgid "Fiducials Tool"
+msgstr "Fiducials Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:939 flatcamGUI/FlatCAMGUI.py:2618
+#: flatcamTools/ToolCalibration.py:37 flatcamTools/ToolCalibration.py:759
+msgid "Calibration Tool"
+msgstr "Calibration Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:941 flatcamGUI/FlatCAMGUI.py:1726
+msgid "Punch Gerber Tool"
+msgstr "Punch Gerber Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:943 flatcamTools/ToolInvertGerber.py:31
+msgid "Invert Gerber Tool"
+msgstr "Invert Gerber Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:949 flatcamGUI/FlatCAMGUI.py:975
+#: flatcamGUI/FlatCAMGUI.py:1027 flatcamGUI/FlatCAMGUI.py:2624
+#: flatcamGUI/FlatCAMGUI.py:2702
+msgid "Select"
+msgstr "Select"
+
+#: flatcamGUI/FlatCAMGUI.py:951 flatcamGUI/FlatCAMGUI.py:2626
+msgid "Add Drill Hole"
+msgstr "Add Drill Hole"
+
+#: flatcamGUI/FlatCAMGUI.py:953 flatcamGUI/FlatCAMGUI.py:2628
+msgid "Add Drill Hole Array"
+msgstr "Add Drill Hole Array"
+
+#: flatcamGUI/FlatCAMGUI.py:955 flatcamGUI/FlatCAMGUI.py:2005
+#: flatcamGUI/FlatCAMGUI.py:2258 flatcamGUI/FlatCAMGUI.py:2632
+msgid "Add Slot"
+msgstr "Add Slot"
+
+#: flatcamGUI/FlatCAMGUI.py:957 flatcamGUI/FlatCAMGUI.py:2004
+#: flatcamGUI/FlatCAMGUI.py:2260 flatcamGUI/FlatCAMGUI.py:2634
+msgid "Add Slot Array"
+msgstr "Add Slot Array"
+
+#: flatcamGUI/FlatCAMGUI.py:959 flatcamGUI/FlatCAMGUI.py:2263
+#: flatcamGUI/FlatCAMGUI.py:2630
+msgid "Resize Drill"
+msgstr "Resize Drill"
+
+#: flatcamGUI/FlatCAMGUI.py:963 flatcamGUI/FlatCAMGUI.py:2638
+msgid "Copy Drill"
+msgstr "Copy Drill"
+
+#: flatcamGUI/FlatCAMGUI.py:965 flatcamGUI/FlatCAMGUI.py:2640
+msgid "Delete Drill"
+msgstr "Delete Drill"
+
+#: flatcamGUI/FlatCAMGUI.py:969 flatcamGUI/FlatCAMGUI.py:2644
+msgid "Move Drill"
+msgstr "Move Drill"
+
+#: flatcamGUI/FlatCAMGUI.py:977 flatcamGUI/FlatCAMGUI.py:2652
+msgid "Add Circle"
+msgstr "Add Circle"
+
+#: flatcamGUI/FlatCAMGUI.py:979 flatcamGUI/FlatCAMGUI.py:2654
+msgid "Add Arc"
+msgstr "Add Arc"
+
+#: flatcamGUI/FlatCAMGUI.py:981 flatcamGUI/FlatCAMGUI.py:2656
+msgid "Add Rectangle"
+msgstr "Add Rectangle"
+
+#: flatcamGUI/FlatCAMGUI.py:985 flatcamGUI/FlatCAMGUI.py:2660
+msgid "Add Path"
+msgstr "Add Path"
+
+#: flatcamGUI/FlatCAMGUI.py:987 flatcamGUI/FlatCAMGUI.py:2662
+msgid "Add Polygon"
+msgstr "Add Polygon"
+
+#: flatcamGUI/FlatCAMGUI.py:990 flatcamGUI/FlatCAMGUI.py:2665
+msgid "Add Text"
+msgstr "Add Text"
+
+#: flatcamGUI/FlatCAMGUI.py:992 flatcamGUI/FlatCAMGUI.py:2667
+msgid "Add Buffer"
+msgstr "Add Buffer"
+
+#: flatcamGUI/FlatCAMGUI.py:994 flatcamGUI/FlatCAMGUI.py:2669
+msgid "Paint Shape"
+msgstr "Paint Shape"
+
+#: flatcamGUI/FlatCAMGUI.py:996 flatcamGUI/FlatCAMGUI.py:1053
+#: flatcamGUI/FlatCAMGUI.py:2199 flatcamGUI/FlatCAMGUI.py:2244
+#: flatcamGUI/FlatCAMGUI.py:2671 flatcamGUI/FlatCAMGUI.py:2727
+msgid "Eraser"
+msgstr "Eraser"
+
+#: flatcamGUI/FlatCAMGUI.py:1000 flatcamGUI/FlatCAMGUI.py:2675
+msgid "Polygon Union"
+msgstr "Polygon Union"
+
+#: flatcamGUI/FlatCAMGUI.py:1002 flatcamGUI/FlatCAMGUI.py:2677
+msgid "Polygon Explode"
+msgstr "Polygon Explode"
+
+#: flatcamGUI/FlatCAMGUI.py:1005 flatcamGUI/FlatCAMGUI.py:2680
+msgid "Polygon Intersection"
+msgstr "Polygon Intersection"
+
+#: flatcamGUI/FlatCAMGUI.py:1007 flatcamGUI/FlatCAMGUI.py:2682
+msgid "Polygon Subtraction"
+msgstr "Polygon Subtraction"
+
+#: flatcamGUI/FlatCAMGUI.py:1011 flatcamGUI/FlatCAMGUI.py:2686
+msgid "Cut Path"
+msgstr "Cut Path"
+
+#: flatcamGUI/FlatCAMGUI.py:1013
+msgid "Copy Shape(s)"
+msgstr "Copy Shape(s)"
+
+#: flatcamGUI/FlatCAMGUI.py:1016
+msgid "Delete Shape '-'"
+msgstr "Delete Shape '-'"
+
+#: flatcamGUI/FlatCAMGUI.py:1018 flatcamGUI/FlatCAMGUI.py:1061
+#: flatcamGUI/FlatCAMGUI.py:2211 flatcamGUI/FlatCAMGUI.py:2248
+#: flatcamGUI/FlatCAMGUI.py:2692 flatcamGUI/FlatCAMGUI.py:2735
+#: flatcamGUI/ObjectUI.py:108
+msgid "Transformations"
+msgstr "Transformations"
+
+#: flatcamGUI/FlatCAMGUI.py:1021
+msgid "Move Objects "
+msgstr "Move Objects "
+
+#: flatcamGUI/FlatCAMGUI.py:1029 flatcamGUI/FlatCAMGUI.py:2124
+#: flatcamGUI/FlatCAMGUI.py:2704
+msgid "Add Pad"
+msgstr "Add Pad"
+
+#: flatcamGUI/FlatCAMGUI.py:1033 flatcamGUI/FlatCAMGUI.py:2125
+#: flatcamGUI/FlatCAMGUI.py:2708
+msgid "Add Track"
+msgstr "Add Track"
+
+#: flatcamGUI/FlatCAMGUI.py:1035 flatcamGUI/FlatCAMGUI.py:2124
+#: flatcamGUI/FlatCAMGUI.py:2710
+msgid "Add Region"
+msgstr "Add Region"
+
+#: flatcamGUI/FlatCAMGUI.py:1037 flatcamGUI/FlatCAMGUI.py:2230
+#: flatcamGUI/FlatCAMGUI.py:2712
+msgid "Poligonize"
+msgstr "Poligonize"
+
+#: flatcamGUI/FlatCAMGUI.py:1040 flatcamGUI/FlatCAMGUI.py:2232
+#: flatcamGUI/FlatCAMGUI.py:2715
+msgid "SemiDisc"
+msgstr "SemiDisc"
+
+#: flatcamGUI/FlatCAMGUI.py:1042 flatcamGUI/FlatCAMGUI.py:2234
+#: flatcamGUI/FlatCAMGUI.py:2717
+msgid "Disc"
+msgstr "Disc"
+
+#: flatcamGUI/FlatCAMGUI.py:1050 flatcamGUI/FlatCAMGUI.py:2242
+#: flatcamGUI/FlatCAMGUI.py:2725
+msgid "Mark Area"
+msgstr "Mark Area"
+
+#: flatcamGUI/FlatCAMGUI.py:1064 flatcamGUI/FlatCAMGUI.py:2124
+#: flatcamGUI/FlatCAMGUI.py:2215 flatcamGUI/FlatCAMGUI.py:2278
+#: flatcamGUI/FlatCAMGUI.py:2738 flatcamTools/ToolMove.py:27
+msgid "Move"
+msgstr "Move"
+
+#: flatcamGUI/FlatCAMGUI.py:1072 flatcamGUI/FlatCAMGUI.py:2747
+msgid "Snap to grid"
+msgstr "Snap to grid"
+
+#: flatcamGUI/FlatCAMGUI.py:1075 flatcamGUI/FlatCAMGUI.py:2750
+msgid "Grid X snapping distance"
+msgstr "Grid X snapping distance"
+
+#: flatcamGUI/FlatCAMGUI.py:1080 flatcamGUI/FlatCAMGUI.py:2755
+msgid "Grid Y snapping distance"
+msgstr "Grid Y snapping distance"
+
+#: flatcamGUI/FlatCAMGUI.py:1086 flatcamGUI/FlatCAMGUI.py:2761
+msgid ""
+"When active, value on Grid_X\n"
+"is copied to the Grid_Y value."
+msgstr ""
+"When active, value on Grid_X\n"
+"is copied to the Grid_Y value."
+
+#: flatcamGUI/FlatCAMGUI.py:1093 flatcamGUI/FlatCAMGUI.py:2768
+msgid "Snap to corner"
+msgstr "Snap to corner"
+
+#: flatcamGUI/FlatCAMGUI.py:1097 flatcamGUI/FlatCAMGUI.py:2772
+#: flatcamGUI/PreferencesUI.py:1159
+msgid "Max. magnet distance"
+msgstr "Max. magnet distance"
+
+#: flatcamGUI/FlatCAMGUI.py:1134
+msgid "Selected"
+msgstr "Selected"
+
+#: flatcamGUI/FlatCAMGUI.py:1162 flatcamGUI/FlatCAMGUI.py:1170
+msgid "Plot Area"
+msgstr "Plot Area"
+
+#: flatcamGUI/FlatCAMGUI.py:1197
+msgid "General"
+msgstr "General"
+
+#: flatcamGUI/FlatCAMGUI.py:1212 flatcamTools/ToolCopperThieving.py:74
+#: flatcamTools/ToolDblSided.py:64 flatcamTools/ToolExtractDrills.py:61
+#: flatcamTools/ToolInvertGerber.py:72 flatcamTools/ToolOptimal.py:71
+#: flatcamTools/ToolPunchGerber.py:64
+msgid "GERBER"
+msgstr "GERBER"
+
+#: flatcamGUI/FlatCAMGUI.py:1222 flatcamTools/ToolDblSided.py:92
+msgid "EXCELLON"
+msgstr "EXCELLON"
+
+#: flatcamGUI/FlatCAMGUI.py:1232 flatcamTools/ToolDblSided.py:120
+msgid "GEOMETRY"
+msgstr "GEOMETRY"
+
+#: flatcamGUI/FlatCAMGUI.py:1242
+msgid "CNC-JOB"
+msgstr "CNC-JOB"
+
+#: flatcamGUI/FlatCAMGUI.py:1251 flatcamGUI/ObjectUI.py:562
+#: flatcamGUI/ObjectUI.py:2051
+msgid "TOOLS"
+msgstr "TOOLS"
+
+#: flatcamGUI/FlatCAMGUI.py:1260
+msgid "TOOLS 2"
+msgstr "TOOLS 2"
+
+#: flatcamGUI/FlatCAMGUI.py:1270
+msgid "UTILITIES"
+msgstr "UTILITIES"
+
+#: flatcamGUI/FlatCAMGUI.py:1287 flatcamGUI/PreferencesUI.py:3015
+msgid "Restore Defaults"
+msgstr "Restore Defaults"
+
+#: flatcamGUI/FlatCAMGUI.py:1290
+msgid ""
+"Restore the entire set of default values\n"
+"to the initial values loaded after first launch."
+msgstr ""
+"Restore the entire set of default values\n"
+"to the initial values loaded after first launch."
+
+#: flatcamGUI/FlatCAMGUI.py:1295
+msgid "Open Pref Folder"
+msgstr "Open Pref Folder"
+
+#: flatcamGUI/FlatCAMGUI.py:1298
+msgid "Open the folder where FlatCAM save the preferences files."
+msgstr "Open the folder where FlatCAM save the preferences files."
+
+#: flatcamGUI/FlatCAMGUI.py:1302 flatcamGUI/FlatCAMGUI.py:2477
+msgid "Clear GUI Settings"
+msgstr "Clear GUI Settings"
+
+#: flatcamGUI/FlatCAMGUI.py:1306
+msgid ""
+"Clear the GUI settings for FlatCAM,\n"
+"such as: layout, gui state, style, hdpi support etc."
+msgstr ""
+"Clear the GUI settings for FlatCAM,\n"
+"such as: layout, gui state, style, hdpi support etc."
+
+#: flatcamGUI/FlatCAMGUI.py:1317
+msgid "Apply"
+msgstr "Apply"
+
+#: flatcamGUI/FlatCAMGUI.py:1320
+msgid "Apply the current preferences without saving to a file."
+msgstr "Apply the current preferences without saving to a file."
+
+#: flatcamGUI/FlatCAMGUI.py:1327
+msgid ""
+"Save the current settings in the 'current_defaults' file\n"
+"which is the file storing the working default preferences."
+msgstr ""
+"Save the current settings in the 'current_defaults' file\n"
+"which is the file storing the working default preferences."
+
+#: flatcamGUI/FlatCAMGUI.py:1335
+msgid "Will not save the changes and will close the preferences window."
+msgstr "Will not save the changes and will close the preferences window."
+
+#: flatcamGUI/FlatCAMGUI.py:1704
+msgid "SHOW SHORTCUT LIST"
+msgstr "SHOW SHORTCUT LIST"
+
+#: flatcamGUI/FlatCAMGUI.py:1704
+msgid "Switch to Project Tab"
+msgstr "Switch to Project Tab"
+
+#: flatcamGUI/FlatCAMGUI.py:1704
+msgid "Switch to Selected Tab"
+msgstr "Switch to Selected Tab"
+
+#: flatcamGUI/FlatCAMGUI.py:1705
+msgid "Switch to Tool Tab"
+msgstr "Switch to Tool Tab"
+
+#: flatcamGUI/FlatCAMGUI.py:1706
+msgid "New Gerber"
+msgstr "New Gerber"
+
+#: flatcamGUI/FlatCAMGUI.py:1706
+msgid "Edit Object (if selected)"
+msgstr "Edit Object (if selected)"
+
+#: flatcamGUI/FlatCAMGUI.py:1706
+msgid "Jump to Coordinates"
+msgstr "Jump to Coordinates"
+
+#: flatcamGUI/FlatCAMGUI.py:1707
+msgid "New Excellon"
+msgstr "New Excellon"
+
+#: flatcamGUI/FlatCAMGUI.py:1707
+msgid "Move Obj"
+msgstr "Move Obj"
+
+#: flatcamGUI/FlatCAMGUI.py:1707
+msgid "New Geometry"
+msgstr "New Geometry"
+
+#: flatcamGUI/FlatCAMGUI.py:1707
+msgid "Change Units"
+msgstr "Change Units"
+
+#: flatcamGUI/FlatCAMGUI.py:1708
+msgid "Open Properties Tool"
+msgstr "Open Properties Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1708
+msgid "Rotate by 90 degree CW"
+msgstr "Rotate by 90 degree CW"
+
+#: flatcamGUI/FlatCAMGUI.py:1708
+msgid "Shell Toggle"
+msgstr "Shell Toggle"
+
+#: flatcamGUI/FlatCAMGUI.py:1709
+msgid ""
+"Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)"
+msgstr ""
+"Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)"
+
+#: flatcamGUI/FlatCAMGUI.py:1710
+msgid "Flip on X_axis"
+msgstr "Flip on X_axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1710
+msgid "Flip on Y_axis"
+msgstr "Flip on Y_axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1713
+msgid "Copy Obj"
+msgstr "Copy Obj"
+
+#: flatcamGUI/FlatCAMGUI.py:1713
+msgid "Open Tools Database"
+msgstr "Open Tools Database"
+
+#: flatcamGUI/FlatCAMGUI.py:1714
+msgid "Open Excellon File"
+msgstr "Open Excellon File"
+
+#: flatcamGUI/FlatCAMGUI.py:1714
+msgid "Open Gerber File"
+msgstr "Open Gerber File"
+
+#: flatcamGUI/FlatCAMGUI.py:1714
+msgid "New Project"
+msgstr "New Project"
+
+#: flatcamGUI/FlatCAMGUI.py:1715 flatcamTools/ToolPDF.py:42
+msgid "PDF Import Tool"
+msgstr "PDF Import Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1715
+msgid "Save Project"
+msgstr "Save Project"
+
+#: flatcamGUI/FlatCAMGUI.py:1715
+msgid "Toggle Plot Area"
+msgstr "Toggle Plot Area"
+
+#: flatcamGUI/FlatCAMGUI.py:1718
+msgid "Copy Obj_Name"
+msgstr "Copy Obj_Name"
+
+#: flatcamGUI/FlatCAMGUI.py:1719
+msgid "Toggle Code Editor"
+msgstr "Toggle Code Editor"
+
+#: flatcamGUI/FlatCAMGUI.py:1719
+msgid "Toggle the axis"
+msgstr "Toggle the axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1719 flatcamGUI/FlatCAMGUI.py:1918
+#: flatcamGUI/FlatCAMGUI.py:2005 flatcamGUI/FlatCAMGUI.py:2127
+msgid "Distance Minimum Tool"
+msgstr "Distance Minimum Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1720
+msgid "Open Preferences Window"
+msgstr "Open Preferences Window"
+
+#: flatcamGUI/FlatCAMGUI.py:1721
+msgid "Rotate by 90 degree CCW"
+msgstr "Rotate by 90 degree CCW"
+
+#: flatcamGUI/FlatCAMGUI.py:1721
+msgid "Run a Script"
+msgstr "Run a Script"
+
+#: flatcamGUI/FlatCAMGUI.py:1721
+msgid "Toggle the workspace"
+msgstr "Toggle the workspace"
+
+#: flatcamGUI/FlatCAMGUI.py:1721
+msgid "Skew on X axis"
+msgstr "Skew on X axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1722
+msgid "Skew on Y axis"
+msgstr "Skew on Y axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1725
+msgid "2-Sided PCB Tool"
+msgstr "2-Sided PCB Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1725
+msgid "Transformations Tool"
+msgstr "Transformations Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1727
+msgid "Solder Paste Dispensing Tool"
+msgstr "Solder Paste Dispensing Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1728
+msgid "Film PCB Tool"
+msgstr "Film PCB Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1728
+msgid "Non-Copper Clearing Tool"
+msgstr "Non-Copper Clearing Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1729
+msgid "Paint Area Tool"
+msgstr "Paint Area Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1729
+msgid "Rules Check Tool"
+msgstr "Rules Check Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1730
+msgid "View File Source"
+msgstr "View File Source"
+
+#: flatcamGUI/FlatCAMGUI.py:1731
+msgid "Cutout PCB Tool"
+msgstr "Cutout PCB Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1731
+msgid "Enable all Plots"
+msgstr "Enable all Plots"
+
+#: flatcamGUI/FlatCAMGUI.py:1731
+msgid "Disable all Plots"
+msgstr "Disable all Plots"
+
+#: flatcamGUI/FlatCAMGUI.py:1731
+msgid "Disable Non-selected Plots"
+msgstr "Disable Non-selected Plots"
+
+#: flatcamGUI/FlatCAMGUI.py:1732
+msgid "Toggle Full Screen"
+msgstr "Toggle Full Screen"
+
+#: flatcamGUI/FlatCAMGUI.py:1735
+msgid "Abort current task (gracefully)"
+msgstr "Abort current task (gracefully)"
+
+#: flatcamGUI/FlatCAMGUI.py:1738
+msgid "Save Project As"
+msgstr "Save Project As"
+
+#: flatcamGUI/FlatCAMGUI.py:1739
+msgid ""
+"Paste Special. Will convert a Windows path style to the one required in Tcl "
+"Shell"
+msgstr ""
+"Paste Special. Will convert a Windows path style to the one required in Tcl "
+"Shell"
+
+#: flatcamGUI/FlatCAMGUI.py:1742
+msgid "Open Online Manual"
+msgstr "Open Online Manual"
+
+#: flatcamGUI/FlatCAMGUI.py:1743
+msgid "Open Online Tutorials"
+msgstr "Open Online Tutorials"
+
+#: flatcamGUI/FlatCAMGUI.py:1743
+msgid "Refresh Plots"
+msgstr "Refresh Plots"
+
+#: flatcamGUI/FlatCAMGUI.py:1743 flatcamTools/ToolSolderPaste.py:509
+msgid "Delete Object"
+msgstr "Delete Object"
+
+#: flatcamGUI/FlatCAMGUI.py:1743
+msgid "Alternate: Delete Tool"
+msgstr "Alternate: Delete Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1744
+msgid "(left to Key_1)Toogle Notebook Area (Left Side)"
+msgstr "(left to Key_1)Toogle Notebook Area (Left Side)"
+
+#: flatcamGUI/FlatCAMGUI.py:1744
+msgid "En(Dis)able Obj Plot"
+msgstr "En(Dis)able Obj Plot"
+
+#: flatcamGUI/FlatCAMGUI.py:1745
+msgid "Deselects all objects"
+msgstr "Deselects all objects"
+
+#: flatcamGUI/FlatCAMGUI.py:1759
+msgid "Editor Shortcut list"
+msgstr "Editor Shortcut list"
+
+#: flatcamGUI/FlatCAMGUI.py:1913
+msgid "GEOMETRY EDITOR"
+msgstr "GEOMETRY EDITOR"
+
+#: flatcamGUI/FlatCAMGUI.py:1913
+msgid "Draw an Arc"
+msgstr "Draw an Arc"
+
+#: flatcamGUI/FlatCAMGUI.py:1913
+msgid "Copy Geo Item"
+msgstr "Copy Geo Item"
+
+#: flatcamGUI/FlatCAMGUI.py:1914
+msgid "Within Add Arc will toogle the ARC direction: CW or CCW"
+msgstr "Within Add Arc will toogle the ARC direction: CW or CCW"
+
+#: flatcamGUI/FlatCAMGUI.py:1914
+msgid "Polygon Intersection Tool"
+msgstr "Polygon Intersection Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1915
+msgid "Geo Paint Tool"
+msgstr "Geo Paint Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1915 flatcamGUI/FlatCAMGUI.py:2004
+#: flatcamGUI/FlatCAMGUI.py:2124
+msgid "Jump to Location (x, y)"
+msgstr "Jump to Location (x, y)"
+
+#: flatcamGUI/FlatCAMGUI.py:1915
+msgid "Toggle Corner Snap"
+msgstr "Toggle Corner Snap"
+
+#: flatcamGUI/FlatCAMGUI.py:1915
+msgid "Move Geo Item"
+msgstr "Move Geo Item"
+
+#: flatcamGUI/FlatCAMGUI.py:1916
+msgid "Within Add Arc will cycle through the ARC modes"
+msgstr "Within Add Arc will cycle through the ARC modes"
+
+#: flatcamGUI/FlatCAMGUI.py:1916
+msgid "Draw a Polygon"
+msgstr "Draw a Polygon"
+
+#: flatcamGUI/FlatCAMGUI.py:1916
+msgid "Draw a Circle"
+msgstr "Draw a Circle"
+
+#: flatcamGUI/FlatCAMGUI.py:1917
+msgid "Draw a Path"
+msgstr "Draw a Path"
+
+#: flatcamGUI/FlatCAMGUI.py:1917
+msgid "Draw Rectangle"
+msgstr "Draw Rectangle"
+
+#: flatcamGUI/FlatCAMGUI.py:1917
+msgid "Polygon Subtraction Tool"
+msgstr "Polygon Subtraction Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1917
+msgid "Add Text Tool"
+msgstr "Add Text Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1918
+msgid "Polygon Union Tool"
+msgstr "Polygon Union Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1918
+msgid "Flip shape on X axis"
+msgstr "Flip shape on X axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1918
+msgid "Flip shape on Y axis"
+msgstr "Flip shape on Y axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1919
+msgid "Skew shape on X axis"
+msgstr "Skew shape on X axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1919
+msgid "Skew shape on Y axis"
+msgstr "Skew shape on Y axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1919
+msgid "Editor Transformation Tool"
+msgstr "Editor Transformation Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1920
+msgid "Offset shape on X axis"
+msgstr "Offset shape on X axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1920
+msgid "Offset shape on Y axis"
+msgstr "Offset shape on Y axis"
+
+#: flatcamGUI/FlatCAMGUI.py:1921 flatcamGUI/FlatCAMGUI.py:2007
+#: flatcamGUI/FlatCAMGUI.py:2129
+msgid "Save Object and Exit Editor"
+msgstr "Save Object and Exit Editor"
+
+#: flatcamGUI/FlatCAMGUI.py:1921
+msgid "Polygon Cut Tool"
+msgstr "Polygon Cut Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:1922
+msgid "Rotate Geometry"
+msgstr "Rotate Geometry"
+
+#: flatcamGUI/FlatCAMGUI.py:1922
+msgid "Finish drawing for certain tools"
+msgstr "Finish drawing for certain tools"
+
+#: flatcamGUI/FlatCAMGUI.py:1922 flatcamGUI/FlatCAMGUI.py:2007
+#: flatcamGUI/FlatCAMGUI.py:2127
+msgid "Abort and return to Select"
+msgstr "Abort and return to Select"
+
+#: flatcamGUI/FlatCAMGUI.py:1923 flatcamGUI/FlatCAMGUI.py:2690
+msgid "Delete Shape"
+msgstr "Delete Shape"
+
+#: flatcamGUI/FlatCAMGUI.py:2003
+msgid "EXCELLON EDITOR"
+msgstr "EXCELLON EDITOR"
+
+#: flatcamGUI/FlatCAMGUI.py:2003
+msgid "Copy Drill(s)"
+msgstr "Copy Drill(s)"
+
+#: flatcamGUI/FlatCAMGUI.py:2003 flatcamGUI/FlatCAMGUI.py:2253
+msgid "Add Drill"
+msgstr "Add Drill"
+
+#: flatcamGUI/FlatCAMGUI.py:2004
+msgid "Move Drill(s)"
+msgstr "Move Drill(s)"
+
+#: flatcamGUI/FlatCAMGUI.py:2005
+msgid "Add a new Tool"
+msgstr "Add a new Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:2006
+msgid "Delete Drill(s)"
+msgstr "Delete Drill(s)"
+
+#: flatcamGUI/FlatCAMGUI.py:2006
+msgid "Alternate: Delete Tool(s)"
+msgstr "Alternate: Delete Tool(s)"
+
+#: flatcamGUI/FlatCAMGUI.py:2123
+msgid "GERBER EDITOR"
+msgstr "GERBER EDITOR"
+
+#: flatcamGUI/FlatCAMGUI.py:2123
+msgid "Add Disc"
+msgstr "Add Disc"
+
+#: flatcamGUI/FlatCAMGUI.py:2123
+msgid "Add SemiDisc"
+msgstr "Add SemiDisc"
+
+#: flatcamGUI/FlatCAMGUI.py:2125
+msgid "Within Track & Region Tools will cycle in REVERSE the bend modes"
+msgstr "Within Track & Region Tools will cycle in REVERSE the bend modes"
+
+#: flatcamGUI/FlatCAMGUI.py:2126
+msgid "Within Track & Region Tools will cycle FORWARD the bend modes"
+msgstr "Within Track & Region Tools will cycle FORWARD the bend modes"
+
+#: flatcamGUI/FlatCAMGUI.py:2127
+msgid "Alternate: Delete Apertures"
+msgstr "Alternate: Delete Apertures"
+
+#: flatcamGUI/FlatCAMGUI.py:2128
+msgid "Eraser Tool"
+msgstr "Eraser Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:2129 flatcamGUI/PreferencesUI.py:2816
+msgid "Mark Area Tool"
+msgstr "Mark Area Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:2129
+msgid "Poligonize Tool"
+msgstr "Poligonize Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:2129
+msgid "Transformation Tool"
+msgstr "Transformation Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:2146
+msgid "Toggle Visibility"
+msgstr "Toggle Visibility"
+
+#: flatcamGUI/FlatCAMGUI.py:2152
+msgid "New"
+msgstr "New"
+
+#: flatcamGUI/FlatCAMGUI.py:2154 flatcamGUI/ObjectUI.py:449
+#: flatcamObjects/FlatCAMGerber.py:238 flatcamObjects/FlatCAMGerber.py:326
+#: flatcamTools/ToolCalibration.py:631 flatcamTools/ToolCalibration.py:648
+#: flatcamTools/ToolCalibration.py:815 flatcamTools/ToolCopperThieving.py:144
+#: flatcamTools/ToolCopperThieving.py:158
+#: flatcamTools/ToolCopperThieving.py:604 flatcamTools/ToolCutOut.py:92
+#: flatcamTools/ToolDblSided.py:226 flatcamTools/ToolFilm.py:69
+#: flatcamTools/ToolFilm.py:102 flatcamTools/ToolFilm.py:549
+#: flatcamTools/ToolFilm.py:557 flatcamTools/ToolImage.py:49
+#: flatcamTools/ToolImage.py:271 flatcamTools/ToolNCC.py:95
+#: flatcamTools/ToolNCC.py:558 flatcamTools/ToolNCC.py:1295
+#: flatcamTools/ToolPaint.py:502 flatcamTools/ToolPaint.py:706
+#: flatcamTools/ToolPanelize.py:116 flatcamTools/ToolPanelize.py:372
+#: flatcamTools/ToolPanelize.py:389
+msgid "Geometry"
+msgstr "Geometry"
+
+#: flatcamGUI/FlatCAMGUI.py:2158 flatcamGUI/PreferencesUI.py:8410
+#: flatcamTools/ToolAlignObjects.py:74 flatcamTools/ToolAlignObjects.py:110
+#: flatcamTools/ToolCalibration.py:197 flatcamTools/ToolCalibration.py:631
+#: flatcamTools/ToolCalibration.py:648 flatcamTools/ToolCalibration.py:807
+#: flatcamTools/ToolCalibration.py:815 flatcamTools/ToolCopperThieving.py:144
+#: flatcamTools/ToolCopperThieving.py:158
+#: flatcamTools/ToolCopperThieving.py:604 flatcamTools/ToolDblSided.py:225
+#: flatcamTools/ToolFilm.py:359 flatcamTools/ToolNCC.py:558
+#: flatcamTools/ToolNCC.py:1295 flatcamTools/ToolPaint.py:502
+#: flatcamTools/ToolPaint.py:706 flatcamTools/ToolPanelize.py:372
+#: flatcamTools/ToolPunchGerber.py:149 flatcamTools/ToolPunchGerber.py:164
+msgid "Excellon"
+msgstr "Excellon"
+
+#: flatcamGUI/FlatCAMGUI.py:2165
+msgid "Grids"
+msgstr "Grids"
+
+#: flatcamGUI/FlatCAMGUI.py:2172
+msgid "Clear Plot"
+msgstr "Clear Plot"
+
+#: flatcamGUI/FlatCAMGUI.py:2174
+msgid "Replot"
+msgstr "Replot"
+
+#: flatcamGUI/FlatCAMGUI.py:2178
+msgid "Geo Editor"
+msgstr "Geo Editor"
+
+#: flatcamGUI/FlatCAMGUI.py:2180
+msgid "Path"
+msgstr "Path"
+
+#: flatcamGUI/FlatCAMGUI.py:2182
+msgid "Rectangle"
+msgstr "Rectangle"
+
+#: flatcamGUI/FlatCAMGUI.py:2185
+msgid "Circle"
+msgstr "Circle"
+
+#: flatcamGUI/FlatCAMGUI.py:2189
+msgid "Arc"
+msgstr "Arc"
+
+#: flatcamGUI/FlatCAMGUI.py:2203
+msgid "Union"
+msgstr "Union"
+
+#: flatcamGUI/FlatCAMGUI.py:2205
+msgid "Intersection"
+msgstr "Intersection"
+
+#: flatcamGUI/FlatCAMGUI.py:2207
+msgid "Subtraction"
+msgstr "Subtraction"
+
+#: flatcamGUI/FlatCAMGUI.py:2209 flatcamGUI/ObjectUI.py:2140
+#: flatcamGUI/PreferencesUI.py:4714
+msgid "Cut"
+msgstr "Cut"
+
+#: flatcamGUI/FlatCAMGUI.py:2220
+msgid "Pad"
+msgstr "Pad"
+
+#: flatcamGUI/FlatCAMGUI.py:2222
+msgid "Pad Array"
+msgstr "Pad Array"
+
+#: flatcamGUI/FlatCAMGUI.py:2226
+msgid "Track"
+msgstr "Track"
+
+#: flatcamGUI/FlatCAMGUI.py:2228
+msgid "Region"
+msgstr "Region"
+
+#: flatcamGUI/FlatCAMGUI.py:2251
+msgid "Exc Editor"
+msgstr "Exc Editor"
+
+#: flatcamGUI/FlatCAMGUI.py:2296
+msgid ""
+"Relative neasurement.\n"
+"Reference is last click position"
+msgstr ""
+"Relative neasurement.\n"
+"Reference is last click position"
+
+#: flatcamGUI/FlatCAMGUI.py:2302
+msgid ""
+"Absolute neasurement.\n"
+"Reference is (X=0, Y= 0) position"
+msgstr ""
+"Absolute neasurement.\n"
+"Reference is (X=0, Y= 0) position"
+
+#: flatcamGUI/FlatCAMGUI.py:2406
+msgid "Lock Toolbars"
+msgstr "Lock Toolbars"
+
+#: flatcamGUI/FlatCAMGUI.py:2465
+msgid "FlatCAM Preferences Folder opened."
+msgstr "FlatCAM Preferences Folder opened."
+
+#: flatcamGUI/FlatCAMGUI.py:2476
+msgid "Are you sure you want to delete the GUI Settings? \n"
+msgstr "Are you sure you want to delete the GUI Settings? \n"
+
+#: flatcamGUI/FlatCAMGUI.py:2584
+msgid "&Cutout Tool"
+msgstr "&Cutout Tool"
+
+#: flatcamGUI/FlatCAMGUI.py:2650
+msgid "Select 'Esc'"
+msgstr "Select 'Esc'"
+
+#: flatcamGUI/FlatCAMGUI.py:2688
+msgid "Copy Objects"
+msgstr "Copy Objects"
+
+#: flatcamGUI/FlatCAMGUI.py:2696
+msgid "Move Objects"
+msgstr "Move Objects"
+
+#: flatcamGUI/FlatCAMGUI.py:3312
+msgid ""
+"Please first select a geometry item to be cutted\n"
+"then select the geometry item that will be cutted\n"
+"out of the first item. In the end press ~X~ key or\n"
+"the toolbar button."
+msgstr ""
+"Please first select a geometry item to be cutted\n"
+"then select the geometry item that will be cutted\n"
+"out of the first item. In the end press ~X~ key or\n"
+"the toolbar button."
+
+#: flatcamGUI/FlatCAMGUI.py:3319 flatcamGUI/FlatCAMGUI.py:3478
+#: flatcamGUI/FlatCAMGUI.py:3523 flatcamGUI/FlatCAMGUI.py:3543
+msgid "Warning"
+msgstr "Warning"
+
+#: flatcamGUI/FlatCAMGUI.py:3473
+msgid ""
+"Please select geometry items \n"
+"on which to perform Intersection Tool."
+msgstr ""
+"Please select geometry items \n"
+"on which to perform Intersection Tool."
+
+#: flatcamGUI/FlatCAMGUI.py:3518
+msgid ""
+"Please select geometry items \n"
+"on which to perform Substraction Tool."
+msgstr ""
+"Please select geometry items \n"
+"on which to perform Substraction Tool."
+
+#: flatcamGUI/FlatCAMGUI.py:3538
+msgid ""
+"Please select geometry items \n"
+"on which to perform union."
+msgstr ""
+"Please select geometry items \n"
+"on which to perform union."
+
+#: flatcamGUI/FlatCAMGUI.py:3617 flatcamGUI/FlatCAMGUI.py:3828
+msgid "Cancelled. Nothing selected to delete."
+msgstr "Cancelled. Nothing selected to delete."
+
+#: flatcamGUI/FlatCAMGUI.py:3701 flatcamGUI/FlatCAMGUI.py:3944
+msgid "Cancelled. Nothing selected to copy."
+msgstr "Cancelled. Nothing selected to copy."
+
+#: flatcamGUI/FlatCAMGUI.py:3747 flatcamGUI/FlatCAMGUI.py:3973
+msgid "Cancelled. Nothing selected to move."
+msgstr "Cancelled. Nothing selected to move."
+
+#: flatcamGUI/FlatCAMGUI.py:3999
+msgid "New Tool ..."
+msgstr "New Tool ..."
+
+#: flatcamGUI/FlatCAMGUI.py:4000 flatcamTools/ToolNCC.py:924
+#: flatcamTools/ToolPaint.py:850 flatcamTools/ToolSolderPaste.py:560
+msgid "Enter a Tool Diameter"
+msgstr "Enter a Tool Diameter"
+
+#: flatcamGUI/FlatCAMGUI.py:4012
+msgid "Adding Tool cancelled ..."
+msgstr "Adding Tool cancelled ..."
+
+#: flatcamGUI/FlatCAMGUI.py:4025
+msgid "Distance Tool exit..."
+msgstr "Distance Tool exit..."
+
+#: flatcamGUI/FlatCAMGUI.py:4234 flatcamGUI/FlatCAMGUI.py:4241
+msgid "Idle."
+msgstr "Idle."
+
+#: flatcamGUI/FlatCAMGUI.py:4272
+msgid "Application started ..."
+msgstr "Application started ..."
+
+#: flatcamGUI/FlatCAMGUI.py:4273
+msgid "Hello!"
+msgstr "Hello!"
+
+#: flatcamGUI/FlatCAMGUI.py:4331
+msgid "Open Project ..."
+msgstr "Open Project ..."
+
+#: flatcamGUI/FlatCAMGUI.py:4357
+msgid "Exit"
+msgstr "Exit"
+
+#: flatcamGUI/GUIElements.py:2513 flatcamGUI/PreferencesUI.py:6313
+#: flatcamTools/ToolDblSided.py:173 flatcamTools/ToolDblSided.py:388
+#: flatcamTools/ToolFilm.py:219
+msgid "Reference"
+msgstr "Reference"
+
+#: flatcamGUI/GUIElements.py:2515
+msgid ""
+"The reference can be:\n"
+"- Absolute -> the reference point is point (0,0)\n"
+"- Relative -> the reference point is the mouse position before Jump"
+msgstr ""
+"The reference can be:\n"
+"- Absolute -> the reference point is point (0,0)\n"
+"- Relative -> the reference point is the mouse position before Jump"
+
+#: flatcamGUI/GUIElements.py:2520
+msgid "Abs"
+msgstr "Abs"
+
+#: flatcamGUI/GUIElements.py:2521
+msgid "Relative"
+msgstr "Relative"
+
+#: flatcamGUI/GUIElements.py:2531
+msgid "Location"
+msgstr "Location"
+
+#: flatcamGUI/GUIElements.py:2533
+msgid ""
+"The Location value is a tuple (x,y).\n"
+"If the reference is Absolute then the Jump will be at the position (x,y).\n"
+"If the reference is Relative then the Jump will be at the (x,y) distance\n"
+"from the current mouse location point."
+msgstr ""
+"The Location value is a tuple (x,y).\n"
+"If the reference is Absolute then the Jump will be at the position (x,y).\n"
+"If the reference is Relative then the Jump will be at the (x,y) distance\n"
+"from the current mouse location point."
+
+#: flatcamGUI/GUIElements.py:2573
+msgid "Save Log"
+msgstr "Save Log"
+
+#: flatcamGUI/GUIElements.py:2592 flatcamTools/ToolShell.py:285
+msgid "Type >help< to get started"
+msgstr "Type >help< to get started"
+
+#: flatcamGUI/ObjectUI.py:38
+msgid "FlatCAM Object"
+msgstr "FlatCAM Object"
+
+#: flatcamGUI/ObjectUI.py:77
+msgid ""
+"BASIC is suitable for a beginner. Many parameters\n"
+"are hidden from the user in this mode.\n"
+"ADVANCED mode will make available all parameters.\n"
+"\n"
+"To change the application LEVEL, go to:\n"
+"Edit -> Preferences -> General and check:\n"
+"'APP. LEVEL' radio button."
+msgstr ""
+"BASIC is suitable for a beginner. Many parameters\n"
+"are hidden from the user in this mode.\n"
+"ADVANCED mode will make available all parameters.\n"
+"\n"
+"To change the application LEVEL, go to:\n"
+"Edit -> Preferences -> General and check:\n"
+"'APP. LEVEL' radio button."
+
+#: flatcamGUI/ObjectUI.py:110
+msgid "Geometrical transformations of the current object."
+msgstr "Geometrical transformations of the current object."
+
+#: flatcamGUI/ObjectUI.py:119
+msgid ""
+"Factor by which to multiply\n"
+"geometric features of this object.\n"
+"Expressions are allowed. E.g: 1/25.4"
+msgstr ""
+"Factor by which to multiply\n"
+"geometric features of this object.\n"
+"Expressions are allowed. E.g: 1/25.4"
+
+#: flatcamGUI/ObjectUI.py:126
+msgid "Perform scaling operation."
+msgstr "Perform scaling operation."
+
+#: flatcamGUI/ObjectUI.py:137
+msgid ""
+"Amount by which to move the object\n"
+"in the x and y axes in (x, y) format.\n"
+"Expressions are allowed. E.g: (1/3.2, 0.5*3)"
+msgstr ""
+"Amount by which to move the object\n"
+"in the x and y axes in (x, y) format.\n"
+"Expressions are allowed. E.g: (1/3.2, 0.5*3)"
+
+#: flatcamGUI/ObjectUI.py:144
+msgid "Perform the offset operation."
+msgstr "Perform the offset operation."
+
+#: flatcamGUI/ObjectUI.py:177
+msgid "Gerber Object"
+msgstr "Gerber Object"
+
+#: flatcamGUI/ObjectUI.py:186 flatcamGUI/ObjectUI.py:729
+#: flatcamGUI/ObjectUI.py:1425 flatcamGUI/ObjectUI.py:2124
+#: flatcamGUI/PreferencesUI.py:1940 flatcamGUI/PreferencesUI.py:2856
+#: flatcamGUI/PreferencesUI.py:4121 flatcamGUI/PreferencesUI.py:4688
+msgid "Plot Options"
+msgstr "Plot Options"
+
+#: flatcamGUI/ObjectUI.py:192 flatcamGUI/ObjectUI.py:730
+#: flatcamGUI/PreferencesUI.py:1947 flatcamGUI/PreferencesUI.py:2868
+#: flatcamGUI/PreferencesUI.py:7728 flatcamTools/ToolCopperThieving.py:191
+msgid "Solid"
+msgstr "Solid"
+
+#: flatcamGUI/ObjectUI.py:194 flatcamGUI/PreferencesUI.py:1949
+msgid "Solid color polygons."
+msgstr "Solid color polygons."
+
+#: flatcamGUI/ObjectUI.py:200
+msgid "Multi-Color"
+msgstr "Multi-Color"
+
+#: flatcamGUI/ObjectUI.py:202 flatcamGUI/PreferencesUI.py:1956
+msgid "Draw polygons in different colors."
+msgstr "Draw polygons in different colors."
+
+#: flatcamGUI/ObjectUI.py:208 flatcamGUI/ObjectUI.py:768
+#: flatcamGUI/PreferencesUI.py:1961 flatcamGUI/PreferencesUI.py:2862
+#: flatcamGUI/PreferencesUI.py:4125
+msgid "Plot"
+msgstr "Plot"
+
+#: flatcamGUI/ObjectUI.py:210 flatcamGUI/ObjectUI.py:770
+#: flatcamGUI/ObjectUI.py:1485 flatcamGUI/ObjectUI.py:2234
+#: flatcamGUI/PreferencesUI.py:1963 flatcamGUI/PreferencesUI.py:4127
+#: flatcamGUI/PreferencesUI.py:4699
+msgid "Plot (show) this object."
+msgstr "Plot (show) this object."
+
+#: flatcamGUI/ObjectUI.py:238
+msgid ""
+"Toggle the display of the Gerber Apertures Table.\n"
+"When unchecked, it will delete all mark shapes\n"
+"that are drawn on canvas."
+msgstr ""
+"Toggle the display of the Gerber Apertures Table.\n"
+"When unchecked, it will delete all mark shapes\n"
+"that are drawn on canvas."
+
+#: flatcamGUI/ObjectUI.py:248
+msgid "Mark All"
+msgstr "Mark All"
+
+#: flatcamGUI/ObjectUI.py:250
+msgid ""
+"When checked it will display all the apertures.\n"
+"When unchecked, it will delete all mark shapes\n"
+"that are drawn on canvas."
+msgstr ""
+"When checked it will display all the apertures.\n"
+"When unchecked, it will delete all mark shapes\n"
+"that are drawn on canvas."
+
+#: flatcamGUI/ObjectUI.py:278
+msgid "Mark the aperture instances on canvas."
+msgstr "Mark the aperture instances on canvas."
+
+#: flatcamGUI/ObjectUI.py:290 flatcamGUI/PreferencesUI.py:2194
+msgid "Isolation Routing"
+msgstr "Isolation Routing"
+
+#: flatcamGUI/ObjectUI.py:292 flatcamGUI/PreferencesUI.py:2196
+msgid ""
+"Create a Geometry object with\n"
+"toolpaths to cut outside polygons."
+msgstr ""
+"Create a Geometry object with\n"
+"toolpaths to cut outside polygons."
+
+#: flatcamGUI/ObjectUI.py:310 flatcamGUI/PreferencesUI.py:2399
+msgid ""
+"Choose what tool to use for Gerber isolation:\n"
+"'Circular' or 'V-shape'.\n"
+"When the 'V-shape' is selected then the tool\n"
+"diameter will depend on the chosen cut depth."
+msgstr ""
+"Choose what tool to use for Gerber isolation:\n"
+"'Circular' or 'V-shape'.\n"
+"When the 'V-shape' is selected then the tool\n"
+"diameter will depend on the chosen cut depth."
+
+#: flatcamGUI/ObjectUI.py:316
+msgid "V-Shape"
+msgstr "V-Shape"
+
+#: flatcamGUI/ObjectUI.py:322 flatcamGUI/ObjectUI.py:1671
+#: flatcamGUI/PreferencesUI.py:2411 flatcamGUI/PreferencesUI.py:5351
+#: flatcamGUI/PreferencesUI.py:5917 flatcamGUI/PreferencesUI.py:5924
+#: flatcamTools/ToolNCC.py:233 flatcamTools/ToolNCC.py:240
+#: flatcamTools/ToolPaint.py:216
+msgid "V-Tip Dia"
+msgstr "V-Tip Dia"
+
+#: flatcamGUI/ObjectUI.py:324 flatcamGUI/ObjectUI.py:1674
+#: flatcamGUI/PreferencesUI.py:2413 flatcamGUI/PreferencesUI.py:5353
+#: flatcamGUI/PreferencesUI.py:5919 flatcamTools/ToolNCC.py:235
+#: flatcamTools/ToolPaint.py:218
+msgid "The tip diameter for V-Shape Tool"
+msgstr "The tip diameter for V-Shape Tool"
+
+#: flatcamGUI/ObjectUI.py:335 flatcamGUI/ObjectUI.py:1686
+#: flatcamGUI/PreferencesUI.py:2424 flatcamGUI/PreferencesUI.py:5363
+#: flatcamGUI/PreferencesUI.py:5930 flatcamGUI/PreferencesUI.py:5938
+#: flatcamTools/ToolNCC.py:246 flatcamTools/ToolNCC.py:254
+#: flatcamTools/ToolPaint.py:229
+msgid "V-Tip Angle"
+msgstr "V-Tip Angle"
+
+#: flatcamGUI/ObjectUI.py:337 flatcamGUI/ObjectUI.py:1689
+#: flatcamGUI/PreferencesUI.py:2426 flatcamGUI/PreferencesUI.py:5365
+#: flatcamGUI/PreferencesUI.py:5932 flatcamTools/ToolNCC.py:248
+#: flatcamTools/ToolPaint.py:231
+msgid ""
+"The tip angle for V-Shape Tool.\n"
+"In degree."
+msgstr ""
+"The tip angle for V-Shape Tool.\n"
+"In degree."
+
+#: flatcamGUI/ObjectUI.py:351 flatcamGUI/ObjectUI.py:1705
+#: flatcamGUI/PreferencesUI.py:2439 flatcamGUI/PreferencesUI.py:4243
+#: flatcamGUI/PreferencesUI.py:5669 flatcamTools/ToolCutOut.py:141
+msgid ""
+"Cutting depth (negative)\n"
+"below the copper surface."
+msgstr ""
+"Cutting depth (negative)\n"
+"below the copper surface."
+
+#: flatcamGUI/ObjectUI.py:365
+msgid ""
+"Diameter of the cutting tool.\n"
+"If you want to have an isolation path\n"
+"inside the actual shape of the Gerber\n"
+"feature, use a negative value for\n"
+"this parameter."
+msgstr ""
+"Diameter of the cutting tool.\n"
+"If you want to have an isolation path\n"
+"inside the actual shape of the Gerber\n"
+"feature, use a negative value for\n"
+"this parameter."
+
+#: flatcamGUI/ObjectUI.py:381 flatcamGUI/PreferencesUI.py:2218
+msgid "# Passes"
+msgstr "# Passes"
+
+#: flatcamGUI/ObjectUI.py:383 flatcamGUI/PreferencesUI.py:2220
+msgid ""
+"Width of the isolation gap in\n"
+"number (integer) of tool widths."
+msgstr ""
+"Width of the isolation gap in\n"
+"number (integer) of tool widths."
+
+#: flatcamGUI/ObjectUI.py:394 flatcamGUI/PreferencesUI.py:2230
+msgid "Pass overlap"
+msgstr "Pass overlap"
+
+#: flatcamGUI/ObjectUI.py:396 flatcamGUI/PreferencesUI.py:2232
+msgid "How much (percentage) of the tool width to overlap each tool pass."
+msgstr "How much (percentage) of the tool width to overlap each tool pass."
+
+#: flatcamGUI/ObjectUI.py:410 flatcamGUI/PreferencesUI.py:2259
+#: flatcamGUI/PreferencesUI.py:4667
+msgid ""
+"Milling type:\n"
+"- climb / best for precision milling and to reduce tool usage\n"
+"- conventional / useful when there is no backlash compensation"
+msgstr ""
+"Milling type:\n"
+"- climb / best for precision milling and to reduce tool usage\n"
+"- conventional / useful when there is no backlash compensation"
+
+#: flatcamGUI/ObjectUI.py:420
+msgid "Combine"
+msgstr "Combine"
+
+#: flatcamGUI/ObjectUI.py:422 flatcamGUI/PreferencesUI.py:2271
+msgid "Combine all passes into one object"
+msgstr "Combine all passes into one object"
+
+#: flatcamGUI/ObjectUI.py:426 flatcamGUI/PreferencesUI.py:2373
+msgid "\"Follow\""
+msgstr "\"Follow\""
+
+#: flatcamGUI/ObjectUI.py:427 flatcamGUI/PreferencesUI.py:2375
+msgid ""
+"Generate a 'Follow' geometry.\n"
+"This means that it will cut through\n"
+"the middle of the trace."
+msgstr ""
+"Generate a 'Follow' geometry.\n"
+"This means that it will cut through\n"
+"the middle of the trace."
+
+#: flatcamGUI/ObjectUI.py:433
+msgid "Except"
+msgstr "Except"
+
+#: flatcamGUI/ObjectUI.py:436
+msgid ""
+"When the isolation geometry is generated,\n"
+"by checking this, the area of the object bellow\n"
+"will be subtracted from the isolation geometry."
+msgstr ""
+"When the isolation geometry is generated,\n"
+"by checking this, the area of the object bellow\n"
+"will be subtracted from the isolation geometry."
+
+#: flatcamGUI/ObjectUI.py:449 flatcamGUI/PreferencesUI.py:6527
+#: flatcamObjects/FlatCAMGerber.py:238 flatcamObjects/FlatCAMGerber.py:326
+#: flatcamTools/ToolAlignObjects.py:73 flatcamTools/ToolAlignObjects.py:109
+#: flatcamTools/ToolCalibration.py:196 flatcamTools/ToolCalibration.py:631
+#: flatcamTools/ToolCalibration.py:648 flatcamTools/ToolCalibration.py:807
+#: flatcamTools/ToolCalibration.py:815 flatcamTools/ToolCopperThieving.py:144
+#: flatcamTools/ToolCopperThieving.py:158
+#: flatcamTools/ToolCopperThieving.py:604 flatcamTools/ToolCutOut.py:91
+#: flatcamTools/ToolDblSided.py:224 flatcamTools/ToolFilm.py:69
+#: flatcamTools/ToolFilm.py:102 flatcamTools/ToolFilm.py:549
+#: flatcamTools/ToolFilm.py:557 flatcamTools/ToolImage.py:49
+#: flatcamTools/ToolImage.py:252 flatcamTools/ToolImage.py:273
+#: flatcamTools/ToolNCC.py:96 flatcamTools/ToolNCC.py:558
+#: flatcamTools/ToolNCC.py:1295 flatcamTools/ToolPaint.py:502
+#: flatcamTools/ToolPaint.py:706 flatcamTools/ToolPanelize.py:116
+#: flatcamTools/ToolPanelize.py:202 flatcamTools/ToolPanelize.py:372
+#: flatcamTools/ToolPanelize.py:389
+msgid "Gerber"
+msgstr "Gerber"
+
+#: flatcamGUI/ObjectUI.py:456 flatcamTools/ToolNCC.py:86
+#: flatcamTools/ToolPaint.py:80
+msgid "Obj Type"
+msgstr "Obj Type"
+
+#: flatcamGUI/ObjectUI.py:458
+msgid ""
+"Specify the type of object to be excepted from isolation.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+msgstr ""
+"Specify the type of object to be excepted from isolation.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+
+#: flatcamGUI/ObjectUI.py:471 flatcamGUI/PreferencesUI.py:8028
+#: flatcamTools/ToolCalibration.py:186 flatcamTools/ToolNCC.py:109
+#: flatcamTools/ToolPaint.py:103 flatcamTools/ToolPanelize.py:98
+#: flatcamTools/ToolQRCode.py:78
+msgid "Object"
+msgstr "Object"
+
+#: flatcamGUI/ObjectUI.py:472
+msgid "Object whose area will be removed from isolation geometry."
+msgstr "Object whose area will be removed from isolation geometry."
+
+#: flatcamGUI/ObjectUI.py:479 flatcamGUI/PreferencesUI.py:2244
+msgid "Scope"
+msgstr "Scope"
+
+#: flatcamGUI/ObjectUI.py:481 flatcamGUI/PreferencesUI.py:2246
+msgid ""
+"Isolation scope. Choose what to isolate:\n"
+"- 'All' -> Isolate all the polygons in the object\n"
+"- 'Selection' -> Isolate a selection of polygons."
+msgstr ""
+"Isolation scope. Choose what to isolate:\n"
+"- 'All' -> Isolate all the polygons in the object\n"
+"- 'Selection' -> Isolate a selection of polygons."
+
+#: flatcamGUI/ObjectUI.py:486 flatcamGUI/PreferencesUI.py:624
+#: flatcamGUI/PreferencesUI.py:2251 flatcamGUI/PreferencesUI.py:5590
+#: flatcamGUI/PreferencesUI.py:6097 flatcamTools/ToolNCC.py:539
+#: flatcamTools/ToolPaint.py:456
+msgid "Selection"
+msgstr "Selection"
+
+#: flatcamGUI/ObjectUI.py:494 flatcamGUI/PreferencesUI.py:2452
+msgid "Isolation Type"
+msgstr "Isolation Type"
+
+#: flatcamGUI/ObjectUI.py:496 flatcamGUI/PreferencesUI.py:2454
+msgid ""
+"Choose how the isolation will be executed:\n"
+"- 'Full' -> complete isolation of polygons\n"
+"- 'Ext' -> will isolate only on the outside\n"
+"- 'Int' -> will isolate only on the inside\n"
+"'Exterior' isolation is almost always possible\n"
+"(with the right tool) but 'Interior'\n"
+"isolation can be done only when there is an opening\n"
+"inside of the polygon (e.g polygon is a 'doughnut' shape)."
+msgstr ""
+"Choose how the isolation will be executed:\n"
+"- 'Full' -> complete isolation of polygons\n"
+"- 'Ext' -> will isolate only on the outside\n"
+"- 'Int' -> will isolate only on the inside\n"
+"'Exterior' isolation is almost always possible\n"
+"(with the right tool) but 'Interior'\n"
+"isolation can be done only when there is an opening\n"
+"inside of the polygon (e.g polygon is a 'doughnut' shape)."
+
+#: flatcamGUI/ObjectUI.py:505 flatcamGUI/PreferencesUI.py:2463
+#: flatcamGUI/PreferencesUI.py:2484
+msgid "Full"
+msgstr "Full"
+
+#: flatcamGUI/ObjectUI.py:506
+msgid "Ext"
+msgstr "Ext"
+
+#: flatcamGUI/ObjectUI.py:507
+msgid "Int"
+msgstr "Int"
+
+#: flatcamGUI/ObjectUI.py:512
+msgid "Generate Isolation Geometry"
+msgstr "Generate Isolation Geometry"
+
+#: flatcamGUI/ObjectUI.py:520
+msgid ""
+"Create a Geometry object with toolpaths to cut \n"
+"isolation outside, inside or on both sides of the\n"
+"object. For a Gerber object outside means outside\n"
+"of the Gerber feature and inside means inside of\n"
+"the Gerber feature, if possible at all. This means\n"
+"that only if the Gerber feature has openings inside, they\n"
+"will be isolated. If what is wanted is to cut isolation\n"
+"inside the actual Gerber feature, use a negative tool\n"
+"diameter above."
+msgstr ""
+"Create a Geometry object with toolpaths to cut \n"
+"isolation outside, inside or on both sides of the\n"
+"object. For a Gerber object outside means outside\n"
+"of the Gerber feature and inside means inside of\n"
+"the Gerber feature, if possible at all. This means\n"
+"that only if the Gerber feature has openings inside, they\n"
+"will be isolated. If what is wanted is to cut isolation\n"
+"inside the actual Gerber feature, use a negative tool\n"
+"diameter above."
+
+#: flatcamGUI/ObjectUI.py:532
+msgid "Buffer Solid Geometry"
+msgstr "Buffer Solid Geometry"
+
+#: flatcamGUI/ObjectUI.py:534
+msgid ""
+"This button is shown only when the Gerber file\n"
+"is loaded without buffering.\n"
+"Clicking this will create the buffered geometry\n"
+"required for isolation."
+msgstr ""
+"This button is shown only when the Gerber file\n"
+"is loaded without buffering.\n"
+"Clicking this will create the buffered geometry\n"
+"required for isolation."
+
+#: flatcamGUI/ObjectUI.py:566
+msgid "Clear N-copper"
+msgstr "Clear N-copper"
+
+#: flatcamGUI/ObjectUI.py:568 flatcamGUI/PreferencesUI.py:5312
+msgid ""
+"Create a Geometry object with\n"
+"toolpaths to cut all non-copper regions."
+msgstr ""
+"Create a Geometry object with\n"
+"toolpaths to cut all non-copper regions."
+
+#: flatcamGUI/ObjectUI.py:575 flatcamGUI/ObjectUI.py:2078
+#: flatcamTools/ToolNCC.py:599
+msgid ""
+"Create the Geometry Object\n"
+"for non-copper routing."
+msgstr ""
+"Create the Geometry Object\n"
+"for non-copper routing."
+
+#: flatcamGUI/ObjectUI.py:588
+msgid "Board cutout"
+msgstr "Board cutout"
+
+#: flatcamGUI/ObjectUI.py:590 flatcamGUI/PreferencesUI.py:5642
+msgid ""
+"Create toolpaths to cut around\n"
+"the PCB and separate it from\n"
+"the original board."
+msgstr ""
+"Create toolpaths to cut around\n"
+"the PCB and separate it from\n"
+"the original board."
+
+#: flatcamGUI/ObjectUI.py:597
+msgid ""
+"Generate the geometry for\n"
+"the board cutout."
+msgstr ""
+"Generate the geometry for\n"
+"the board cutout."
+
+#: flatcamGUI/ObjectUI.py:615 flatcamGUI/PreferencesUI.py:2281
+msgid "Non-copper regions"
+msgstr "Non-copper regions"
+
+#: flatcamGUI/ObjectUI.py:617 flatcamGUI/PreferencesUI.py:2283
+msgid ""
+"Create polygons covering the\n"
+"areas without copper on the PCB.\n"
+"Equivalent to the inverse of this\n"
+"object. Can be used to remove all\n"
+"copper from a specified region."
+msgstr ""
+"Create polygons covering the\n"
+"areas without copper on the PCB.\n"
+"Equivalent to the inverse of this\n"
+"object. Can be used to remove all\n"
+"copper from a specified region."
+
+#: flatcamGUI/ObjectUI.py:627 flatcamGUI/ObjectUI.py:668
+#: flatcamGUI/PreferencesUI.py:2295 flatcamGUI/PreferencesUI.py:2328
+msgid "Boundary Margin"
+msgstr "Boundary Margin"
+
+#: flatcamGUI/ObjectUI.py:629 flatcamGUI/PreferencesUI.py:2297
+msgid ""
+"Specify the edge of the PCB\n"
+"by drawing a box around all\n"
+"objects with this minimum\n"
+"distance."
+msgstr ""
+"Specify the edge of the PCB\n"
+"by drawing a box around all\n"
+"objects with this minimum\n"
+"distance."
+
+#: flatcamGUI/ObjectUI.py:644 flatcamGUI/ObjectUI.py:682
+#: flatcamGUI/PreferencesUI.py:2310 flatcamGUI/PreferencesUI.py:2341
+msgid "Rounded Geo"
+msgstr "Rounded Geo"
+
+#: flatcamGUI/ObjectUI.py:646 flatcamGUI/PreferencesUI.py:2312
+msgid "Resulting geometry will have rounded corners."
+msgstr "Resulting geometry will have rounded corners."
+
+#: flatcamGUI/ObjectUI.py:650 flatcamGUI/ObjectUI.py:691
+#: flatcamTools/ToolSolderPaste.py:134
+msgid "Generate Geo"
+msgstr "Generate Geo"
+
+#: flatcamGUI/ObjectUI.py:660 flatcamGUI/PreferencesUI.py:2322
+#: flatcamGUI/PreferencesUI.py:7558 flatcamTools/ToolPanelize.py:99
+#: flatcamTools/ToolQRCode.py:192
+msgid "Bounding Box"
+msgstr "Bounding Box"
+
+#: flatcamGUI/ObjectUI.py:662
+msgid ""
+"Create a geometry surrounding the Gerber object.\n"
+"Square shape."
+msgstr ""
+"Create a geometry surrounding the Gerber object.\n"
+"Square shape."
+
+#: flatcamGUI/ObjectUI.py:670 flatcamGUI/PreferencesUI.py:2330
+msgid ""
+"Distance of the edges of the box\n"
+"to the nearest polygon."
+msgstr ""
+"Distance of the edges of the box\n"
+"to the nearest polygon."
+
+#: flatcamGUI/ObjectUI.py:684 flatcamGUI/PreferencesUI.py:2343
+msgid ""
+"If the bounding box is \n"
+"to have rounded corners\n"
+"their radius is equal to\n"
+"the margin."
+msgstr ""
+"If the bounding box is \n"
+"to have rounded corners\n"
+"their radius is equal to\n"
+"the margin."
+
+#: flatcamGUI/ObjectUI.py:693
+msgid "Generate the Geometry object."
+msgstr "Generate the Geometry object."
+
+#: flatcamGUI/ObjectUI.py:720
+msgid "Excellon Object"
+msgstr "Excellon Object"
+
+#: flatcamGUI/ObjectUI.py:732
+msgid "Solid circles."
+msgstr "Solid circles."
+
+#: flatcamGUI/ObjectUI.py:780 flatcamGUI/ObjectUI.py:875
+#: flatcamGUI/ObjectUI.py:2255 flatcamGUI/PreferencesUI.py:3289
+#: flatcamTools/ToolProperties.py:166
+msgid "Drills"
+msgstr "Drills"
+
+#: flatcamGUI/ObjectUI.py:780 flatcamGUI/ObjectUI.py:876
+#: flatcamGUI/ObjectUI.py:2255 flatcamGUI/PreferencesUI.py:3290
+#: flatcamGUI/PreferencesUI.py:3961 flatcamTools/ToolProperties.py:168
+msgid "Slots"
+msgstr "Slots"
+
+#: flatcamGUI/ObjectUI.py:785
+msgid ""
+"This is the Tool Number.\n"
+"When ToolChange is checked, on toolchange event this value\n"
+"will be showed as a T1, T2 ... Tn in the Machine Code.\n"
+"\n"
+"Here the tools are selected for G-code generation."
+msgstr ""
+"This is the Tool Number.\n"
+"When ToolChange is checked, on toolchange event this value\n"
+"will be showed as a T1, T2 ... Tn in the Machine Code.\n"
+"\n"
+"Here the tools are selected for G-code generation."
+
+#: flatcamGUI/ObjectUI.py:790 flatcamGUI/ObjectUI.py:1509
+#: flatcamTools/ToolPaint.py:142
+msgid ""
+"Tool Diameter. It's value (in current FlatCAM units) \n"
+"is the cut width into the material."
+msgstr ""
+"Tool Diameter. It's value (in current FlatCAM units) \n"
+"is the cut width into the material."
+
+#: flatcamGUI/ObjectUI.py:793
+msgid ""
+"The number of Drill holes. Holes that are drilled with\n"
+"a drill bit."
+msgstr ""
+"The number of Drill holes. Holes that are drilled with\n"
+"a drill bit."
+
+#: flatcamGUI/ObjectUI.py:796
+msgid ""
+"The number of Slot holes. Holes that are created by\n"
+"milling them with an endmill bit."
+msgstr ""
+"The number of Slot holes. Holes that are created by\n"
+"milling them with an endmill bit."
+
+#: flatcamGUI/ObjectUI.py:799
+msgid ""
+"Toggle display of the drills for the current tool.\n"
+"This does not select the tools for G-code generation."
+msgstr ""
+"Toggle display of the drills for the current tool.\n"
+"This does not select the tools for G-code generation."
+
+#: flatcamGUI/ObjectUI.py:817 flatcamGUI/ObjectUI.py:1661
+#: flatcamObjects/FlatCAMExcellon.py:514 flatcamObjects/FlatCAMExcellon.py:726
+#: flatcamObjects/FlatCAMExcellon.py:742 flatcamObjects/FlatCAMExcellon.py:746
+#: flatcamObjects/FlatCAMGeometry.py:306 flatcamObjects/FlatCAMGeometry.py:731
+#: flatcamObjects/FlatCAMGeometry.py:767 flatcamTools/ToolNCC.py:331
+#: flatcamTools/ToolNCC.py:797 flatcamTools/ToolNCC.py:811
+#: flatcamTools/ToolNCC.py:1191 flatcamTools/ToolPaint.py:314
+#: flatcamTools/ToolPaint.py:767 flatcamTools/ToolPaint.py:779
+#: flatcamTools/ToolPaint.py:1166
+msgid "Parameters for"
+msgstr "Parameters for"
+
+#: flatcamGUI/ObjectUI.py:820 flatcamGUI/ObjectUI.py:1664
+#: flatcamTools/ToolNCC.py:334 flatcamTools/ToolPaint.py:317
+msgid ""
+"The data used for creating GCode.\n"
+"Each tool store it's own set of such data."
+msgstr ""
+"The data used for creating GCode.\n"
+"Each tool store it's own set of such data."
+
+#: flatcamGUI/ObjectUI.py:846 flatcamGUI/PreferencesUI.py:3266
+msgid ""
+"Operation type:\n"
+"- Drilling -> will drill the drills/slots associated with this tool\n"
+"- Milling -> will mill the drills/slots"
+msgstr ""
+"Operation type:\n"
+"- Drilling -> will drill the drills/slots associated with this tool\n"
+"- Milling -> will mill the drills/slots"
+
+#: flatcamGUI/ObjectUI.py:852 flatcamGUI/PreferencesUI.py:3272
+msgid "Drilling"
+msgstr "Drilling"
+
+#: flatcamGUI/ObjectUI.py:853 flatcamGUI/PreferencesUI.py:3273
+msgid "Milling"
+msgstr "Milling"
+
+#: flatcamGUI/ObjectUI.py:868 flatcamGUI/PreferencesUI.py:3282
+msgid ""
+"Milling type:\n"
+"- Drills -> will mill the drills associated with this tool\n"
+"- Slots -> will mill the slots associated with this tool\n"
+"- Both -> will mill both drills and mills or whatever is available"
+msgstr ""
+"Milling type:\n"
+"- Drills -> will mill the drills associated with this tool\n"
+"- Slots -> will mill the slots associated with this tool\n"
+"- Both -> will mill both drills and mills or whatever is available"
+
+#: flatcamGUI/ObjectUI.py:877 flatcamGUI/PreferencesUI.py:3291
+#: flatcamGUI/PreferencesUI.py:6343 flatcamTools/ToolFilm.py:258
+msgid "Both"
+msgstr "Both"
+
+#: flatcamGUI/ObjectUI.py:885 flatcamGUI/PreferencesUI.py:3298
+msgid "Milling Diameter"
+msgstr "Milling Diameter"
+
+#: flatcamGUI/ObjectUI.py:887 flatcamGUI/PreferencesUI.py:3300
+msgid "The diameter of the tool who will do the milling"
+msgstr "The diameter of the tool who will do the milling"
+
+#: flatcamGUI/ObjectUI.py:901 flatcamGUI/PreferencesUI.py:3313
+msgid ""
+"Drill depth (negative)\n"
+"below the copper surface."
+msgstr ""
+"Drill depth (negative)\n"
+"below the copper surface."
+
+#: flatcamGUI/ObjectUI.py:920 flatcamGUI/ObjectUI.py:1723
+#: flatcamGUI/PreferencesUI.py:3331 flatcamGUI/PreferencesUI.py:4261
+#: flatcamGUI/PreferencesUI.py:5687 flatcamTools/ToolCutOut.py:159
+msgid "Multi-Depth"
+msgstr "Multi-Depth"
+
+#: flatcamGUI/ObjectUI.py:923 flatcamGUI/ObjectUI.py:1726
+#: flatcamGUI/PreferencesUI.py:3334 flatcamGUI/PreferencesUI.py:4264
+#: flatcamGUI/PreferencesUI.py:5690 flatcamTools/ToolCutOut.py:162
+msgid ""
+"Use multiple passes to limit\n"
+"the cut depth in each pass. Will\n"
+"cut multiple times until Cut Z is\n"
+"reached."
+msgstr ""
+"Use multiple passes to limit\n"
+"the cut depth in each pass. Will\n"
+"cut multiple times until Cut Z is\n"
+"reached."
+
+#: flatcamGUI/ObjectUI.py:936 flatcamGUI/ObjectUI.py:1740
+#: flatcamGUI/PreferencesUI.py:3346 flatcamGUI/PreferencesUI.py:5702
+#: flatcamTools/ToolCutOut.py:176
+msgid "Depth of each pass (positive)."
+msgstr "Depth of each pass (positive)."
+
+#: flatcamGUI/ObjectUI.py:947 flatcamGUI/PreferencesUI.py:3354
+msgid ""
+"Tool height when travelling\n"
+"across the XY plane."
+msgstr ""
+"Tool height when travelling\n"
+"across the XY plane."
+
+#: flatcamGUI/ObjectUI.py:968 flatcamGUI/ObjectUI.py:1770
+#: flatcamGUI/PreferencesUI.py:4380
+msgid ""
+"Cutting speed in the XY\n"
+"plane in units per minute"
+msgstr ""
+"Cutting speed in the XY\n"
+"plane in units per minute"
+
+#: flatcamGUI/ObjectUI.py:983 flatcamGUI/PreferencesUI.py:3427
+msgid ""
+"Tool speed while drilling\n"
+"(in units per minute).\n"
+"So called 'Plunge' feedrate.\n"
+"This is for linear move G01."
+msgstr ""
+"Tool speed while drilling\n"
+"(in units per minute).\n"
+"So called 'Plunge' feedrate.\n"
+"This is for linear move G01."
+
+#: flatcamGUI/ObjectUI.py:998 flatcamGUI/ObjectUI.py:1797
+#: flatcamGUI/PreferencesUI.py:3597 flatcamGUI/PreferencesUI.py:4503
+msgid "Feedrate Rapids"
+msgstr "Feedrate Rapids"
+
+#: flatcamGUI/ObjectUI.py:1000 flatcamGUI/PreferencesUI.py:3599
+msgid ""
+"Tool speed while drilling\n"
+"(in units per minute).\n"
+"This is for the rapid move G00.\n"
+"It is useful only for Marlin,\n"
+"ignore for any other cases."
+msgstr ""
+"Tool speed while drilling\n"
+"(in units per minute).\n"
+"This is for the rapid move G00.\n"
+"It is useful only for Marlin,\n"
+"ignore for any other cases."
+
+#: flatcamGUI/ObjectUI.py:1020 flatcamGUI/ObjectUI.py:1817
+#: flatcamGUI/PreferencesUI.py:4521
+msgid "Re-cut"
+msgstr "Re-cut"
+
+#: flatcamGUI/ObjectUI.py:1022 flatcamGUI/ObjectUI.py:1035
+#: flatcamGUI/ObjectUI.py:1819 flatcamGUI/ObjectUI.py:1831
+#: flatcamGUI/PreferencesUI.py:4523 flatcamGUI/PreferencesUI.py:4535
+msgid ""
+"In order to remove possible\n"
+"copper leftovers where first cut\n"
+"meet with last cut, we generate an\n"
+"extended cut over the first cut section."
+msgstr ""
+"In order to remove possible\n"
+"copper leftovers where first cut\n"
+"meet with last cut, we generate an\n"
+"extended cut over the first cut section."
+
+#: flatcamGUI/ObjectUI.py:1048 flatcamGUI/ObjectUI.py:1840
+#: flatcamGUI/PreferencesUI.py:4409 flatcamObjects/FlatCAMExcellon.py:1332
+#: flatcamObjects/FlatCAMGeometry.py:1568
+msgid "Spindle speed"
+msgstr "Spindle speed"
+
+#: flatcamGUI/ObjectUI.py:1050 flatcamGUI/PreferencesUI.py:3442
+msgid ""
+"Speed of the spindle\n"
+"in RPM (optional)"
+msgstr ""
+"Speed of the spindle\n"
+"in RPM (optional)"
+
+#: flatcamGUI/ObjectUI.py:1065 flatcamGUI/ObjectUI.py:1859
+#: flatcamGUI/PreferencesUI.py:3456 flatcamGUI/PreferencesUI.py:4427
+msgid ""
+"Pause to allow the spindle to reach its\n"
+"speed before cutting."
+msgstr ""
+"Pause to allow the spindle to reach its\n"
+"speed before cutting."
+
+#: flatcamGUI/ObjectUI.py:1076 flatcamGUI/ObjectUI.py:1869
+#: flatcamGUI/PreferencesUI.py:3464 flatcamGUI/PreferencesUI.py:4432
+msgid "Number of time units for spindle to dwell."
+msgstr "Number of time units for spindle to dwell."
+
+#: flatcamGUI/ObjectUI.py:1086 flatcamGUI/PreferencesUI.py:3563
+msgid "Offset Z"
+msgstr "Offset Z"
+
+#: flatcamGUI/ObjectUI.py:1088 flatcamGUI/PreferencesUI.py:3565
+msgid ""
+"Some drill bits (the larger ones) need to drill deeper\n"
+"to create the desired exit hole diameter due of the tip shape.\n"
+"The value here can compensate the Cut Z parameter."
+msgstr ""
+"Some drill bits (the larger ones) need to drill deeper\n"
+"to create the desired exit hole diameter due of the tip shape.\n"
+"The value here can compensate the Cut Z parameter."
+
+#: flatcamGUI/ObjectUI.py:1148 flatcamGUI/ObjectUI.py:1923
+#: flatcamTools/ToolNCC.py:492 flatcamTools/ToolPaint.py:423
+msgid "Apply parameters to all tools"
+msgstr "Apply parameters to all tools"
+
+#: flatcamGUI/ObjectUI.py:1150 flatcamGUI/ObjectUI.py:1925
+#: flatcamTools/ToolNCC.py:494 flatcamTools/ToolPaint.py:425
+msgid ""
+"The parameters in the current form will be applied\n"
+"on all the tools from the Tool Table."
+msgstr ""
+"The parameters in the current form will be applied\n"
+"on all the tools from the Tool Table."
+
+#: flatcamGUI/ObjectUI.py:1161 flatcamGUI/ObjectUI.py:1936
+#: flatcamTools/ToolNCC.py:505 flatcamTools/ToolPaint.py:436
+msgid "Common Parameters"
+msgstr "Common Parameters"
+
+#: flatcamGUI/ObjectUI.py:1163 flatcamGUI/ObjectUI.py:1938
+#: flatcamTools/ToolNCC.py:507 flatcamTools/ToolPaint.py:438
+msgid "Parameters that are common for all tools."
+msgstr "Parameters that are common for all tools."
+
+#: flatcamGUI/ObjectUI.py:1168 flatcamGUI/ObjectUI.py:1943
+msgid "Tool change Z"
+msgstr "Tool change Z"
+
+#: flatcamGUI/ObjectUI.py:1170 flatcamGUI/PreferencesUI.py:3372
+msgid ""
+"Include tool-change sequence\n"
+"in G-Code (Pause for tool change)."
+msgstr ""
+"Include tool-change sequence\n"
+"in G-Code (Pause for tool change)."
+
+#: flatcamGUI/ObjectUI.py:1177 flatcamGUI/ObjectUI.py:1954
+#: flatcamGUI/PreferencesUI.py:3380 flatcamGUI/PreferencesUI.py:4327
+msgid ""
+"Z-axis position (height) for\n"
+"tool change."
+msgstr ""
+"Z-axis position (height) for\n"
+"tool change."
+
+#: flatcamGUI/ObjectUI.py:1194 flatcamGUI/PreferencesUI.py:3588
+msgid ""
+"Height of the tool just after start.\n"
+"Delete the value if you don't need this feature."
+msgstr ""
+"Height of the tool just after start.\n"
+"Delete the value if you don't need this feature."
+
+#: flatcamGUI/ObjectUI.py:1203 flatcamGUI/ObjectUI.py:1982
+#: flatcamGUI/PreferencesUI.py:3396 flatcamGUI/PreferencesUI.py:4346
+msgid "End move Z"
+msgstr "End move Z"
+
+#: flatcamGUI/ObjectUI.py:1205 flatcamGUI/ObjectUI.py:1984
+#: flatcamGUI/PreferencesUI.py:3398 flatcamGUI/PreferencesUI.py:4348
+msgid ""
+"Height of the tool after\n"
+"the last move at the end of the job."
+msgstr ""
+"Height of the tool after\n"
+"the last move at the end of the job."
+
+#: flatcamGUI/ObjectUI.py:1222 flatcamGUI/ObjectUI.py:2001
+#: flatcamGUI/PreferencesUI.py:3413 flatcamGUI/PreferencesUI.py:4366
+msgid "End move X,Y"
+msgstr "End move X,Y"
+
+#: flatcamGUI/ObjectUI.py:1224 flatcamGUI/ObjectUI.py:2003
+#: flatcamGUI/PreferencesUI.py:3415 flatcamGUI/PreferencesUI.py:4368
+msgid ""
+"End move X,Y position. In format (x,y).\n"
+"If no value is entered then there is no move\n"
+"on X,Y plane at the end of the job."
+msgstr ""
+"End move X,Y position. In format (x,y).\n"
+"If no value is entered then there is no move\n"
+"on X,Y plane at the end of the job."
+
+#: flatcamGUI/ObjectUI.py:1234 flatcamGUI/ObjectUI.py:1877
+#: flatcamGUI/PreferencesUI.py:3613 flatcamGUI/PreferencesUI.py:4544
+msgid "Probe Z depth"
+msgstr "Probe Z depth"
+
+#: flatcamGUI/ObjectUI.py:1236 flatcamGUI/ObjectUI.py:1879
+#: flatcamGUI/PreferencesUI.py:3615 flatcamGUI/PreferencesUI.py:4546
+msgid ""
+"The maximum depth that the probe is allowed\n"
+"to probe. Negative value, in current units."
+msgstr ""
+"The maximum depth that the probe is allowed\n"
+"to probe. Negative value, in current units."
+
+#: flatcamGUI/ObjectUI.py:1253 flatcamGUI/ObjectUI.py:1894
+#: flatcamGUI/PreferencesUI.py:3626 flatcamGUI/PreferencesUI.py:4559
+msgid "Feedrate Probe"
+msgstr "Feedrate Probe"
+
+#: flatcamGUI/ObjectUI.py:1255 flatcamGUI/ObjectUI.py:1896
+#: flatcamGUI/PreferencesUI.py:3628 flatcamGUI/PreferencesUI.py:4561
+msgid "The feedrate used while the probe is probing."
+msgstr "The feedrate used while the probe is probing."
+
+#: flatcamGUI/ObjectUI.py:1271
+msgid "Preprocessor E"
+msgstr "Preprocessor E"
+
+#: flatcamGUI/ObjectUI.py:1273
+msgid ""
+"The preprocessor JSON file that dictates\n"
+"Gcode output for Excellon Objects."
+msgstr ""
+"The preprocessor JSON file that dictates\n"
+"Gcode output for Excellon Objects."
+
+#: flatcamGUI/ObjectUI.py:1283
+msgid "Preprocessor G"
+msgstr "Preprocessor G"
+
+#: flatcamGUI/ObjectUI.py:1285
+msgid ""
+"The preprocessor JSON file that dictates\n"
+"Gcode output for Geometry (Milling) Objects."
+msgstr ""
+"The preprocessor JSON file that dictates\n"
+"Gcode output for Geometry (Milling) Objects."
+
+#: flatcamGUI/ObjectUI.py:1309 flatcamGUI/ObjectUI.py:2027
+msgid ""
+"Add / Select at least one tool in the tool-table.\n"
+"Click the # header to select all, or Ctrl + LMB\n"
+"for custom selection of tools."
+msgstr ""
+"Add / Select at least one tool in the tool-table.\n"
+"Click the # header to select all, or Ctrl + LMB\n"
+"for custom selection of tools."
+
+#: flatcamGUI/ObjectUI.py:1317 flatcamGUI/ObjectUI.py:2034
+msgid "Generate CNCJob object"
+msgstr "Generate CNCJob object"
+
+#: flatcamGUI/ObjectUI.py:1319
+msgid ""
+"Generate the CNC Job.\n"
+"If milling then an additional Geometry object will be created"
+msgstr ""
+"Generate the CNC Job.\n"
+"If milling then an additional Geometry object will be created"
+
+#: flatcamGUI/ObjectUI.py:1336
+msgid "Milling Geometry"
+msgstr "Milling Geometry"
+
+#: flatcamGUI/ObjectUI.py:1338
+msgid ""
+"Create Geometry for milling holes.\n"
+"Select from the Tools Table above the hole dias to be\n"
+"milled. Use the # column to make the selection."
+msgstr ""
+"Create Geometry for milling holes.\n"
+"Select from the Tools Table above the hole dias to be\n"
+"milled. Use the # column to make the selection."
+
+#: flatcamGUI/ObjectUI.py:1346 flatcamGUI/PreferencesUI.py:2207
+#: flatcamGUI/PreferencesUI.py:3514
+msgid "Diameter of the cutting tool."
+msgstr "Diameter of the cutting tool."
+
+#: flatcamGUI/ObjectUI.py:1356
+msgid "Mill Drills"
+msgstr "Mill Drills"
+
+#: flatcamGUI/ObjectUI.py:1358
+msgid ""
+"Create the Geometry Object\n"
+"for milling DRILLS toolpaths."
+msgstr ""
+"Create the Geometry Object\n"
+"for milling DRILLS toolpaths."
+
+#: flatcamGUI/ObjectUI.py:1376
+msgid "Mill Slots"
+msgstr "Mill Slots"
+
+#: flatcamGUI/ObjectUI.py:1378
+msgid ""
+"Create the Geometry Object\n"
+"for milling SLOTS toolpaths."
+msgstr ""
+"Create the Geometry Object\n"
+"for milling SLOTS toolpaths."
+
+#: flatcamGUI/ObjectUI.py:1420 flatcamTools/ToolCutOut.py:326
+msgid "Geometry Object"
+msgstr "Geometry Object"
+
+#: flatcamGUI/ObjectUI.py:1466
+msgid ""
+"Tools in this Geometry object used for cutting.\n"
+"The 'Offset' entry will set an offset for the cut.\n"
+"'Offset' can be inside, outside, on path (none) and custom.\n"
+"'Type' entry is only informative and it allow to know the \n"
+"intent of using the current tool. \n"
+"It can be Rough(ing), Finish(ing) or Iso(lation).\n"
+"The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n"
+"ball(B), or V-Shaped(V). \n"
+"When V-shaped is selected the 'Type' entry is automatically \n"
+"set to Isolation, the CutZ parameter in the UI form is\n"
+"grayed out and Cut Z is automatically calculated from the newly \n"
+"showed UI form entries named V-Tip Dia and V-Tip Angle."
+msgstr ""
+"Tools in this Geometry object used for cutting.\n"
+"The 'Offset' entry will set an offset for the cut.\n"
+"'Offset' can be inside, outside, on path (none) and custom.\n"
+"'Type' entry is only informative and it allow to know the \n"
+"intent of using the current tool. \n"
+"It can be Rough(ing), Finish(ing) or Iso(lation).\n"
+"The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n"
+"ball(B), or V-Shaped(V). \n"
+"When V-shaped is selected the 'Type' entry is automatically \n"
+"set to Isolation, the CutZ parameter in the UI form is\n"
+"grayed out and Cut Z is automatically calculated from the newly \n"
+"showed UI form entries named V-Tip Dia and V-Tip Angle."
+
+#: flatcamGUI/ObjectUI.py:1483 flatcamGUI/ObjectUI.py:2232
+#: flatcamGUI/PreferencesUI.py:4698
+msgid "Plot Object"
+msgstr "Plot Object"
+
+#: flatcamGUI/ObjectUI.py:1496 flatcamGUI/ObjectUI.py:2245
+#: flatcamGUI/ObjectUI.py:2255 flatcamGUI/PreferencesUI.py:7747
+#: flatcamTools/ToolCopperThieving.py:221
+msgid "Dia"
+msgstr "Dia"
+
+#: flatcamGUI/ObjectUI.py:1496 flatcamGUI/ObjectUI.py:2245
+#: flatcamTools/ToolNCC.py:132 flatcamTools/ToolPaint.py:128
+msgid "TT"
+msgstr "TT"
+
+#: flatcamGUI/ObjectUI.py:1503
+msgid ""
+"This is the Tool Number.\n"
+"When ToolChange is checked, on toolchange event this value\n"
+"will be showed as a T1, T2 ... Tn"
+msgstr ""
+"This is the Tool Number.\n"
+"When ToolChange is checked, on toolchange event this value\n"
+"will be showed as a T1, T2 ... Tn"
+
+#: flatcamGUI/ObjectUI.py:1514
+msgid ""
+"The value for the Offset can be:\n"
+"- Path -> There is no offset, the tool cut will be done through the geometry "
+"line.\n"
+"- In(side) -> The tool cut will follow the geometry inside. It will create a "
+"'pocket'.\n"
+"- Out(side) -> The tool cut will follow the geometry line on the outside."
+msgstr ""
+"The value for the Offset can be:\n"
+"- Path -> There is no offset, the tool cut will be done through the geometry "
+"line.\n"
+"- In(side) -> The tool cut will follow the geometry inside. It will create a "
+"'pocket'.\n"
+"- Out(side) -> The tool cut will follow the geometry line on the outside."
+
+#: flatcamGUI/ObjectUI.py:1521
+msgid ""
+"The (Operation) Type has only informative value. Usually the UI form "
+"values \n"
+"are choose based on the operation type and this will serve as a reminder.\n"
+"Can be 'Roughing', 'Finishing' or 'Isolation'.\n"
+"For Roughing we may choose a lower Feedrate and multiDepth cut.\n"
+"For Finishing we may choose a higher Feedrate, without multiDepth.\n"
+"For Isolation we need a lower Feedrate as it use a milling bit with a fine "
+"tip."
+msgstr ""
+"The (Operation) Type has only informative value. Usually the UI form "
+"values \n"
+"are choose based on the operation type and this will serve as a reminder.\n"
+"Can be 'Roughing', 'Finishing' or 'Isolation'.\n"
+"For Roughing we may choose a lower Feedrate and multiDepth cut.\n"
+"For Finishing we may choose a higher Feedrate, without multiDepth.\n"
+"For Isolation we need a lower Feedrate as it use a milling bit with a fine "
+"tip."
+
+#: flatcamGUI/ObjectUI.py:1530
+msgid ""
+"The Tool Type (TT) can be:\n"
+"- Circular with 1 ... 4 teeth -> it is informative only. Being circular the "
+"cut width in material\n"
+"is exactly the tool diameter.\n"
+"- Ball -> informative only and make reference to the Ball type endmill.\n"
+"- V-Shape -> it will disable de Z-Cut parameter in the UI form and enable "
+"two additional UI form\n"
+"fields: V-Tip Dia and V-Tip Angle. Adjusting those two values will adjust "
+"the Z-Cut parameter such\n"
+"as the cut width into material will be equal with the value in the Tool "
+"Diameter column of this table.\n"
+"Choosing the V-Shape Tool Type automatically will select the Operation Type "
+"as Isolation."
+msgstr ""
+"The Tool Type (TT) can be:\n"
+"- Circular with 1 ... 4 teeth -> it is informative only. Being circular the "
+"cut width in material\n"
+"is exactly the tool diameter.\n"
+"- Ball -> informative only and make reference to the Ball type endmill.\n"
+"- V-Shape -> it will disable de Z-Cut parameter in the UI form and enable "
+"two additional UI form\n"
+"fields: V-Tip Dia and V-Tip Angle. Adjusting those two values will adjust "
+"the Z-Cut parameter such\n"
+"as the cut width into material will be equal with the value in the Tool "
+"Diameter column of this table.\n"
+"Choosing the V-Shape Tool Type automatically will select the Operation Type "
+"as Isolation."
+
+#: flatcamGUI/ObjectUI.py:1542
+msgid ""
+"Plot column. It is visible only for MultiGeo geometries, meaning geometries "
+"that holds the geometry\n"
+"data into the tools. For those geometries, deleting the tool will delete the "
+"geometry data also,\n"
+"so be WARNED. From the checkboxes on each row it can be enabled/disabled the "
+"plot on canvas\n"
+"for the corresponding tool."
+msgstr ""
+"Plot column. It is visible only for MultiGeo geometries, meaning geometries "
+"that holds the geometry\n"
+"data into the tools. For those geometries, deleting the tool will delete the "
+"geometry data also,\n"
+"so be WARNED. From the checkboxes on each row it can be enabled/disabled the "
+"plot on canvas\n"
+"for the corresponding tool."
+
+#: flatcamGUI/ObjectUI.py:1560
+msgid ""
+"The value to offset the cut when \n"
+"the Offset type selected is 'Offset'.\n"
+"The value can be positive for 'outside'\n"
+"cut and negative for 'inside' cut."
+msgstr ""
+"The value to offset the cut when \n"
+"the Offset type selected is 'Offset'.\n"
+"The value can be positive for 'outside'\n"
+"cut and negative for 'inside' cut."
+
+#: flatcamGUI/ObjectUI.py:1579 flatcamTools/ToolNCC.py:209
+#: flatcamTools/ToolNCC.py:923 flatcamTools/ToolPaint.py:192
+#: flatcamTools/ToolPaint.py:849 flatcamTools/ToolSolderPaste.py:559
+msgid "New Tool"
+msgstr "New Tool"
+
+#: flatcamGUI/ObjectUI.py:1596
+msgid ""
+"Add a new tool to the Tool Table\n"
+"with the specified diameter."
+msgstr ""
+"Add a new tool to the Tool Table\n"
+"with the specified diameter."
+
+#: flatcamGUI/ObjectUI.py:1601 flatcamTools/ToolNCC.py:300
+#: flatcamTools/ToolNCC.py:634 flatcamTools/ToolPaint.py:283
+#: flatcamTools/ToolPaint.py:679
+msgid "Add from DB"
+msgstr "Add from DB"
+
+#: flatcamGUI/ObjectUI.py:1603 flatcamTools/ToolNCC.py:302
+#: flatcamTools/ToolPaint.py:285
+msgid ""
+"Add a new tool to the Tool Table\n"
+"from the Tool DataBase."
+msgstr ""
+"Add a new tool to the Tool Table\n"
+"from the Tool DataBase."
+
+#: flatcamGUI/ObjectUI.py:1618
+msgid ""
+"Copy a selection of tools in the Tool Table\n"
+"by first selecting a row in the Tool Table."
+msgstr ""
+"Copy a selection of tools in the Tool Table\n"
+"by first selecting a row in the Tool Table."
+
+#: flatcamGUI/ObjectUI.py:1624
+msgid ""
+"Delete a selection of tools in the Tool Table\n"
+"by first selecting a row in the Tool Table."
+msgstr ""
+"Delete a selection of tools in the Tool Table\n"
+"by first selecting a row in the Tool Table."
+
+#: flatcamGUI/ObjectUI.py:1751 flatcamGUI/PreferencesUI.py:4296
+msgid ""
+"Height of the tool when\n"
+"moving without cutting."
+msgstr ""
+"Height of the tool when\n"
+"moving without cutting."
+
+#: flatcamGUI/ObjectUI.py:1784 flatcamGUI/PreferencesUI.py:4395
+msgid ""
+"Cutting speed in the XY\n"
+"plane in units per minute.\n"
+"It is called also Plunge."
+msgstr ""
+"Cutting speed in the XY\n"
+"plane in units per minute.\n"
+"It is called also Plunge."
+
+#: flatcamGUI/ObjectUI.py:1799 flatcamGUI/PreferencesUI.py:4505
+msgid ""
+"Cutting speed in the XY plane\n"
+"(in units per minute).\n"
+"This is for the rapid move G00.\n"
+"It is useful only for Marlin,\n"
+"ignore for any other cases."
+msgstr ""
+"Cutting speed in the XY plane\n"
+"(in units per minute).\n"
+"This is for the rapid move G00.\n"
+"It is useful only for Marlin,\n"
+"ignore for any other cases."
+
+#: flatcamGUI/ObjectUI.py:1843 flatcamGUI/PreferencesUI.py:4412
+msgid ""
+"Speed of the spindle in RPM (optional).\n"
+"If LASER preprocessor is used,\n"
+"this value is the power of laser."
+msgstr ""
+"Speed of the spindle in RPM (optional).\n"
+"If LASER preprocessor is used,\n"
+"this value is the power of laser."
+
+#: flatcamGUI/ObjectUI.py:1946 flatcamGUI/PreferencesUI.py:4317
+msgid ""
+"Include tool-change sequence\n"
+"in the Machine Code (Pause for tool change)."
+msgstr ""
+"Include tool-change sequence\n"
+"in the Machine Code (Pause for tool change)."
+
+#: flatcamGUI/ObjectUI.py:2015 flatcamGUI/PreferencesUI.py:4449
+msgid ""
+"The Preprocessor file that dictates\n"
+"the Machine Code (like GCode, RML, HPGL) output."
+msgstr ""
+"The Preprocessor file that dictates\n"
+"the Machine Code (like GCode, RML, HPGL) output."
+
+#: flatcamGUI/ObjectUI.py:2036
+msgid "Generate the CNC Job object."
+msgstr "Generate the CNC Job object."
+
+#: flatcamGUI/ObjectUI.py:2053
+msgid "Launch Paint Tool in Tools Tab."
+msgstr "Launch Paint Tool in Tools Tab."
+
+#: flatcamGUI/ObjectUI.py:2061 flatcamGUI/PreferencesUI.py:5874
+msgid ""
+"Creates tool paths to cover the\n"
+"whole area of a polygon (remove\n"
+"all copper). You will be asked\n"
+"to click on the desired polygon."
+msgstr ""
+"Creates tool paths to cover the\n"
+"whole area of a polygon (remove\n"
+"all copper). You will be asked\n"
+"to click on the desired polygon."
+
+#: flatcamGUI/ObjectUI.py:2116
+msgid "CNC Job Object"
+msgstr "CNC Job Object"
+
+#: flatcamGUI/ObjectUI.py:2127 flatcamGUI/PreferencesUI.py:4703
+msgid "Plot kind"
+msgstr "Plot kind"
+
+#: flatcamGUI/ObjectUI.py:2130 flatcamGUI/PreferencesUI.py:4705
+msgid ""
+"This selects the kind of geometries on the canvas to plot.\n"
+"Those can be either of type 'Travel' which means the moves\n"
+"above the work piece or it can be of type 'Cut',\n"
+"which means the moves that cut into the material."
+msgstr ""
+"This selects the kind of geometries on the canvas to plot.\n"
+"Those can be either of type 'Travel' which means the moves\n"
+"above the work piece or it can be of type 'Cut',\n"
+"which means the moves that cut into the material."
+
+#: flatcamGUI/ObjectUI.py:2139 flatcamGUI/PreferencesUI.py:4713
+msgid "Travel"
+msgstr "Travel"
+
+#: flatcamGUI/ObjectUI.py:2143 flatcamGUI/PreferencesUI.py:4722
+msgid "Display Annotation"
+msgstr "Display Annotation"
+
+#: flatcamGUI/ObjectUI.py:2145 flatcamGUI/PreferencesUI.py:4724
+msgid ""
+"This selects if to display text annotation on the plot.\n"
+"When checked it will display numbers in order for each end\n"
+"of a travel line."
+msgstr ""
+"This selects if to display text annotation on the plot.\n"
+"When checked it will display numbers in order for each end\n"
+"of a travel line."
+
+#: flatcamGUI/ObjectUI.py:2160
+msgid "Travelled dist."
+msgstr "Travelled dist."
+
+#: flatcamGUI/ObjectUI.py:2162 flatcamGUI/ObjectUI.py:2167
+msgid ""
+"This is the total travelled distance on X-Y plane.\n"
+"In current units."
+msgstr ""
+"This is the total travelled distance on X-Y plane.\n"
+"In current units."
+
+#: flatcamGUI/ObjectUI.py:2172
+msgid "Estimated time"
+msgstr "Estimated time"
+
+#: flatcamGUI/ObjectUI.py:2174 flatcamGUI/ObjectUI.py:2179
+msgid ""
+"This is the estimated time to do the routing/drilling,\n"
+"without the time spent in ToolChange events."
+msgstr ""
+"This is the estimated time to do the routing/drilling,\n"
+"without the time spent in ToolChange events."
+
+#: flatcamGUI/ObjectUI.py:2214
+msgid "CNC Tools Table"
+msgstr "CNC Tools Table"
+
+#: flatcamGUI/ObjectUI.py:2217
+msgid ""
+"Tools in this CNCJob object used for cutting.\n"
+"The tool diameter is used for plotting on canvas.\n"
+"The 'Offset' entry will set an offset for the cut.\n"
+"'Offset' can be inside, outside, on path (none) and custom.\n"
+"'Type' entry is only informative and it allow to know the \n"
+"intent of using the current tool. \n"
+"It can be Rough(ing), Finish(ing) or Iso(lation).\n"
+"The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n"
+"ball(B), or V-Shaped(V)."
+msgstr ""
+"Tools in this CNCJob object used for cutting.\n"
+"The tool diameter is used for plotting on canvas.\n"
+"The 'Offset' entry will set an offset for the cut.\n"
+"'Offset' can be inside, outside, on path (none) and custom.\n"
+"'Type' entry is only informative and it allow to know the \n"
+"intent of using the current tool. \n"
+"It can be Rough(ing), Finish(ing) or Iso(lation).\n"
+"The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n"
+"ball(B), or V-Shaped(V)."
+
+#: flatcamGUI/ObjectUI.py:2245 flatcamGUI/ObjectUI.py:2256
+msgid "P"
+msgstr "P"
+
+#: flatcamGUI/ObjectUI.py:2266
+msgid "Update Plot"
+msgstr "Update Plot"
+
+#: flatcamGUI/ObjectUI.py:2268
+msgid "Update the plot."
+msgstr "Update the plot."
+
+#: flatcamGUI/ObjectUI.py:2275 flatcamGUI/PreferencesUI.py:5120
+msgid "Export CNC Code"
+msgstr "Export CNC Code"
+
+#: flatcamGUI/ObjectUI.py:2277 flatcamGUI/PreferencesUI.py:5061
+#: flatcamGUI/PreferencesUI.py:5122
+msgid ""
+"Export and save G-Code to\n"
+"make this object to a file."
+msgstr ""
+"Export and save G-Code to\n"
+"make this object to a file."
+
+#: flatcamGUI/ObjectUI.py:2283
+msgid "Prepend to CNC Code"
+msgstr "Prepend to CNC Code"
+
+#: flatcamGUI/ObjectUI.py:2285 flatcamGUI/ObjectUI.py:2292
+#: flatcamGUI/PreferencesUI.py:5077
+msgid ""
+"Type here any G-Code commands you would\n"
+"like to add at the beginning of the G-Code file."
+msgstr ""
+"Type here any G-Code commands you would\n"
+"like to add at the beginning of the G-Code file."
+
+#: flatcamGUI/ObjectUI.py:2298
+msgid "Append to CNC Code"
+msgstr "Append to CNC Code"
+
+#: flatcamGUI/ObjectUI.py:2300 flatcamGUI/ObjectUI.py:2308
+#: flatcamGUI/PreferencesUI.py:5093
+msgid ""
+"Type here any G-Code commands you would\n"
+"like to append to the generated file.\n"
+"I.e.: M2 (End of program)"
+msgstr ""
+"Type here any G-Code commands you would\n"
+"like to append to the generated file.\n"
+"I.e.: M2 (End of program)"
+
+#: flatcamGUI/ObjectUI.py:2322 flatcamGUI/PreferencesUI.py:5128
+msgid "Toolchange G-Code"
+msgstr "Toolchange G-Code"
+
+#: flatcamGUI/ObjectUI.py:2325 flatcamGUI/PreferencesUI.py:5131
+msgid ""
+"Type here any G-Code commands you would\n"
+"like to be executed when Toolchange event is encountered.\n"
+"This will constitute a Custom Toolchange GCode,\n"
+"or a Toolchange Macro.\n"
+"The FlatCAM variables are surrounded by '%' symbol.\n"
+"\n"
+"WARNING: it can be used only with a preprocessor file\n"
+"that has 'toolchange_custom' in it's name and this is built\n"
+"having as template the 'Toolchange Custom' posprocessor file."
+msgstr ""
+"Type here any G-Code commands you would\n"
+"like to be executed when Toolchange event is encountered.\n"
+"This will constitute a Custom Toolchange GCode,\n"
+"or a Toolchange Macro.\n"
+"The FlatCAM variables are surrounded by '%' symbol.\n"
+"\n"
+"WARNING: it can be used only with a preprocessor file\n"
+"that has 'toolchange_custom' in it's name and this is built\n"
+"having as template the 'Toolchange Custom' posprocessor file."
+
+#: flatcamGUI/ObjectUI.py:2340
+msgid ""
+"Type here any G-Code commands you would\n"
+"like to be executed when Toolchange event is encountered.\n"
+"This will constitute a Custom Toolchange GCode,\n"
+"or a Toolchange Macro.\n"
+"The FlatCAM variables are surrounded by '%' symbol.\n"
+"WARNING: it can be used only with a preprocessor file\n"
+"that has 'toolchange_custom' in it's name."
+msgstr ""
+"Type here any G-Code commands you would\n"
+"like to be executed when Toolchange event is encountered.\n"
+"This will constitute a Custom Toolchange GCode,\n"
+"or a Toolchange Macro.\n"
+"The FlatCAM variables are surrounded by '%' symbol.\n"
+"WARNING: it can be used only with a preprocessor file\n"
+"that has 'toolchange_custom' in it's name."
+
+#: flatcamGUI/ObjectUI.py:2355 flatcamGUI/PreferencesUI.py:5170
+msgid "Use Toolchange Macro"
+msgstr "Use Toolchange Macro"
+
+#: flatcamGUI/ObjectUI.py:2357 flatcamGUI/PreferencesUI.py:5172
+msgid ""
+"Check this box if you want to use\n"
+"a Custom Toolchange GCode (macro)."
+msgstr ""
+"Check this box if you want to use\n"
+"a Custom Toolchange GCode (macro)."
+
+#: flatcamGUI/ObjectUI.py:2365 flatcamGUI/PreferencesUI.py:5184
+msgid ""
+"A list of the FlatCAM variables that can be used\n"
+"in the Toolchange event.\n"
+"They have to be surrounded by the '%' symbol"
+msgstr ""
+"A list of the FlatCAM variables that can be used\n"
+"in the Toolchange event.\n"
+"They have to be surrounded by the '%' symbol"
+
+#: flatcamGUI/ObjectUI.py:2372 flatcamGUI/PreferencesUI.py:2627
+#: flatcamGUI/PreferencesUI.py:3833 flatcamGUI/PreferencesUI.py:4640
+#: flatcamGUI/PreferencesUI.py:5191 flatcamGUI/PreferencesUI.py:5310
+#: flatcamGUI/PreferencesUI.py:5640 flatcamGUI/PreferencesUI.py:5797
+#: flatcamGUI/PreferencesUI.py:6164 flatcamGUI/PreferencesUI.py:6461
+#: flatcamGUI/PreferencesUI.py:6711 flatcamGUI/PreferencesUI.py:6942
+#: flatcamGUI/PreferencesUI.py:7169 flatcamGUI/PreferencesUI.py:7191
+#: flatcamGUI/PreferencesUI.py:7415 flatcamGUI/PreferencesUI.py:7452
+#: flatcamGUI/PreferencesUI.py:7646 flatcamGUI/PreferencesUI.py:7900
+#: flatcamGUI/PreferencesUI.py:8016 flatcamGUI/PreferencesUI.py:8135
+#: flatcamGUI/PreferencesUI.py:8347 flatcamGUI/PreferencesUI.py:8556
+#: flatcamTools/ToolCopperThieving.py:89 flatcamTools/ToolFiducials.py:149
+#: flatcamTools/ToolInvertGerber.py:82
+msgid "Parameters"
+msgstr "Parameters"
+
+#: flatcamGUI/ObjectUI.py:2375 flatcamGUI/PreferencesUI.py:5196
+msgid "FlatCAM CNC parameters"
+msgstr "FlatCAM CNC parameters"
+
+#: flatcamGUI/ObjectUI.py:2376 flatcamGUI/PreferencesUI.py:5201
+msgid "tool number"
+msgstr "tool number"
+
+#: flatcamGUI/ObjectUI.py:2377 flatcamGUI/PreferencesUI.py:5202
+msgid "tool diameter"
+msgstr "tool diameter"
+
+#: flatcamGUI/ObjectUI.py:2378 flatcamGUI/PreferencesUI.py:5203
+msgid "for Excellon, total number of drills"
+msgstr "for Excellon, total number of drills"
+
+#: flatcamGUI/ObjectUI.py:2380 flatcamGUI/PreferencesUI.py:5205
+msgid "X coord for Toolchange"
+msgstr "X coord for Toolchange"
+
+#: flatcamGUI/ObjectUI.py:2381 flatcamGUI/PreferencesUI.py:5206
+msgid "Y coord for Toolchange"
+msgstr "Y coord for Toolchange"
+
+#: flatcamGUI/ObjectUI.py:2382 flatcamGUI/PreferencesUI.py:5208
+msgid "Z coord for Toolchange"
+msgstr "Z coord for Toolchange"
+
+#: flatcamGUI/ObjectUI.py:2383
+msgid "depth where to cut"
+msgstr "depth where to cut"
+
+#: flatcamGUI/ObjectUI.py:2384
+msgid "height where to travel"
+msgstr "height where to travel"
+
+#: flatcamGUI/ObjectUI.py:2385 flatcamGUI/PreferencesUI.py:5211
+msgid "the step value for multidepth cut"
+msgstr "the step value for multidepth cut"
+
+#: flatcamGUI/ObjectUI.py:2387 flatcamGUI/PreferencesUI.py:5213
+msgid "the value for the spindle speed"
+msgstr "the value for the spindle speed"
+
+#: flatcamGUI/ObjectUI.py:2389
+msgid "time to dwell to allow the spindle to reach it's set RPM"
+msgstr "time to dwell to allow the spindle to reach it's set RPM"
+
+#: flatcamGUI/ObjectUI.py:2405
+msgid "View CNC Code"
+msgstr "View CNC Code"
+
+#: flatcamGUI/ObjectUI.py:2407
+msgid ""
+"Opens TAB to view/modify/print G-Code\n"
+"file."
+msgstr ""
+"Opens TAB to view/modify/print G-Code\n"
+"file."
+
+#: flatcamGUI/ObjectUI.py:2412
+msgid "Save CNC Code"
+msgstr "Save CNC Code"
+
+#: flatcamGUI/ObjectUI.py:2414
+msgid ""
+"Opens dialog to save G-Code\n"
+"file."
+msgstr ""
+"Opens dialog to save G-Code\n"
+"file."
+
+#: flatcamGUI/ObjectUI.py:2448
+msgid "Script Object"
+msgstr "Script Object"
+
+#: flatcamGUI/ObjectUI.py:2468 flatcamGUI/ObjectUI.py:2542
+msgid "Auto Completer"
+msgstr "Auto Completer"
+
+#: flatcamGUI/ObjectUI.py:2470
+msgid "This selects if the auto completer is enabled in the Script Editor."
+msgstr "This selects if the auto completer is enabled in the Script Editor."
+
+#: flatcamGUI/ObjectUI.py:2515
+msgid "Document Object"
+msgstr "Document Object"
+
+#: flatcamGUI/ObjectUI.py:2544
+msgid "This selects if the auto completer is enabled in the Document Editor."
+msgstr "This selects if the auto completer is enabled in the Document Editor."
+
+#: flatcamGUI/ObjectUI.py:2562
+msgid "Font Type"
+msgstr "Font Type"
+
+#: flatcamGUI/ObjectUI.py:2579 flatcamGUI/PreferencesUI.py:1278
+msgid "Font Size"
+msgstr "Font Size"
+
+#: flatcamGUI/ObjectUI.py:2615
+msgid "Alignment"
+msgstr "Alignment"
+
+#: flatcamGUI/ObjectUI.py:2620
+msgid "Align Left"
+msgstr "Align Left"
+
+#: flatcamGUI/ObjectUI.py:2630
+msgid "Align Right"
+msgstr "Align Right"
+
+#: flatcamGUI/ObjectUI.py:2635
+msgid "Justify"
+msgstr "Justify"
+
+#: flatcamGUI/ObjectUI.py:2642
+msgid "Font Color"
+msgstr "Font Color"
+
+#: flatcamGUI/ObjectUI.py:2644
+msgid "Set the font color for the selected text"
+msgstr "Set the font color for the selected text"
+
+#: flatcamGUI/ObjectUI.py:2658
+msgid "Selection Color"
+msgstr "Selection Color"
+
+#: flatcamGUI/ObjectUI.py:2660
+msgid "Set the selection color when doing text selection."
+msgstr "Set the selection color when doing text selection."
+
+#: flatcamGUI/ObjectUI.py:2674
+msgid "Tab Size"
+msgstr "Tab Size"
+
+#: flatcamGUI/ObjectUI.py:2676
+msgid "Set the tab size. In pixels. Default value is 80 pixels."
+msgstr "Set the tab size. In pixels. Default value is 80 pixels."
+
+#: flatcamGUI/PlotCanvasLegacy.py:1299
+msgid ""
+"Could not annotate due of a difference between the number of text elements "
+"and the number of text positions."
+msgstr ""
+"Could not annotate due of a difference between the number of text elements "
+"and the number of text positions."
+
+#: flatcamGUI/PreferencesUI.py:343
+msgid "GUI Preferences"
+msgstr "GUI Preferences"
+
+#: flatcamGUI/PreferencesUI.py:353
+msgid "Theme"
+msgstr "Theme"
+
+#: flatcamGUI/PreferencesUI.py:355
+msgid ""
+"Select a theme for FlatCAM.\n"
+"It will theme the plot area."
+msgstr ""
+"Select a theme for FlatCAM.\n"
+"It will theme the plot area."
+
+#: flatcamGUI/PreferencesUI.py:360
+msgid "Light"
+msgstr "Light"
+
+#: flatcamGUI/PreferencesUI.py:361
+msgid "Dark"
+msgstr "Dark"
+
+#: flatcamGUI/PreferencesUI.py:368
+msgid "Use Gray Icons"
+msgstr "Use Gray Icons"
+
+#: flatcamGUI/PreferencesUI.py:370
+msgid ""
+"Check this box to use a set of icons with\n"
+"a lighter (gray) color. To be used when a\n"
+"full dark theme is applied."
+msgstr ""
+"Check this box to use a set of icons with\n"
+"a lighter (gray) color. To be used when a\n"
+"full dark theme is applied."
+
+#: flatcamGUI/PreferencesUI.py:376
+msgid "Apply Theme"
+msgstr "Apply Theme"
+
+#: flatcamGUI/PreferencesUI.py:378
+msgid ""
+"Select a theme for FlatCAM.\n"
+"It will theme the plot area.\n"
+"The application will restart after change."
+msgstr ""
+"Select a theme for FlatCAM.\n"
+"It will theme the plot area.\n"
+"The application will restart after change."
+
+#: flatcamGUI/PreferencesUI.py:390
+msgid "Layout"
+msgstr "Layout"
+
+#: flatcamGUI/PreferencesUI.py:392
+msgid ""
+"Select an layout for FlatCAM.\n"
+"It is applied immediately."
+msgstr ""
+"Select an layout for FlatCAM.\n"
+"It is applied immediately."
+
+#: flatcamGUI/PreferencesUI.py:412
+msgid "Style"
+msgstr "Style"
+
+#: flatcamGUI/PreferencesUI.py:414
+msgid ""
+"Select an style for FlatCAM.\n"
+"It will be applied at the next app start."
+msgstr ""
+"Select an style for FlatCAM.\n"
+"It will be applied at the next app start."
+
+#: flatcamGUI/PreferencesUI.py:428
+msgid "Activate HDPI Support"
+msgstr "Activate HDPI Support"
+
+#: flatcamGUI/PreferencesUI.py:430
+msgid ""
+"Enable High DPI support for FlatCAM.\n"
+"It will be applied at the next app start."
+msgstr ""
+"Enable High DPI support for FlatCAM.\n"
+"It will be applied at the next app start."
+
+#: flatcamGUI/PreferencesUI.py:444
+msgid "Display Hover Shape"
+msgstr "Display Hover Shape"
+
+#: flatcamGUI/PreferencesUI.py:446
+msgid ""
+"Enable display of a hover shape for FlatCAM objects.\n"
+"It is displayed whenever the mouse cursor is hovering\n"
+"over any kind of not-selected object."
+msgstr ""
+"Enable display of a hover shape for FlatCAM objects.\n"
+"It is displayed whenever the mouse cursor is hovering\n"
+"over any kind of not-selected object."
+
+#: flatcamGUI/PreferencesUI.py:453
+msgid "Display Selection Shape"
+msgstr "Display Selection Shape"
+
+#: flatcamGUI/PreferencesUI.py:455
+msgid ""
+"Enable the display of a selection shape for FlatCAM objects.\n"
+"It is displayed whenever the mouse selects an object\n"
+"either by clicking or dragging mouse from left to right or\n"
+"right to left."
+msgstr ""
+"Enable the display of a selection shape for FlatCAM objects.\n"
+"It is displayed whenever the mouse selects an object\n"
+"either by clicking or dragging mouse from left to right or\n"
+"right to left."
+
+#: flatcamGUI/PreferencesUI.py:468
+msgid "Left-Right Selection Color"
+msgstr "Left-Right Selection Color"
+
+#: flatcamGUI/PreferencesUI.py:471 flatcamGUI/PreferencesUI.py:537
+#: flatcamGUI/PreferencesUI.py:2062 flatcamGUI/PreferencesUI.py:3085
+#: flatcamGUI/PreferencesUI.py:4174 flatcamGUI/PreferencesUI.py:4827
+#: flatcamGUI/PreferencesUI.py:4893 flatcamTools/ToolRulesCheck.py:186
+msgid "Outline"
+msgstr "Outline"
+
+#: flatcamGUI/PreferencesUI.py:473
+msgid "Set the line color for the 'left to right' selection box."
+msgstr "Set the line color for the 'left to right' selection box."
+
+#: flatcamGUI/PreferencesUI.py:487 flatcamGUI/PreferencesUI.py:554
+#: flatcamGUI/PreferencesUI.py:2079 flatcamGUI/PreferencesUI.py:3102
+#: flatcamGUI/PreferencesUI.py:4844 flatcamGUI/PreferencesUI.py:4910
+msgid "Fill"
+msgstr "Fill"
+
+#: flatcamGUI/PreferencesUI.py:489
+msgid ""
+"Set the fill color for the selection box\n"
+"in case that the selection is done from left to right.\n"
+"First 6 digits are the color and the last 2\n"
+"digits are for alpha (transparency) level."
+msgstr ""
+"Set the fill color for the selection box\n"
+"in case that the selection is done from left to right.\n"
+"First 6 digits are the color and the last 2\n"
+"digits are for alpha (transparency) level."
+
+#: flatcamGUI/PreferencesUI.py:507 flatcamGUI/PreferencesUI.py:574
+#: flatcamGUI/PreferencesUI.py:2098 flatcamGUI/PreferencesUI.py:3121
+#: flatcamGUI/PreferencesUI.py:4863
+msgid "Alpha"
+msgstr "Alpha"
+
+#: flatcamGUI/PreferencesUI.py:509
+msgid "Set the fill transparency for the 'left to right' selection box."
+msgstr "Set the fill transparency for the 'left to right' selection box."
+
+#: flatcamGUI/PreferencesUI.py:533
+msgid "Right-Left Selection Color"
+msgstr "Right-Left Selection Color"
+
+#: flatcamGUI/PreferencesUI.py:539
+msgid "Set the line color for the 'right to left' selection box."
+msgstr "Set the line color for the 'right to left' selection box."
+
+#: flatcamGUI/PreferencesUI.py:556
+msgid ""
+"Set the fill color for the selection box\n"
+"in case that the selection is done from right to left.\n"
+"First 6 digits are the color and the last 2\n"
+"digits are for alpha (transparency) level."
+msgstr ""
+"Set the fill color for the selection box\n"
+"in case that the selection is done from right to left.\n"
+"First 6 digits are the color and the last 2\n"
+"digits are for alpha (transparency) level."
+
+#: flatcamGUI/PreferencesUI.py:576
+msgid "Set the fill transparency for selection 'right to left' box."
+msgstr "Set the fill transparency for selection 'right to left' box."
+
+#: flatcamGUI/PreferencesUI.py:603
+msgid "Editor Color"
+msgstr "Editor Color"
+
+#: flatcamGUI/PreferencesUI.py:607
+msgid "Drawing"
+msgstr "Drawing"
+
+#: flatcamGUI/PreferencesUI.py:609
+msgid "Set the color for the shape."
+msgstr "Set the color for the shape."
+
+#: flatcamGUI/PreferencesUI.py:626
+msgid "Set the color of the shape when selected."
+msgstr "Set the color of the shape when selected."
+
+#: flatcamGUI/PreferencesUI.py:649
+msgid "Project Items Color"
+msgstr "Project Items Color"
+
+#: flatcamGUI/PreferencesUI.py:653
+msgid "Enabled"
+msgstr "Enabled"
+
+#: flatcamGUI/PreferencesUI.py:655
+msgid "Set the color of the items in Project Tab Tree."
+msgstr "Set the color of the items in Project Tab Tree."
+
+#: flatcamGUI/PreferencesUI.py:669
+msgid "Disabled"
+msgstr "Disabled"
+
+#: flatcamGUI/PreferencesUI.py:671
+msgid ""
+"Set the color of the items in Project Tab Tree,\n"
+"for the case when the items are disabled."
+msgstr ""
+"Set the color of the items in Project Tab Tree,\n"
+"for the case when the items are disabled."
+
+#: flatcamGUI/PreferencesUI.py:687
+msgid "Project AutoHide"
+msgstr "Project AutoHide"
+
+#: flatcamGUI/PreferencesUI.py:689
+msgid ""
+"Check this box if you want the project/selected/tool tab area to\n"
+"hide automatically when there are no objects loaded and\n"
+"to show whenever a new object is created."
+msgstr ""
+"Check this box if you want the project/selected/tool tab area to\n"
+"hide automatically when there are no objects loaded and\n"
+"to show whenever a new object is created."
+
+#: flatcamGUI/PreferencesUI.py:1109
+msgid "App Settings"
+msgstr "App Settings"
+
+#: flatcamGUI/PreferencesUI.py:1130
+msgid "Grid Settings"
+msgstr "Grid Settings"
+
+#: flatcamGUI/PreferencesUI.py:1134
+msgid "X value"
+msgstr "X value"
+
+#: flatcamGUI/PreferencesUI.py:1136
+msgid "This is the Grid snap value on X axis."
+msgstr "This is the Grid snap value on X axis."
+
+#: flatcamGUI/PreferencesUI.py:1146
+msgid "Y value"
+msgstr "Y value"
+
+#: flatcamGUI/PreferencesUI.py:1148
+msgid "This is the Grid snap value on Y axis."
+msgstr "This is the Grid snap value on Y axis."
+
+#: flatcamGUI/PreferencesUI.py:1158
+msgid "Snap Max"
+msgstr "Snap Max"
+
+#: flatcamGUI/PreferencesUI.py:1173
+msgid "Workspace Settings"
+msgstr "Workspace Settings"
+
+#: flatcamGUI/PreferencesUI.py:1176
+msgid "Active"
+msgstr "Active"
+
+#: flatcamGUI/PreferencesUI.py:1178
+msgid ""
+"Draw a delimiting rectangle on canvas.\n"
+"The purpose is to illustrate the limits for our work."
+msgstr ""
+"Draw a delimiting rectangle on canvas.\n"
+"The purpose is to illustrate the limits for our work."
+
+#: flatcamGUI/PreferencesUI.py:1186
+msgid ""
+"Select the type of rectangle to be used on canvas,\n"
+"as valid workspace."
+msgstr ""
+"Select the type of rectangle to be used on canvas,\n"
+"as valid workspace."
+
+#: flatcamGUI/PreferencesUI.py:1252
+msgid "Orientation"
+msgstr "Orientation"
+
+#: flatcamGUI/PreferencesUI.py:1253 flatcamGUI/PreferencesUI.py:6372
+#: flatcamTools/ToolFilm.py:422
+msgid ""
+"Can be:\n"
+"- Portrait\n"
+"- Landscape"
+msgstr ""
+"Can be:\n"
+"- Portrait\n"
+"- Landscape"
+
+#: flatcamGUI/PreferencesUI.py:1257 flatcamGUI/PreferencesUI.py:6376
+#: flatcamTools/ToolFilm.py:426
+msgid "Portrait"
+msgstr "Portrait"
+
+#: flatcamGUI/PreferencesUI.py:1258 flatcamGUI/PreferencesUI.py:6377
+#: flatcamTools/ToolFilm.py:427
+msgid "Landscape"
+msgstr "Landscape"
+
+#: flatcamGUI/PreferencesUI.py:1282
+msgid "Notebook"
+msgstr "Notebook"
+
+#: flatcamGUI/PreferencesUI.py:1284
+msgid ""
+"This sets the font size for the elements found in the Notebook.\n"
+"The notebook is the collapsible area in the left side of the GUI,\n"
+"and include the Project, Selected and Tool tabs."
+msgstr ""
+"This sets the font size for the elements found in the Notebook.\n"
+"The notebook is the collapsible area in the left side of the GUI,\n"
+"and include the Project, Selected and Tool tabs."
+
+#: flatcamGUI/PreferencesUI.py:1303
+msgid "Axis"
+msgstr "Axis"
+
+#: flatcamGUI/PreferencesUI.py:1305
+msgid "This sets the font size for canvas axis."
+msgstr "This sets the font size for canvas axis."
+
+#: flatcamGUI/PreferencesUI.py:1322
+msgid "Textbox"
+msgstr "Textbox"
+
+#: flatcamGUI/PreferencesUI.py:1324
+msgid ""
+"This sets the font size for the Textbox GUI\n"
+"elements that are used in FlatCAM."
+msgstr ""
+"This sets the font size for the Textbox GUI\n"
+"elements that are used in FlatCAM."
+
+#: flatcamGUI/PreferencesUI.py:1350
+msgid "Mouse Settings"
+msgstr "Mouse Settings"
+
+#: flatcamGUI/PreferencesUI.py:1354
+msgid "Cursor Shape"
+msgstr "Cursor Shape"
+
+#: flatcamGUI/PreferencesUI.py:1356
+msgid ""
+"Choose a mouse cursor shape.\n"
+"- Small -> with a customizable size.\n"
+"- Big -> Infinite lines"
+msgstr ""
+"Choose a mouse cursor shape.\n"
+"- Small -> with a customizable size.\n"
+"- Big -> Infinite lines"
+
+#: flatcamGUI/PreferencesUI.py:1362
+msgid "Small"
+msgstr "Small"
+
+#: flatcamGUI/PreferencesUI.py:1363
+msgid "Big"
+msgstr "Big"
+
+#: flatcamGUI/PreferencesUI.py:1370
+msgid "Cursor Size"
+msgstr "Cursor Size"
+
+#: flatcamGUI/PreferencesUI.py:1372
+msgid "Set the size of the mouse cursor, in pixels."
+msgstr "Set the size of the mouse cursor, in pixels."
+
+#: flatcamGUI/PreferencesUI.py:1383
+msgid "Cursor Width"
+msgstr "Cursor Width"
+
+#: flatcamGUI/PreferencesUI.py:1385
+msgid "Set the line width of the mouse cursor, in pixels."
+msgstr "Set the line width of the mouse cursor, in pixels."
+
+#: flatcamGUI/PreferencesUI.py:1396 flatcamGUI/PreferencesUI.py:1403
+msgid "Cursor Color"
+msgstr "Cursor Color"
+
+#: flatcamGUI/PreferencesUI.py:1398
+msgid "Check this box to color mouse cursor."
+msgstr "Check this box to color mouse cursor."
+
+#: flatcamGUI/PreferencesUI.py:1405
+msgid "Set the color of the mouse cursor."
+msgstr "Set the color of the mouse cursor."
+
+#: flatcamGUI/PreferencesUI.py:1428
+msgid "Pan Button"
+msgstr "Pan Button"
+
+#: flatcamGUI/PreferencesUI.py:1430
+msgid ""
+"Select the mouse button to use for panning:\n"
+"- MMB --> Middle Mouse Button\n"
+"- RMB --> Right Mouse Button"
+msgstr ""
+"Select the mouse button to use for panning:\n"
+"- MMB --> Middle Mouse Button\n"
+"- RMB --> Right Mouse Button"
+
+#: flatcamGUI/PreferencesUI.py:1434
+msgid "MMB"
+msgstr "MMB"
+
+#: flatcamGUI/PreferencesUI.py:1435
+msgid "RMB"
+msgstr "RMB"
+
+#: flatcamGUI/PreferencesUI.py:1441
+msgid "Multiple Selection"
+msgstr "Multiple Selection"
+
+#: flatcamGUI/PreferencesUI.py:1443
+msgid "Select the key used for multiple selection."
+msgstr "Select the key used for multiple selection."
+
+#: flatcamGUI/PreferencesUI.py:1445
+msgid "CTRL"
+msgstr "CTRL"
+
+#: flatcamGUI/PreferencesUI.py:1446
+msgid "SHIFT"
+msgstr "SHIFT"
+
+#: flatcamGUI/PreferencesUI.py:1457
+msgid "Delete object confirmation"
+msgstr "Delete object confirmation"
+
+#: flatcamGUI/PreferencesUI.py:1459
+msgid ""
+"When checked the application will ask for user confirmation\n"
+"whenever the Delete object(s) event is triggered, either by\n"
+"menu shortcut or key shortcut."
+msgstr ""
+"When checked the application will ask for user confirmation\n"
+"whenever the Delete object(s) event is triggered, either by\n"
+"menu shortcut or key shortcut."
+
+#: flatcamGUI/PreferencesUI.py:1466
+msgid "\"Open\" behavior"
+msgstr "\"Open\" behavior"
+
+#: flatcamGUI/PreferencesUI.py:1468
+msgid ""
+"When checked the path for the last saved file is used when saving files,\n"
+"and the path for the last opened file is used when opening files.\n"
+"\n"
+"When unchecked the path for opening files is the one used last: either the\n"
+"path for saving files or the path for opening files."
+msgstr ""
+"When checked the path for the last saved file is used when saving files,\n"
+"and the path for the last opened file is used when opening files.\n"
+"\n"
+"When unchecked the path for opening files is the one used last: either the\n"
+"path for saving files or the path for opening files."
+
+#: flatcamGUI/PreferencesUI.py:1477
+msgid "Enable ToolTips"
+msgstr "Enable ToolTips"
+
+#: flatcamGUI/PreferencesUI.py:1479
+msgid ""
+"Check this box if you want to have toolTips displayed\n"
+"when hovering with mouse over items throughout the App."
+msgstr ""
+"Check this box if you want to have toolTips displayed\n"
+"when hovering with mouse over items throughout the App."
+
+#: flatcamGUI/PreferencesUI.py:1486
+msgid "Allow Machinist Unsafe Settings"
+msgstr "Allow Machinist Unsafe Settings"
+
+#: flatcamGUI/PreferencesUI.py:1488
+msgid ""
+"If checked, some of the application settings will be allowed\n"
+"to have values that are usually unsafe to use.\n"
+"Like Z travel negative values or Z Cut positive values.\n"
+"It will applied at the next application start.\n"
+"<<WARNING>>: Don't change this unless you know what you are doing !!!"
+msgstr ""
+"If checked, some of the application settings will be allowed\n"
+"to have values that are usually unsafe to use.\n"
+"Like Z travel negative values or Z Cut positive values.\n"
+"It will applied at the next application start.\n"
+"<<WARNING>>: Don't change this unless you know what you are doing !!!"
+
+#: flatcamGUI/PreferencesUI.py:1500
+msgid "Bookmarks limit"
+msgstr "Bookmarks limit"
+
+#: flatcamGUI/PreferencesUI.py:1502
+msgid ""
+"The maximum number of bookmarks that may be installed in the menu.\n"
+"The number of bookmarks in the bookmark manager may be greater\n"
+"but the menu will hold only so much."
+msgstr ""
+"The maximum number of bookmarks that may be installed in the menu.\n"
+"The number of bookmarks in the bookmark manager may be greater\n"
+"but the menu will hold only so much."
+
+#: flatcamGUI/PreferencesUI.py:1511
+msgid "Activity Icon"
+msgstr "Activity Icon"
+
+#: flatcamGUI/PreferencesUI.py:1513
+msgid "Select the GIF that show activity when FlatCAM is active."
+msgstr "Select the GIF that show activity when FlatCAM is active."
+
+#: flatcamGUI/PreferencesUI.py:1571
+msgid "App Preferences"
+msgstr "App Preferences"
+
+#: flatcamGUI/PreferencesUI.py:1581 flatcamGUI/PreferencesUI.py:1991
+#: flatcamGUI/PreferencesUI.py:2539 flatcamGUI/PreferencesUI.py:2986
+#: flatcamGUI/PreferencesUI.py:3695 flatcamTools/ToolDistance.py:56
+#: flatcamTools/ToolDistanceMin.py:50 flatcamTools/ToolPcbWizard.py:127
+#: flatcamTools/ToolProperties.py:154
+msgid "Units"
+msgstr "Units"
+
+#: flatcamGUI/PreferencesUI.py:1582
+msgid ""
+"The default value for FlatCAM units.\n"
+"Whatever is selected here is set every time\n"
+"FLatCAM is started."
+msgstr ""
+"The default value for FlatCAM units.\n"
+"Whatever is selected here is set every time\n"
+"FLatCAM is started."
+
+#: flatcamGUI/PreferencesUI.py:1585 flatcamGUI/PreferencesUI.py:1997
+#: flatcamGUI/PreferencesUI.py:2545 flatcamGUI/PreferencesUI.py:2997
+#: flatcamGUI/PreferencesUI.py:3701 flatcamTools/ToolCalculators.py:62
+#: flatcamTools/ToolPcbWizard.py:126
+msgid "MM"
+msgstr "MM"
+
+#: flatcamGUI/PreferencesUI.py:1586
+msgid "IN"
+msgstr "IN"
+
+#: flatcamGUI/PreferencesUI.py:1592
+msgid "Precision MM"
+msgstr "Precision MM"
+
+#: flatcamGUI/PreferencesUI.py:1594
+msgid ""
+"The number of decimals used throughout the application\n"
+"when the set units are in METRIC system.\n"
+"Any change here require an application restart."
+msgstr ""
+"The number of decimals used throughout the application\n"
+"when the set units are in METRIC system.\n"
+"Any change here require an application restart."
+
+#: flatcamGUI/PreferencesUI.py:1606
+msgid "Precision INCH"
+msgstr "Precision INCH"
+
+#: flatcamGUI/PreferencesUI.py:1608
+msgid ""
+"The number of decimals used throughout the application\n"
+"when the set units are in INCH system.\n"
+"Any change here require an application restart."
+msgstr ""
+"The number of decimals used throughout the application\n"
+"when the set units are in INCH system.\n"
+"Any change here require an application restart."
+
+#: flatcamGUI/PreferencesUI.py:1620
+msgid "Graphic Engine"
+msgstr "Graphic Engine"
+
+#: flatcamGUI/PreferencesUI.py:1621
+msgid ""
+"Choose what graphic engine to use in FlatCAM.\n"
+"Legacy(2D) -> reduced functionality, slow performance but enhanced "
+"compatibility.\n"
+"OpenGL(3D) -> full functionality, high performance\n"
+"Some graphic cards are too old and do not work in OpenGL(3D) mode, like:\n"
+"Intel HD3000 or older. In this case the plot area will be black therefore\n"
+"use the Legacy(2D) mode."
+msgstr ""
+"Choose what graphic engine to use in FlatCAM.\n"
+"Legacy(2D) -> reduced functionality, slow performance but enhanced "
+"compatibility.\n"
+"OpenGL(3D) -> full functionality, high performance\n"
+"Some graphic cards are too old and do not work in OpenGL(3D) mode, like:\n"
+"Intel HD3000 or older. In this case the plot area will be black therefore\n"
+"use the Legacy(2D) mode."
+
+#: flatcamGUI/PreferencesUI.py:1627
+msgid "Legacy(2D)"
+msgstr "Legacy(2D)"
+
+#: flatcamGUI/PreferencesUI.py:1628
+msgid "OpenGL(3D)"
+msgstr "OpenGL(3D)"
+
+#: flatcamGUI/PreferencesUI.py:1640
+msgid "APP. LEVEL"
+msgstr "APP. LEVEL"
+
+#: flatcamGUI/PreferencesUI.py:1641
+msgid ""
+"Choose the default level of usage for FlatCAM.\n"
+"BASIC level -> reduced functionality, best for beginner's.\n"
+"ADVANCED level -> full functionality.\n"
+"\n"
+"The choice here will influence the parameters in\n"
+"the Selected Tab for all kinds of FlatCAM objects."
+msgstr ""
+"Choose the default level of usage for FlatCAM.\n"
+"BASIC level -> reduced functionality, best for beginner's.\n"
+"ADVANCED level -> full functionality.\n"
+"\n"
+"The choice here will influence the parameters in\n"
+"the Selected Tab for all kinds of FlatCAM objects."
+
+#: flatcamGUI/PreferencesUI.py:1646 flatcamGUI/PreferencesUI.py:3041
+#: flatcamObjects/FlatCAMExcellon.py:614 flatcamObjects/FlatCAMGeometry.py:485
+#: flatcamObjects/FlatCAMGerber.py:250
+msgid "Basic"
+msgstr "Basic"
+
+#: flatcamGUI/PreferencesUI.py:1647 flatcamObjects/FlatCAMExcellon.py:627
+#: flatcamObjects/FlatCAMGeometry.py:506 flatcamObjects/FlatCAMGerber.py:277
+msgid "Advanced"
+msgstr "Advanced"
+
+#: flatcamGUI/PreferencesUI.py:1653
+msgid "Portable app"
+msgstr "Portable app"
+
+#: flatcamGUI/PreferencesUI.py:1654
+msgid ""
+"Choose if the application should run as portable.\n"
+"\n"
+"If Checked the application will run portable,\n"
+"which means that the preferences files will be saved\n"
+"in the application folder, in the lib\\config subfolder."
+msgstr ""
+"Choose if the application should run as portable.\n"
+"\n"
+"If Checked the application will run portable,\n"
+"which means that the preferences files will be saved\n"
+"in the application folder, in the lib\\config subfolder."
+
+#: flatcamGUI/PreferencesUI.py:1667
+msgid "Languages"
+msgstr "Languages"
+
+#: flatcamGUI/PreferencesUI.py:1668
+msgid "Set the language used throughout FlatCAM."
+msgstr "Set the language used throughout FlatCAM."
+
+#: flatcamGUI/PreferencesUI.py:1674
+msgid "Apply Language"
+msgstr "Apply Language"
+
+#: flatcamGUI/PreferencesUI.py:1675
+msgid ""
+"Set the language used throughout FlatCAM.\n"
+"The app will restart after click."
+msgstr ""
+"Set the language used throughout FlatCAM.\n"
+"The app will restart after click."
+
+#: flatcamGUI/PreferencesUI.py:1689
+msgid "Startup Settings"
+msgstr "Startup Settings"
+
+#: flatcamGUI/PreferencesUI.py:1693
+msgid "Splash Screen"
+msgstr "Splash Screen"
+
+#: flatcamGUI/PreferencesUI.py:1695
+msgid "Enable display of the splash screen at application startup."
+msgstr "Enable display of the splash screen at application startup."
+
+#: flatcamGUI/PreferencesUI.py:1707
+msgid "Sys Tray Icon"
+msgstr "Sys Tray Icon"
+
+#: flatcamGUI/PreferencesUI.py:1709
+msgid "Enable display of FlatCAM icon in Sys Tray."
+msgstr "Enable display of FlatCAM icon in Sys Tray."
+
+#: flatcamGUI/PreferencesUI.py:1714
+msgid "Show Shell"
+msgstr "Show Shell"
+
+#: flatcamGUI/PreferencesUI.py:1716
+msgid ""
+"Check this box if you want the shell to\n"
+"start automatically at startup."
+msgstr ""
+"Check this box if you want the shell to\n"
+"start automatically at startup."
+
+#: flatcamGUI/PreferencesUI.py:1723
+msgid "Show Project"
+msgstr "Show Project"
+
+#: flatcamGUI/PreferencesUI.py:1725
+msgid ""
+"Check this box if you want the project/selected/tool tab area to\n"
+"to be shown automatically at startup."
+msgstr ""
+"Check this box if you want the project/selected/tool tab area to\n"
+"to be shown automatically at startup."
+
+#: flatcamGUI/PreferencesUI.py:1731
+msgid "Version Check"
+msgstr "Version Check"
+
+#: flatcamGUI/PreferencesUI.py:1733
+msgid ""
+"Check this box if you want to check\n"
+"for a new version automatically at startup."
+msgstr ""
+"Check this box if you want to check\n"
+"for a new version automatically at startup."
+
+#: flatcamGUI/PreferencesUI.py:1740
+msgid "Send Statistics"
+msgstr "Send Statistics"
+
+#: flatcamGUI/PreferencesUI.py:1742
+msgid ""
+"Check this box if you agree to send anonymous\n"
+"stats automatically at startup, to help improve FlatCAM."
+msgstr ""
+"Check this box if you agree to send anonymous\n"
+"stats automatically at startup, to help improve FlatCAM."
+
+#: flatcamGUI/PreferencesUI.py:1756
+msgid "Workers number"
+msgstr "Workers number"
+
+#: flatcamGUI/PreferencesUI.py:1758
+msgid ""
+"The number of Qthreads made available to the App.\n"
+"A bigger number may finish the jobs more quickly but\n"
+"depending on your computer speed, may make the App\n"
+"unresponsive. Can have a value between 2 and 16.\n"
+"Default value is 2.\n"
+"After change, it will be applied at next App start."
+msgstr ""
+"The number of Qthreads made available to the App.\n"
+"A bigger number may finish the jobs more quickly but\n"
+"depending on your computer speed, may make the App\n"
+"unresponsive. Can have a value between 2 and 16.\n"
+"Default value is 2.\n"
+"After change, it will be applied at next App start."
+
+#: flatcamGUI/PreferencesUI.py:1772
+msgid "Geo Tolerance"
+msgstr "Geo Tolerance"
+
+#: flatcamGUI/PreferencesUI.py:1774
+msgid ""
+"This value can counter the effect of the Circle Steps\n"
+"parameter. Default value is 0.005.\n"
+"A lower value will increase the detail both in image\n"
+"and in Gcode for the circles, with a higher cost in\n"
+"performance. Higher value will provide more\n"
+"performance at the expense of level of detail."
+msgstr ""
+"This value can counter the effect of the Circle Steps\n"
+"parameter. Default value is 0.005.\n"
+"A lower value will increase the detail both in image\n"
+"and in Gcode for the circles, with a higher cost in\n"
+"performance. Higher value will provide more\n"
+"performance at the expense of level of detail."
+
+#: flatcamGUI/PreferencesUI.py:1794
+msgid "Save Settings"
+msgstr "Save Settings"
+
+#: flatcamGUI/PreferencesUI.py:1798
+msgid "Save Compressed Project"
+msgstr "Save Compressed Project"
+
+#: flatcamGUI/PreferencesUI.py:1800
+msgid ""
+"Whether to save a compressed or uncompressed project.\n"
+"When checked it will save a compressed FlatCAM project."
+msgstr ""
+"Whether to save a compressed or uncompressed project.\n"
+"When checked it will save a compressed FlatCAM project."
+
+#: flatcamGUI/PreferencesUI.py:1809
+msgid "Compression"
+msgstr "Compression"
+
+#: flatcamGUI/PreferencesUI.py:1811
+msgid ""
+"The level of compression used when saving\n"
+"a FlatCAM project. Higher value means better compression\n"
+"but require more RAM usage and more processing time."
+msgstr ""
+"The level of compression used when saving\n"
+"a FlatCAM project. Higher value means better compression\n"
+"but require more RAM usage and more processing time."
+
+#: flatcamGUI/PreferencesUI.py:1822
+msgid "Enable Auto Save"
+msgstr "Enable Auto Save"
+
+#: flatcamGUI/PreferencesUI.py:1824
+msgid ""
+"Check to enable the autosave feature.\n"
+"When enabled, the application will try to save a project\n"
+"at the set interval."
+msgstr ""
+"Check to enable the autosave feature.\n"
+"When enabled, the application will try to save a project\n"
+"at the set interval."
+
+#: flatcamGUI/PreferencesUI.py:1834
+msgid "Interval"
+msgstr "Interval"
+
+#: flatcamGUI/PreferencesUI.py:1836
+msgid ""
+"Time interval for autosaving. In milliseconds.\n"
+"The application will try to save periodically but only\n"
+"if the project was saved manually at least once.\n"
+"While active, some operations may block this feature."
+msgstr ""
+"Time interval for autosaving. In milliseconds.\n"
+"The application will try to save periodically but only\n"
+"if the project was saved manually at least once.\n"
+"While active, some operations may block this feature."
+
+#: flatcamGUI/PreferencesUI.py:1852
+msgid "Text to PDF parameters"
+msgstr "Text to PDF parameters"
+
+#: flatcamGUI/PreferencesUI.py:1854
+msgid "Used when saving text in Code Editor or in FlatCAM Document objects."
+msgstr "Used when saving text in Code Editor or in FlatCAM Document objects."
+
+#: flatcamGUI/PreferencesUI.py:1863
+msgid "Top Margin"
+msgstr "Top Margin"
+
+#: flatcamGUI/PreferencesUI.py:1865
+msgid "Distance between text body and the top of the PDF file."
+msgstr "Distance between text body and the top of the PDF file."
+
+#: flatcamGUI/PreferencesUI.py:1876
+msgid "Bottom Margin"
+msgstr "Bottom Margin"
+
+#: flatcamGUI/PreferencesUI.py:1878
+msgid "Distance between text body and the bottom of the PDF file."
+msgstr "Distance between text body and the bottom of the PDF file."
+
+#: flatcamGUI/PreferencesUI.py:1889
+msgid "Left Margin"
+msgstr "Left Margin"
+
+#: flatcamGUI/PreferencesUI.py:1891
+msgid "Distance between text body and the left of the PDF file."
+msgstr "Distance between text body and the left of the PDF file."
+
+#: flatcamGUI/PreferencesUI.py:1902
+msgid "Right Margin"
+msgstr "Right Margin"
+
+#: flatcamGUI/PreferencesUI.py:1904
+msgid "Distance between text body and the right of the PDF file."
+msgstr "Distance between text body and the right of the PDF file."
+
+#: flatcamGUI/PreferencesUI.py:1936
+msgid "Gerber General"
+msgstr "Gerber General"
+
+#: flatcamGUI/PreferencesUI.py:1954
+msgid "M-Color"
+msgstr "M-Color"
+
+#: flatcamGUI/PreferencesUI.py:1968 flatcamGUI/PreferencesUI.py:4137
+#: flatcamGUI/PreferencesUI.py:4735 flatcamGUI/PreferencesUI.py:7654
+msgid "Circle Steps"
+msgstr "Circle Steps"
+
+#: flatcamGUI/PreferencesUI.py:1970
+msgid ""
+"The number of circle steps for Gerber \n"
+"circular aperture linear approximation."
+msgstr ""
+"The number of circle steps for Gerber \n"
+"circular aperture linear approximation."
+
+#: flatcamGUI/PreferencesUI.py:1982
+msgid "Default Values"
+msgstr "Default Values"
+
+#: flatcamGUI/PreferencesUI.py:1984
+msgid ""
+"Those values will be used as fallback values\n"
+"in case that they are not found in the Gerber file."
+msgstr ""
+"Those values will be used as fallback values\n"
+"in case that they are not found in the Gerber file."
+
+#: flatcamGUI/PreferencesUI.py:1993 flatcamGUI/PreferencesUI.py:1999
+#: flatcamGUI/PreferencesUI.py:2541 flatcamGUI/PreferencesUI.py:2547
+msgid "The units used in the Gerber file."
+msgstr "The units used in the Gerber file."
+
+#: flatcamGUI/PreferencesUI.py:1996 flatcamGUI/PreferencesUI.py:2544
+#: flatcamGUI/PreferencesUI.py:2910 flatcamGUI/PreferencesUI.py:2996
+#: flatcamGUI/PreferencesUI.py:3700 flatcamTools/ToolCalculators.py:61
+#: flatcamTools/ToolPcbWizard.py:125
+msgid "INCH"
+msgstr "INCH"
+
+#: flatcamGUI/PreferencesUI.py:2006 flatcamGUI/PreferencesUI.py:2593
+#: flatcamGUI/PreferencesUI.py:2968 flatcamGUI/PreferencesUI.py:3768
+msgid "Zeros"
+msgstr "Zeros"
+
+#: flatcamGUI/PreferencesUI.py:2009 flatcamGUI/PreferencesUI.py:2019
+#: flatcamGUI/PreferencesUI.py:2596 flatcamGUI/PreferencesUI.py:2606
+msgid ""
+"This sets the type of Gerber zeros.\n"
+"If LZ then Leading Zeros are removed and\n"
+"Trailing Zeros are kept.\n"
+"If TZ is checked then Trailing Zeros are removed\n"
+"and Leading Zeros are kept."
+msgstr ""
+"This sets the type of Gerber zeros.\n"
+"If LZ then Leading Zeros are removed and\n"
+"Trailing Zeros are kept.\n"
+"If TZ is checked then Trailing Zeros are removed\n"
+"and Leading Zeros are kept."
+
+#: flatcamGUI/PreferencesUI.py:2016 flatcamGUI/PreferencesUI.py:2603
+#: flatcamGUI/PreferencesUI.py:2981 flatcamGUI/PreferencesUI.py:3778
+#: flatcamTools/ToolPcbWizard.py:111
+msgid "LZ"
+msgstr "LZ"
+
+#: flatcamGUI/PreferencesUI.py:2017 flatcamGUI/PreferencesUI.py:2604
+#: flatcamGUI/PreferencesUI.py:2982 flatcamGUI/PreferencesUI.py:3779
+#: flatcamTools/ToolPcbWizard.py:112
+msgid "TZ"
+msgstr "TZ"
+
+#: flatcamGUI/PreferencesUI.py:2035
+msgid "Clean Apertures"
+msgstr "Clean Apertures"
+
+#: flatcamGUI/PreferencesUI.py:2037
+msgid ""
+"Will remove apertures that do not have geometry\n"
+"thus lowering the number of apertures in the Gerber object."
+msgstr ""
+"Will remove apertures that do not have geometry\n"
+"thus lowering the number of apertures in the Gerber object."
+
+#: flatcamGUI/PreferencesUI.py:2043
+msgid "Polarity change buffer"
+msgstr "Polarity change buffer"
+
+#: flatcamGUI/PreferencesUI.py:2045
+msgid ""
+"Will apply extra buffering for the\n"
+"solid geometry when we have polarity changes.\n"
+"May help loading Gerber files that otherwise\n"
+"do not load correctly."
+msgstr ""
+"Will apply extra buffering for the\n"
+"solid geometry when we have polarity changes.\n"
+"May help loading Gerber files that otherwise\n"
+"do not load correctly."
+
+#: flatcamGUI/PreferencesUI.py:2058
+msgid "Gerber Object Color"
+msgstr "Gerber Object Color"
+
+#: flatcamGUI/PreferencesUI.py:2064 flatcamGUI/PreferencesUI.py:3087
+#: flatcamGUI/PreferencesUI.py:4176
+msgid "Set the line color for plotted objects."
+msgstr "Set the line color for plotted objects."
+
+#: flatcamGUI/PreferencesUI.py:2081 flatcamGUI/PreferencesUI.py:3104
+#: flatcamGUI/PreferencesUI.py:4846 flatcamGUI/PreferencesUI.py:4912
+msgid ""
+"Set the fill color for plotted objects.\n"
+"First 6 digits are the color and the last 2\n"
+"digits are for alpha (transparency) level."
+msgstr ""
+"Set the fill color for plotted objects.\n"
+"First 6 digits are the color and the last 2\n"
+"digits are for alpha (transparency) level."
+
+#: flatcamGUI/PreferencesUI.py:2100 flatcamGUI/PreferencesUI.py:3123
+#: flatcamGUI/PreferencesUI.py:4865
+msgid "Set the fill transparency for plotted objects."
+msgstr "Set the fill transparency for plotted objects."
+
+#: flatcamGUI/PreferencesUI.py:2191
+msgid "Gerber Options"
+msgstr "Gerber Options"
+
+#: flatcamGUI/PreferencesUI.py:2269
+msgid "Combine Passes"
+msgstr "Combine Passes"
+
+#: flatcamGUI/PreferencesUI.py:2357
+msgid "Gerber Adv. Options"
+msgstr "Gerber Adv. Options"
+
+#: flatcamGUI/PreferencesUI.py:2361 flatcamGUI/PreferencesUI.py:3551
+#: flatcamGUI/PreferencesUI.py:4472
+msgid "Advanced Options"
+msgstr "Advanced Options"
+
+#: flatcamGUI/PreferencesUI.py:2363
+msgid ""
+"A list of Gerber advanced parameters.\n"
+"Those parameters are available only for\n"
+"Advanced App. Level."
+msgstr ""
+"A list of Gerber advanced parameters.\n"
+"Those parameters are available only for\n"
+"Advanced App. Level."
+
+#: flatcamGUI/PreferencesUI.py:2382
+msgid "Table Show/Hide"
+msgstr "Table Show/Hide"
+
+#: flatcamGUI/PreferencesUI.py:2384
+msgid ""
+"Toggle the display of the Gerber Apertures Table.\n"
+"Also, on hide, it will delete all mark shapes\n"
+"that are drawn on canvas."
+msgstr ""
+"Toggle the display of the Gerber Apertures Table.\n"
+"Also, on hide, it will delete all mark shapes\n"
+"that are drawn on canvas."
+
+#: flatcamGUI/PreferencesUI.py:2464
+msgid "Exterior"
+msgstr "Exterior"
+
+#: flatcamGUI/PreferencesUI.py:2465
+msgid "Interior"
+msgstr "Interior"
+
+#: flatcamGUI/PreferencesUI.py:2478
+msgid ""
+"Buffering type:\n"
+"- None --> best performance, fast file loading but no so good display\n"
+"- Full --> slow file loading but good visuals. This is the default.\n"
+"<<WARNING>>: Don't change this unless you know what you are doing !!!"
+msgstr ""
+"Buffering type:\n"
+"- None --> best performance, fast file loading but no so good display\n"
+"- Full --> slow file loading but good visuals. This is the default.\n"
+"<<WARNING>>: Don't change this unless you know what you are doing !!!"
+
+#: flatcamGUI/PreferencesUI.py:2483 flatcamGUI/PreferencesUI.py:6340
+#: flatcamGUI/PreferencesUI.py:7952 flatcamTools/ToolFiducials.py:201
+#: flatcamTools/ToolFilm.py:255 flatcamTools/ToolProperties.py:452
+#: flatcamTools/ToolProperties.py:455 flatcamTools/ToolProperties.py:458
+#: flatcamTools/ToolProperties.py:483
+msgid "None"
+msgstr "None"
+
+#: flatcamGUI/PreferencesUI.py:2489
+msgid "Simplify"
+msgstr "Simplify"
+
+#: flatcamGUI/PreferencesUI.py:2491
+msgid ""
+"When checked all the Gerber polygons will be\n"
+"loaded with simplification having a set tolerance.\n"
+"<<WARNING>>: Don't change this unless you know what you are doing !!!"
+msgstr ""
+"When checked all the Gerber polygons will be\n"
+"loaded with simplification having a set tolerance.\n"
+"<<WARNING>>: Don't change this unless you know what you are doing !!!"
+
+#: flatcamGUI/PreferencesUI.py:2498
+msgid "Tolerance"
+msgstr "Tolerance"
+
+#: flatcamGUI/PreferencesUI.py:2499
+msgid "Tolerance for polygon simplification."
+msgstr "Tolerance for polygon simplification."
+
+#: flatcamGUI/PreferencesUI.py:2524
+msgid "Gerber Export"
+msgstr "Gerber Export"
+
+#: flatcamGUI/PreferencesUI.py:2528 flatcamGUI/PreferencesUI.py:3684
+msgid "Export Options"
+msgstr "Export Options"
+
+#: flatcamGUI/PreferencesUI.py:2530
+msgid ""
+"The parameters set here are used in the file exported\n"
+"when using the File -> Export -> Export Gerber menu entry."
+msgstr ""
+"The parameters set here are used in the file exported\n"
+"when using the File -> Export -> Export Gerber menu entry."
+
+#: flatcamGUI/PreferencesUI.py:2553 flatcamGUI/PreferencesUI.py:3709
+msgid "Int/Decimals"
+msgstr "Int/Decimals"
+
+#: flatcamGUI/PreferencesUI.py:2555
+msgid ""
+"The number of digits in the whole part of the number\n"
+"and in the fractional part of the number."
+msgstr ""
+"The number of digits in the whole part of the number\n"
+"and in the fractional part of the number."
+
+#: flatcamGUI/PreferencesUI.py:2568
+msgid ""
+"This numbers signify the number of digits in\n"
+"the whole part of Gerber coordinates."
+msgstr ""
+"This numbers signify the number of digits in\n"
+"the whole part of Gerber coordinates."
+
+#: flatcamGUI/PreferencesUI.py:2584
+msgid ""
+"This numbers signify the number of digits in\n"
+"the decimal part of Gerber coordinates."
+msgstr ""
+"This numbers signify the number of digits in\n"
+"the decimal part of Gerber coordinates."
+
+#: flatcamGUI/PreferencesUI.py:2629
+msgid "A list of Gerber Editor parameters."
+msgstr "A list of Gerber Editor parameters."
+
+#: flatcamGUI/PreferencesUI.py:2637 flatcamGUI/PreferencesUI.py:3843
+#: flatcamGUI/PreferencesUI.py:4650 flatcamGUI/PreferencesUI.py:7615
+msgid "Selection limit"
+msgstr "Selection limit"
+
+#: flatcamGUI/PreferencesUI.py:2639
+msgid ""
+"Set the number of selected Gerber geometry\n"
+"items above which the utility geometry\n"
+"becomes just a selection rectangle.\n"
+"Increases the performance when moving a\n"
+"large number of geometric elements."
+msgstr ""
+"Set the number of selected Gerber geometry\n"
+"items above which the utility geometry\n"
+"becomes just a selection rectangle.\n"
+"Increases the performance when moving a\n"
+"large number of geometric elements."
+
+#: flatcamGUI/PreferencesUI.py:2652
+msgid "New Aperture code"
+msgstr "New Aperture code"
+
+#: flatcamGUI/PreferencesUI.py:2665
+msgid "New Aperture size"
+msgstr "New Aperture size"
+
+#: flatcamGUI/PreferencesUI.py:2667
+msgid "Size for the new aperture"
+msgstr "Size for the new aperture"
+
+#: flatcamGUI/PreferencesUI.py:2678
+msgid "New Aperture type"
+msgstr "New Aperture type"
+
+#: flatcamGUI/PreferencesUI.py:2680
+msgid ""
+"Type for the new aperture.\n"
+"Can be 'C', 'R' or 'O'."
+msgstr ""
+"Type for the new aperture.\n"
+"Can be 'C', 'R' or 'O'."
+
+#: flatcamGUI/PreferencesUI.py:2702
+msgid "Aperture Dimensions"
+msgstr "Aperture Dimensions"
+
+#: flatcamGUI/PreferencesUI.py:2704 flatcamGUI/PreferencesUI.py:4155
+#: flatcamGUI/PreferencesUI.py:5322 flatcamGUI/PreferencesUI.py:5889
+#: flatcamGUI/PreferencesUI.py:6955
+msgid ""
+"Diameters of the tools, separated by comma.\n"
+"The value of the diameter has to use the dot decimals separator.\n"
+"Valid values: 0.3, 1.0"
+msgstr ""
+"Diameters of the tools, separated by comma.\n"
+"The value of the diameter has to use the dot decimals separator.\n"
+"Valid values: 0.3, 1.0"
+
+#: flatcamGUI/PreferencesUI.py:2712
+msgid "Linear Pad Array"
+msgstr "Linear Pad Array"
+
+#: flatcamGUI/PreferencesUI.py:2716 flatcamGUI/PreferencesUI.py:3887
+#: flatcamGUI/PreferencesUI.py:4035
+msgid "Linear Direction"
+msgstr "Linear Direction"
+
+#: flatcamGUI/PreferencesUI.py:2756
+msgid "Circular Pad Array"
+msgstr "Circular Pad Array"
+
+#: flatcamGUI/PreferencesUI.py:2760 flatcamGUI/PreferencesUI.py:3933
+#: flatcamGUI/PreferencesUI.py:4083
+msgid "Circular Direction"
+msgstr "Circular Direction"
+
+#: flatcamGUI/PreferencesUI.py:2762 flatcamGUI/PreferencesUI.py:3935
+#: flatcamGUI/PreferencesUI.py:4085
+msgid ""
+"Direction for circular array.\n"
+"Can be CW = clockwise or CCW = counter clockwise."
+msgstr ""
+"Direction for circular array.\n"
+"Can be CW = clockwise or CCW = counter clockwise."
+
+#: flatcamGUI/PreferencesUI.py:2773 flatcamGUI/PreferencesUI.py:3946
+#: flatcamGUI/PreferencesUI.py:4096
+msgid "Circular Angle"
+msgstr "Circular Angle"
+
+#: flatcamGUI/PreferencesUI.py:2792
+msgid "Distance at which to buffer the Gerber element."
+msgstr "Distance at which to buffer the Gerber element."
+
+#: flatcamGUI/PreferencesUI.py:2801
+msgid "Scale Tool"
+msgstr "Scale Tool"
+
+#: flatcamGUI/PreferencesUI.py:2807
+msgid "Factor to scale the Gerber element."
+msgstr "Factor to scale the Gerber element."
+
+#: flatcamGUI/PreferencesUI.py:2820
+msgid "Threshold low"
+msgstr "Threshold low"
+
+#: flatcamGUI/PreferencesUI.py:2822
+msgid "Threshold value under which the apertures are not marked."
+msgstr "Threshold value under which the apertures are not marked."
+
+#: flatcamGUI/PreferencesUI.py:2832
+msgid "Threshold high"
+msgstr "Threshold high"
+
+#: flatcamGUI/PreferencesUI.py:2834
+msgid "Threshold value over which the apertures are not marked."
+msgstr "Threshold value over which the apertures are not marked."
+
+#: flatcamGUI/PreferencesUI.py:2852
+msgid "Excellon General"
+msgstr "Excellon General"
+
+#: flatcamGUI/PreferencesUI.py:2885
+msgid "Excellon Format"
+msgstr "Excellon Format"
+
+#: flatcamGUI/PreferencesUI.py:2887
+msgid ""
+"The NC drill files, usually named Excellon files\n"
+"are files that can be found in different formats.\n"
+"Here we set the format used when the provided\n"
+"coordinates are not using period.\n"
+"\n"
+"Possible presets:\n"
+"\n"
+"PROTEUS 3:3 MM LZ\n"
+"DipTrace 5:2 MM TZ\n"
+"DipTrace 4:3 MM LZ\n"
+"\n"
+"EAGLE 3:3 MM TZ\n"
+"EAGLE 4:3 MM TZ\n"
+"EAGLE 2:5 INCH TZ\n"
+"EAGLE 3:5 INCH TZ\n"
+"\n"
+"ALTIUM 2:4 INCH LZ\n"
+"Sprint Layout 2:4 INCH LZ\n"
+"KiCAD 3:5 INCH TZ"
+msgstr ""
+"The NC drill files, usually named Excellon files\n"
+"are files that can be found in different formats.\n"
+"Here we set the format used when the provided\n"
+"coordinates are not using period.\n"
+"\n"
+"Possible presets:\n"
+"\n"
+"PROTEUS 3:3 MM LZ\n"
+"DipTrace 5:2 MM TZ\n"
+"DipTrace 4:3 MM LZ\n"
+"\n"
+"EAGLE 3:3 MM TZ\n"
+"EAGLE 4:3 MM TZ\n"
+"EAGLE 2:5 INCH TZ\n"
+"EAGLE 3:5 INCH TZ\n"
+"\n"
+"ALTIUM 2:4 INCH LZ\n"
+"Sprint Layout 2:4 INCH LZ\n"
+"KiCAD 3:5 INCH TZ"
+
+#: flatcamGUI/PreferencesUI.py:2911
+msgid "Default values for INCH are 2:4"
+msgstr "Default values for INCH are 2:4"
+
+#: flatcamGUI/PreferencesUI.py:2918 flatcamGUI/PreferencesUI.py:2947
+#: flatcamGUI/PreferencesUI.py:3723
+msgid ""
+"This numbers signify the number of digits in\n"
+"the whole part of Excellon coordinates."
+msgstr ""
+"This numbers signify the number of digits in\n"
+"the whole part of Excellon coordinates."
+
+#: flatcamGUI/PreferencesUI.py:2931 flatcamGUI/PreferencesUI.py:2960
+#: flatcamGUI/PreferencesUI.py:3736
+msgid ""
+"This numbers signify the number of digits in\n"
+"the decimal part of Excellon coordinates."
+msgstr ""
+"This numbers signify the number of digits in\n"
+"the decimal part of Excellon coordinates."
+
+#: flatcamGUI/PreferencesUI.py:2939
+msgid "METRIC"
+msgstr "METRIC"
+
+#: flatcamGUI/PreferencesUI.py:2940
+msgid "Default values for METRIC are 3:3"
+msgstr "Default values for METRIC are 3:3"
+
+#: flatcamGUI/PreferencesUI.py:2971
+msgid ""
+"This sets the type of Excellon zeros.\n"
+"If LZ then Leading Zeros are kept and\n"
+"Trailing Zeros are removed.\n"
+"If TZ is checked then Trailing Zeros are kept\n"
+"and Leading Zeros are removed.\n"
+"\n"
+"This is used when there is no information\n"
+"stored in the Excellon file."
+msgstr ""
+"This sets the type of Excellon zeros.\n"
+"If LZ then Leading Zeros are kept and\n"
+"Trailing Zeros are removed.\n"
+"If TZ is checked then Trailing Zeros are kept\n"
+"and Leading Zeros are removed.\n"
+"\n"
+"This is used when there is no information\n"
+"stored in the Excellon file."
+
+#: flatcamGUI/PreferencesUI.py:2989
+msgid ""
+"This sets the default units of Excellon files.\n"
+"If it is not detected in the parsed file the value here\n"
+"will be used.Some Excellon files don't have an header\n"
+"therefore this parameter will be used."
+msgstr ""
+"This sets the default units of Excellon files.\n"
+"If it is not detected in the parsed file the value here\n"
+"will be used.Some Excellon files don't have an header\n"
+"therefore this parameter will be used."
+
+#: flatcamGUI/PreferencesUI.py:2999
+msgid ""
+"This sets the units of Excellon files.\n"
+"Some Excellon files don't have an header\n"
+"therefore this parameter will be used."
+msgstr ""
+"This sets the units of Excellon files.\n"
+"Some Excellon files don't have an header\n"
+"therefore this parameter will be used."
+
+#: flatcamGUI/PreferencesUI.py:3007
+msgid "Update Export settings"
+msgstr "Update Export settings"
+
+#: flatcamGUI/PreferencesUI.py:3024
+msgid "Excellon Optimization"
+msgstr "Excellon Optimization"
+
+#: flatcamGUI/PreferencesUI.py:3027
+msgid "Algorithm:"
+msgstr "Algorithm:"
+
+#: flatcamGUI/PreferencesUI.py:3029 flatcamGUI/PreferencesUI.py:3045
+msgid ""
+"This sets the optimization type for the Excellon drill path.\n"
+"If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
+"MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
+"If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
+"If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
+"drill path optimization.\n"
+"\n"
+"If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
+"Travelling Salesman algorithm for path optimization."
+msgstr ""
+"This sets the optimization type for the Excellon drill path.\n"
+"If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
+"MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
+"If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
+"If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
+"drill path optimization.\n"
+"\n"
+"If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
+"Travelling Salesman algorithm for path optimization."
+
+#: flatcamGUI/PreferencesUI.py:3040
+msgid "MetaHeuristic"
+msgstr "MetaHeuristic"
+
+#: flatcamGUI/PreferencesUI.py:3042
+msgid "TSA"
+msgstr "TSA"
+
+#: flatcamGUI/PreferencesUI.py:3059 flatcamGUI/PreferencesUI.py:3463
+#: flatcamGUI/PreferencesUI.py:4430
+msgid "Duration"
+msgstr "Duration"
+
+#: flatcamGUI/PreferencesUI.py:3062
+msgid ""
+"When OR-Tools Metaheuristic (MH) is enabled there is a\n"
+"maximum threshold for how much time is spent doing the\n"
+"path optimization. This max duration is set here.\n"
+"In seconds."
+msgstr ""
+"When OR-Tools Metaheuristic (MH) is enabled there is a\n"
+"maximum threshold for how much time is spent doing the\n"
+"path optimization. This max duration is set here.\n"
+"In seconds."
+
+#: flatcamGUI/PreferencesUI.py:3081
+msgid "Excellon Object Color"
+msgstr "Excellon Object Color"
+
+#: flatcamGUI/PreferencesUI.py:3247
+msgid "Excellon Options"
+msgstr "Excellon Options"
+
+#: flatcamGUI/PreferencesUI.py:3251 flatcamGUI/PreferencesUI.py:4227
+msgid "Create CNC Job"
+msgstr "Create CNC Job"
+
+#: flatcamGUI/PreferencesUI.py:3253
+msgid ""
+"Parameters used to create a CNC Job object\n"
+"for this drill object."
+msgstr ""
+"Parameters used to create a CNC Job object\n"
+"for this drill object."
+
+#: flatcamGUI/PreferencesUI.py:3370 flatcamGUI/PreferencesUI.py:4314
+msgid "Tool change"
+msgstr "Tool change"
+
+#: flatcamGUI/PreferencesUI.py:3454 flatcamGUI/PreferencesUI.py:4425
+msgid "Enable Dwell"
+msgstr "Enable Dwell"
+
+#: flatcamGUI/PreferencesUI.py:3477
+msgid ""
+"The preprocessor JSON file that dictates\n"
+"Gcode output."
+msgstr ""
+"The preprocessor JSON file that dictates\n"
+"Gcode output."
+
+#: flatcamGUI/PreferencesUI.py:3488
+msgid "Gcode"
+msgstr "Gcode"
+
+#: flatcamGUI/PreferencesUI.py:3490
+msgid ""
+"Choose what to use for GCode generation:\n"
+"'Drills', 'Slots' or 'Both'.\n"
+"When choosing 'Slots' or 'Both', slots will be\n"
+"converted to drills."
+msgstr ""
+"Choose what to use for GCode generation:\n"
+"'Drills', 'Slots' or 'Both'.\n"
+"When choosing 'Slots' or 'Both', slots will be\n"
+"converted to drills."
+
+#: flatcamGUI/PreferencesUI.py:3506
+msgid "Mill Holes"
+msgstr "Mill Holes"
+
+#: flatcamGUI/PreferencesUI.py:3508
+msgid "Create Geometry for milling holes."
+msgstr "Create Geometry for milling holes."
+
+#: flatcamGUI/PreferencesUI.py:3512
+msgid "Drill Tool dia"
+msgstr "Drill Tool dia"
+
+#: flatcamGUI/PreferencesUI.py:3523
+msgid "Slot Tool dia"
+msgstr "Slot Tool dia"
+
+#: flatcamGUI/PreferencesUI.py:3525
+msgid ""
+"Diameter of the cutting tool\n"
+"when milling slots."
+msgstr ""
+"Diameter of the cutting tool\n"
+"when milling slots."
+
+#: flatcamGUI/PreferencesUI.py:3544
+msgid "Excellon Adv. Options"
+msgstr "Excellon Adv. Options"
+
+#: flatcamGUI/PreferencesUI.py:3553
+msgid ""
+"A list of Excellon advanced parameters.\n"
+"Those parameters are available only for\n"
+"Advanced App. Level."
+msgstr ""
+"A list of Excellon advanced parameters.\n"
+"Those parameters are available only for\n"
+"Advanced App. Level."
+
+#: flatcamGUI/PreferencesUI.py:3576
+msgid "Toolchange X,Y"
+msgstr "Toolchange X,Y"
+
+#: flatcamGUI/PreferencesUI.py:3578 flatcamGUI/PreferencesUI.py:4486
+msgid "Toolchange X,Y position."
+msgstr "Toolchange X,Y position."
+
+#: flatcamGUI/PreferencesUI.py:3638 flatcamGUI/PreferencesUI.py:4573
+msgid "Spindle direction"
+msgstr "Spindle direction"
+
+#: flatcamGUI/PreferencesUI.py:3640 flatcamGUI/PreferencesUI.py:4575
+msgid ""
+"This sets the direction that the spindle is rotating.\n"
+"It can be either:\n"
+"- CW = clockwise or\n"
+"- CCW = counter clockwise"
+msgstr ""
+"This sets the direction that the spindle is rotating.\n"
+"It can be either:\n"
+"- CW = clockwise or\n"
+"- CCW = counter clockwise"
+
+#: flatcamGUI/PreferencesUI.py:3651 flatcamGUI/PreferencesUI.py:4587
+msgid "Fast Plunge"
+msgstr "Fast Plunge"
+
+#: flatcamGUI/PreferencesUI.py:3653 flatcamGUI/PreferencesUI.py:4589
+msgid ""
+"By checking this, the vertical move from\n"
+"Z_Toolchange to Z_move is done with G0,\n"
+"meaning the fastest speed available.\n"
+"WARNING: the move is done at Toolchange X,Y coords."
+msgstr ""
+"By checking this, the vertical move from\n"
+"Z_Toolchange to Z_move is done with G0,\n"
+"meaning the fastest speed available.\n"
+"WARNING: the move is done at Toolchange X,Y coords."
+
+#: flatcamGUI/PreferencesUI.py:3660
+msgid "Fast Retract"
+msgstr "Fast Retract"
+
+#: flatcamGUI/PreferencesUI.py:3662
+msgid ""
+"Exit hole strategy.\n"
+" - When uncheked, while exiting the drilled hole the drill bit\n"
+"will travel slow, with set feedrate (G1), up to zero depth and then\n"
+"travel as fast as possible (G0) to the Z Move (travel height).\n"
+" - When checked the travel from Z cut (cut depth) to Z_move\n"
+"(travel height) is done as fast as possible (G0) in one move."
+msgstr ""
+"Exit hole strategy.\n"
+" - When uncheked, while exiting the drilled hole the drill bit\n"
+"will travel slow, with set feedrate (G1), up to zero depth and then\n"
+"travel as fast as possible (G0) to the Z Move (travel height).\n"
+" - When checked the travel from Z cut (cut depth) to Z_move\n"
+"(travel height) is done as fast as possible (G0) in one move."
+
+#: flatcamGUI/PreferencesUI.py:3680
+msgid "Excellon Export"
+msgstr "Excellon Export"
+
+#: flatcamGUI/PreferencesUI.py:3686
+msgid ""
+"The parameters set here are used in the file exported\n"
+"when using the File -> Export -> Export Excellon menu entry."
+msgstr ""
+"The parameters set here are used in the file exported\n"
+"when using the File -> Export -> Export Excellon menu entry."
+
+#: flatcamGUI/PreferencesUI.py:3697 flatcamGUI/PreferencesUI.py:3703
+msgid "The units used in the Excellon file."
+msgstr "The units used in the Excellon file."
+
+#: flatcamGUI/PreferencesUI.py:3711
+msgid ""
+"The NC drill files, usually named Excellon files\n"
+"are files that can be found in different formats.\n"
+"Here we set the format used when the provided\n"
+"coordinates are not using period."
+msgstr ""
+"The NC drill files, usually named Excellon files\n"
+"are files that can be found in different formats.\n"
+"Here we set the format used when the provided\n"
+"coordinates are not using period."
+
+#: flatcamGUI/PreferencesUI.py:3745
+msgid "Format"
+msgstr "Format"
+
+#: flatcamGUI/PreferencesUI.py:3747 flatcamGUI/PreferencesUI.py:3757
+msgid ""
+"Select the kind of coordinates format used.\n"
+"Coordinates can be saved with decimal point or without.\n"
+"When there is no decimal point, it is required to specify\n"
+"the number of digits for integer part and the number of decimals.\n"
+"Also it will have to be specified if LZ = leading zeros are kept\n"
+"or TZ = trailing zeros are kept."
+msgstr ""
+"Select the kind of coordinates format used.\n"
+"Coordinates can be saved with decimal point or without.\n"
+"When there is no decimal point, it is required to specify\n"
+"the number of digits for integer part and the number of decimals.\n"
+"Also it will have to be specified if LZ = leading zeros are kept\n"
+"or TZ = trailing zeros are kept."
+
+#: flatcamGUI/PreferencesUI.py:3754
+msgid "Decimal"
+msgstr "Decimal"
+
+#: flatcamGUI/PreferencesUI.py:3755
+msgid "No-Decimal"
+msgstr "No-Decimal"
+
+#: flatcamGUI/PreferencesUI.py:3771
+msgid ""
+"This sets the type of Excellon zeros.\n"
+"If LZ then Leading Zeros are kept and\n"
+"Trailing Zeros are removed.\n"
+"If TZ is checked then Trailing Zeros are kept\n"
+"and Leading Zeros are removed."
+msgstr ""
+"This sets the type of Excellon zeros.\n"
+"If LZ then Leading Zeros are kept and\n"
+"Trailing Zeros are removed.\n"
+"If TZ is checked then Trailing Zeros are kept\n"
+"and Leading Zeros are removed."
+
+#: flatcamGUI/PreferencesUI.py:3781
+msgid ""
+"This sets the default type of Excellon zeros.\n"
+"If LZ then Leading Zeros are kept and\n"
+"Trailing Zeros are removed.\n"
+"If TZ is checked then Trailing Zeros are kept\n"
+"and Leading Zeros are removed."
+msgstr ""
+"This sets the default type of Excellon zeros.\n"
+"If LZ then Leading Zeros are kept and\n"
+"Trailing Zeros are removed.\n"
+"If TZ is checked then Trailing Zeros are kept\n"
+"and Leading Zeros are removed."
+
+#: flatcamGUI/PreferencesUI.py:3791
+msgid "Slot type"
+msgstr "Slot type"
+
+#: flatcamGUI/PreferencesUI.py:3794 flatcamGUI/PreferencesUI.py:3804
+msgid ""
+"This sets how the slots will be exported.\n"
+"If ROUTED then the slots will be routed\n"
+"using M15/M16 commands.\n"
+"If DRILLED(G85) the slots will be exported\n"
+"using the Drilled slot command (G85)."
+msgstr ""
+"This sets how the slots will be exported.\n"
+"If ROUTED then the slots will be routed\n"
+"using M15/M16 commands.\n"
+"If DRILLED(G85) the slots will be exported\n"
+"using the Drilled slot command (G85)."
+
+#: flatcamGUI/PreferencesUI.py:3801
+msgid "Routed"
+msgstr "Routed"
+
+#: flatcamGUI/PreferencesUI.py:3802
+msgid "Drilled(G85)"
+msgstr "Drilled(G85)"
+
+#: flatcamGUI/PreferencesUI.py:3835
+msgid "A list of Excellon Editor parameters."
+msgstr "A list of Excellon Editor parameters."
+
+#: flatcamGUI/PreferencesUI.py:3845
+msgid ""
+"Set the number of selected Excellon geometry\n"
+"items above which the utility geometry\n"
+"becomes just a selection rectangle.\n"
+"Increases the performance when moving a\n"
+"large number of geometric elements."
+msgstr ""
+"Set the number of selected Excellon geometry\n"
+"items above which the utility geometry\n"
+"becomes just a selection rectangle.\n"
+"Increases the performance when moving a\n"
+"large number of geometric elements."
+
+#: flatcamGUI/PreferencesUI.py:3858 flatcamGUI/PreferencesUI.py:5396
+#: flatcamGUI/PreferencesUI.py:5962
+msgid "New Dia"
+msgstr "New Dia"
+
+#: flatcamGUI/PreferencesUI.py:3883
+msgid "Linear Drill Array"
+msgstr "Linear Drill Array"
+
+#: flatcamGUI/PreferencesUI.py:3929
+msgid "Circular Drill Array"
+msgstr "Circular Drill Array"
+
+#: flatcamGUI/PreferencesUI.py:3999
+msgid ""
+"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."
+msgstr ""
+"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."
+
+#: flatcamGUI/PreferencesUI.py:4018
+msgid "Linear Slot Array"
+msgstr "Linear Slot Array"
+
+#: flatcamGUI/PreferencesUI.py:4079
+msgid "Circular Slot Array"
+msgstr "Circular Slot Array"
+
+#: flatcamGUI/PreferencesUI.py:4117
+msgid "Geometry General"
+msgstr "Geometry General"
+
+#: flatcamGUI/PreferencesUI.py:4139
+msgid ""
+"The number of circle steps for <b>Geometry</b> \n"
+"circle and arc shapes linear approximation."
+msgstr ""
+"The number of circle steps for <b>Geometry</b> \n"
+"circle and arc shapes linear approximation."
+
+#: flatcamGUI/PreferencesUI.py:4153 flatcamGUI/PreferencesUI.py:5320
+#: flatcamGUI/PreferencesUI.py:5887 flatcamGUI/PreferencesUI.py:6953
+msgid "Tools Dia"
+msgstr "Tools Dia"
+
+#: flatcamGUI/PreferencesUI.py:4170
+msgid "Geometry Object Color"
+msgstr "Geometry Object Color"
+
+#: flatcamGUI/PreferencesUI.py:4221
+msgid "Geometry Options"
+msgstr "Geometry Options"
+
+#: flatcamGUI/PreferencesUI.py:4229
+msgid ""
+"Create a CNC Job object\n"
+"tracing the contours of this\n"
+"Geometry object."
+msgstr ""
+"Create a CNC Job object\n"
+"tracing the contours of this\n"
+"Geometry object."
+
+#: flatcamGUI/PreferencesUI.py:4273
+msgid "Depth/Pass"
+msgstr "Depth/Pass"
+
+#: flatcamGUI/PreferencesUI.py:4275
+msgid ""
+"The depth to cut on each pass,\n"
+"when multidepth is enabled.\n"
+"It has positive value although\n"
+"it is a fraction from the depth\n"
+"which has negative value."
+msgstr ""
+"The depth to cut on each pass,\n"
+"when multidepth is enabled.\n"
+"It has positive value although\n"
+"it is a fraction from the depth\n"
+"which has negative value."
+
+#: flatcamGUI/PreferencesUI.py:4466
+msgid "Geometry Adv. Options"
+msgstr "Geometry Adv. Options"
+
+#: flatcamGUI/PreferencesUI.py:4474
+msgid ""
+"A list of Geometry advanced parameters.\n"
+"Those parameters are available only for\n"
+"Advanced App. Level."
+msgstr ""
+"A list of Geometry advanced parameters.\n"
+"Those parameters are available only for\n"
+"Advanced App. Level."
+
+#: flatcamGUI/PreferencesUI.py:4484 flatcamGUI/PreferencesUI.py:7045
+#: flatcamGUI/PreferencesUI.py:8092 flatcamTools/ToolCalibration.py:125
+#: flatcamTools/ToolSolderPaste.py:240
+msgid "Toolchange X-Y"
+msgstr "Toolchange X-Y"
+
+#: flatcamGUI/PreferencesUI.py:4495
+msgid ""
+"Height of the tool just after starting the work.\n"
+"Delete the value if you don't need this feature."
+msgstr ""
+"Height of the tool just after starting the work.\n"
+"Delete the value if you don't need this feature."
+
+#: flatcamGUI/PreferencesUI.py:4597
+msgid "Segment X size"
+msgstr "Segment X size"
+
+#: flatcamGUI/PreferencesUI.py:4599
+msgid ""
+"The size of the trace segment on the X axis.\n"
+"Useful for auto-leveling.\n"
+"A value of 0 means no segmentation on the X axis."
+msgstr ""
+"The size of the trace segment on the X axis.\n"
+"Useful for auto-leveling.\n"
+"A value of 0 means no segmentation on the X axis."
+
+#: flatcamGUI/PreferencesUI.py:4613
+msgid "Segment Y size"
+msgstr "Segment Y size"
+
+#: flatcamGUI/PreferencesUI.py:4615
+msgid ""
+"The size of the trace segment on the Y axis.\n"
+"Useful for auto-leveling.\n"
+"A value of 0 means no segmentation on the Y axis."
+msgstr ""
+"The size of the trace segment on the Y axis.\n"
+"Useful for auto-leveling.\n"
+"A value of 0 means no segmentation on the Y axis."
+
+#: flatcamGUI/PreferencesUI.py:4642
+msgid "A list of Geometry Editor parameters."
+msgstr "A list of Geometry Editor parameters."
+
+#: flatcamGUI/PreferencesUI.py:4652 flatcamGUI/PreferencesUI.py:7617
+msgid ""
+"Set the number of selected geometry\n"
+"items above which the utility geometry\n"
+"becomes just a selection rectangle.\n"
+"Increases the performance when moving a\n"
+"large number of geometric elements."
+msgstr ""
+"Set the number of selected geometry\n"
+"items above which the utility geometry\n"
+"becomes just a selection rectangle.\n"
+"Increases the performance when moving a\n"
+"large number of geometric elements."
+
+#: flatcamGUI/PreferencesUI.py:4684
+msgid "CNC Job General"
+msgstr "CNC Job General"
+
+#: flatcamGUI/PreferencesUI.py:4737
+msgid ""
+"The number of circle steps for <b>GCode</b> \n"
+"circle and arc shapes linear approximation."
+msgstr ""
+"The number of circle steps for <b>GCode</b> \n"
+"circle and arc shapes linear approximation."
+
+#: flatcamGUI/PreferencesUI.py:4746
+msgid "Travel dia"
+msgstr "Travel dia"
+
+#: flatcamGUI/PreferencesUI.py:4748
+msgid ""
+"The width of the travel lines to be\n"
+"rendered in the plot."
+msgstr ""
+"The width of the travel lines to be\n"
+"rendered in the plot."
+
+#: flatcamGUI/PreferencesUI.py:4761
+msgid "G-code Decimals"
+msgstr "G-code Decimals"
+
+#: flatcamGUI/PreferencesUI.py:4764 flatcamTools/ToolFiducials.py:74
+msgid "Coordinates"
+msgstr "Coordinates"
+
+#: flatcamGUI/PreferencesUI.py:4766
+msgid ""
+"The number of decimals to be used for \n"
+"the X, Y, Z coordinates in CNC code (GCODE, etc.)"
+msgstr ""
+"The number of decimals to be used for \n"
+"the X, Y, Z coordinates in CNC code (GCODE, etc.)"
+
+#: flatcamGUI/PreferencesUI.py:4777 flatcamTools/ToolProperties.py:519
+msgid "Feedrate"
+msgstr "Feedrate"
+
+#: flatcamGUI/PreferencesUI.py:4779
+msgid ""
+"The number of decimals to be used for \n"
+"the Feedrate parameter in CNC code (GCODE, etc.)"
+msgstr ""
+"The number of decimals to be used for \n"
+"the Feedrate parameter in CNC code (GCODE, etc.)"
+
+#: flatcamGUI/PreferencesUI.py:4790
+msgid "Coordinates type"
+msgstr "Coordinates type"
+
+#: flatcamGUI/PreferencesUI.py:4792
+msgid ""
+"The type of coordinates to be used in Gcode.\n"
+"Can be:\n"
+"- Absolute G90 -> the reference is the origin x=0, y=0\n"
+"- Incremental G91 -> the reference is the previous position"
+msgstr ""
+"The type of coordinates to be used in Gcode.\n"
+"Can be:\n"
+"- Absolute G90 -> the reference is the origin x=0, y=0\n"
+"- Incremental G91 -> the reference is the previous position"
+
+#: flatcamGUI/PreferencesUI.py:4798
+msgid "Absolute G90"
+msgstr "Absolute G90"
+
+#: flatcamGUI/PreferencesUI.py:4799
+msgid "Incremental G91"
+msgstr "Incremental G91"
+
+#: flatcamGUI/PreferencesUI.py:4809
+msgid "Force Windows style line-ending"
+msgstr "Force Windows style line-ending"
+
+#: flatcamGUI/PreferencesUI.py:4811
+msgid ""
+"When checked will force a Windows style line-ending\n"
+"(\\r\\n) on non-Windows OS's."
+msgstr ""
+"When checked will force a Windows style line-ending\n"
+"(\\r\\n) on non-Windows OS's."
+
+#: flatcamGUI/PreferencesUI.py:4823
+msgid "Travel Line Color"
+msgstr "Travel Line Color"
+
+#: flatcamGUI/PreferencesUI.py:4829
+msgid "Set the travel line color for plotted objects."
+msgstr "Set the travel line color for plotted objects."
+
+#: flatcamGUI/PreferencesUI.py:4889
+msgid "CNCJob Object Color"
+msgstr "CNCJob Object Color"
+
+#: flatcamGUI/PreferencesUI.py:4895
+msgid "Set the color for plotted objects."
+msgstr "Set the color for plotted objects."
+
+#: flatcamGUI/PreferencesUI.py:5055
+msgid "CNC Job Options"
+msgstr "CNC Job Options"
+
+#: flatcamGUI/PreferencesUI.py:5059
+msgid "Export G-Code"
+msgstr "Export G-Code"
+
+#: flatcamGUI/PreferencesUI.py:5075
+msgid "Prepend to G-Code"
+msgstr "Prepend to G-Code"
+
+#: flatcamGUI/PreferencesUI.py:5084
+msgid ""
+"Type here any G-Code commands you would like to add at the beginning of the "
+"G-Code file."
+msgstr ""
+"Type here any G-Code commands you would like to add at the beginning of the "
+"G-Code file."
+
+#: flatcamGUI/PreferencesUI.py:5091
+msgid "Append to G-Code"
+msgstr "Append to G-Code"
+
+#: flatcamGUI/PreferencesUI.py:5101
+msgid ""
+"Type here any G-Code commands you would like to append to the generated "
+"file.\n"
+"I.e.: M2 (End of program)"
+msgstr ""
+"Type here any G-Code commands you would like to append to the generated "
+"file.\n"
+"I.e.: M2 (End of program)"
+
+#: flatcamGUI/PreferencesUI.py:5117
+msgid "CNC Job Adv. Options"
+msgstr "CNC Job Adv. Options"
+
+#: flatcamGUI/PreferencesUI.py:5154
+msgid ""
+"Type here any G-Code commands you would like to be executed when Toolchange "
+"event is encountered.\n"
+"This will constitute a Custom Toolchange GCode, or a Toolchange Macro.\n"
+"The FlatCAM variables are surrounded by '%' symbol.\n"
+"WARNING: it can be used only with a preprocessor file that has "
+"'toolchange_custom' in it's name."
+msgstr ""
+"Type here any G-Code commands you would like to be executed when Toolchange "
+"event is encountered.\n"
+"This will constitute a Custom Toolchange GCode, or a Toolchange Macro.\n"
+"The FlatCAM variables are surrounded by '%' symbol.\n"
+"WARNING: it can be used only with a preprocessor file that has "
+"'toolchange_custom' in it's name."
+
+#: flatcamGUI/PreferencesUI.py:5209
+msgid "Z depth for the cut"
+msgstr "Z depth for the cut"
+
+#: flatcamGUI/PreferencesUI.py:5210
+msgid "Z height for travel"
+msgstr "Z height for travel"
+
+#: flatcamGUI/PreferencesUI.py:5216
+msgid "dwelltime = time to dwell to allow the spindle to reach it's set RPM"
+msgstr "dwelltime = time to dwell to allow the spindle to reach it's set RPM"
+
+#: flatcamGUI/PreferencesUI.py:5235
+msgid "Annotation Size"
+msgstr "Annotation Size"
+
+#: flatcamGUI/PreferencesUI.py:5237
+msgid "The font size of the annotation text. In pixels."
+msgstr "The font size of the annotation text. In pixels."
+
+#: flatcamGUI/PreferencesUI.py:5247
+msgid "Annotation Color"
+msgstr "Annotation Color"
+
+#: flatcamGUI/PreferencesUI.py:5249
+msgid "Set the font color for the annotation texts."
+msgstr "Set the font color for the annotation texts."
+
+#: flatcamGUI/PreferencesUI.py:5306
+msgid "NCC Tool Options"
+msgstr "NCC Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:5328 flatcamGUI/PreferencesUI.py:5896
+msgid "Comma separated values"
+msgstr "Comma separated values"
+
+#: flatcamGUI/PreferencesUI.py:5334 flatcamGUI/PreferencesUI.py:5342
+#: flatcamGUI/PreferencesUI.py:5903 flatcamTools/ToolNCC.py:215
+#: flatcamTools/ToolNCC.py:223 flatcamTools/ToolPaint.py:198
+#: flatcamTools/ToolPaint.py:206
+msgid ""
+"Default tool type:\n"
+"- 'V-shape'\n"
+"- Circular"
+msgstr ""
+"Default tool type:\n"
+"- 'V-shape'\n"
+"- Circular"
+
+#: flatcamGUI/PreferencesUI.py:5339 flatcamGUI/PreferencesUI.py:5908
+#: flatcamTools/ToolNCC.py:220 flatcamTools/ToolPaint.py:203
+msgid "V-shape"
+msgstr "V-shape"
+
+#: flatcamGUI/PreferencesUI.py:5379 flatcamGUI/PreferencesUI.py:5388
+#: flatcamGUI/PreferencesUI.py:5946 flatcamGUI/PreferencesUI.py:5955
+#: flatcamTools/ToolNCC.py:262 flatcamTools/ToolNCC.py:271
+#: flatcamTools/ToolPaint.py:245 flatcamTools/ToolPaint.py:254
+msgid ""
+"Depth of cut into material. Negative value.\n"
+"In FlatCAM units."
+msgstr ""
+"Depth of cut into material. Negative value.\n"
+"In FlatCAM units."
+
+#: flatcamGUI/PreferencesUI.py:5398 flatcamGUI/PreferencesUI.py:5964
+#: flatcamTools/ToolNCC.py:280 flatcamTools/ToolPaint.py:263
+msgid ""
+"Diameter for the new tool to add in the Tool Table.\n"
+"If the tool is V-shape type then this value is automatically\n"
+"calculated from the other parameters."
+msgstr ""
+"Diameter for the new tool to add in the Tool Table.\n"
+"If the tool is V-shape type then this value is automatically\n"
+"calculated from the other parameters."
+
+#: flatcamGUI/PreferencesUI.py:5435 flatcamGUI/PreferencesUI.py:5981
+#: flatcamTools/ToolNCC.py:174 flatcamTools/ToolPaint.py:158
+msgid "Tool order"
+msgstr "Tool order"
+
+#: flatcamGUI/PreferencesUI.py:5436 flatcamGUI/PreferencesUI.py:5446
+#: flatcamGUI/PreferencesUI.py:5982 flatcamTools/ToolNCC.py:175
+#: flatcamTools/ToolNCC.py:185 flatcamTools/ToolPaint.py:159
+#: flatcamTools/ToolPaint.py:169
+msgid ""
+"This set the way that the tools in the tools table are used.\n"
+"'No' --> means that the used order is the one in the tool table\n"
+"'Forward' --> means that the tools will be ordered from small to big\n"
+"'Reverse' --> menas that the tools will ordered from big to small\n"
+"\n"
+"WARNING: using rest machining will automatically set the order\n"
+"in reverse and disable this control."
+msgstr ""
+"This set the way that the tools in the tools table are used.\n"
+"'No' --> means that the used order is the one in the tool table\n"
+"'Forward' --> means that the tools will be ordered from small to big\n"
+"'Reverse' --> menas that the tools will ordered from big to small\n"
+"\n"
+"WARNING: using rest machining will automatically set the order\n"
+"in reverse and disable this control."
+
+#: flatcamGUI/PreferencesUI.py:5444 flatcamGUI/PreferencesUI.py:5990
+#: flatcamTools/ToolNCC.py:183 flatcamTools/ToolPaint.py:167
+msgid "Forward"
+msgstr "Forward"
+
+#: flatcamGUI/PreferencesUI.py:5445 flatcamGUI/PreferencesUI.py:5991
+#: flatcamTools/ToolNCC.py:184 flatcamTools/ToolPaint.py:168
+msgid "Reverse"
+msgstr "Reverse"
+
+#: flatcamGUI/PreferencesUI.py:5545
+msgid "Offset value"
+msgstr "Offset value"
+
+#: flatcamGUI/PreferencesUI.py:5547
+msgid ""
+"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."
+msgstr ""
+"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."
+
+#: flatcamGUI/PreferencesUI.py:5567 flatcamGUI/PreferencesUI.py:6083
+#: flatcamGUI/PreferencesUI.py:6084 flatcamTools/ToolNCC.py:512
+#: flatcamTools/ToolPaint.py:442
+msgid "Rest Machining"
+msgstr "Rest Machining"
+
+#: flatcamGUI/PreferencesUI.py:5569 flatcamTools/ToolNCC.py:516
+msgid ""
+"If checked, use 'rest machining'.\n"
+"Basically it will clear 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"
+"If not checked, use the standard algorithm."
+msgstr ""
+"If checked, use 'rest machining'.\n"
+"Basically it will clear 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"
+"If not checked, use the standard algorithm."
+
+#: flatcamGUI/PreferencesUI.py:5588 flatcamGUI/PreferencesUI.py:6119
+#: flatcamGUI/PreferencesUI.py:7696 flatcamTools/ToolCopperThieving.py:126
+#: flatcamTools/ToolNCC.py:535 flatcamTools/ToolNCC.py:1311
+#: flatcamTools/ToolNCC.py:1642 flatcamTools/ToolNCC.py:1930
+#: flatcamTools/ToolNCC.py:1985 flatcamTools/ToolPaint.py:486
+#: flatcamTools/ToolPaint.py:946 flatcamTools/ToolPaint.py:1447
+msgid "Area Selection"
+msgstr "Area Selection"
+
+#: flatcamGUI/PreferencesUI.py:5588 flatcamGUI/PreferencesUI.py:6119
+#: flatcamGUI/PreferencesUI.py:7697 flatcamTools/ToolCopperThieving.py:127
+#: flatcamTools/ToolDblSided.py:216 flatcamTools/ToolNCC.py:535
+#: flatcamTools/ToolNCC.py:1658 flatcamTools/ToolNCC.py:1936
+#: flatcamTools/ToolNCC.py:1993 flatcamTools/ToolNCC.py:2301
+#: flatcamTools/ToolNCC.py:2581 flatcamTools/ToolNCC.py:3007
+#: flatcamTools/ToolPaint.py:486 flatcamTools/ToolPaint.py:931
+#: flatcamTools/ToolPaint.py:1463 tclCommands/TclCommandCopperClear.py:192
+#: tclCommands/TclCommandPaint.py:166
+msgid "Reference Object"
+msgstr "Reference Object"
+
+#: flatcamGUI/PreferencesUI.py:5592 flatcamTools/ToolNCC.py:541
+msgid ""
+"Selection of area to be processed.\n"
+"- 'Itself' - the processing extent is based on the object that is "
+"processed.\n"
+" - 'Area Selection' - left mouse click to start selection of the area to be "
+"processed.\n"
+"- 'Reference Object' - will process the area specified by another object."
+msgstr ""
+"Selection of area to be processed.\n"
+"- 'Itself' - the processing extent is based on the object that is "
+"processed.\n"
+" - 'Area Selection' - left mouse click to start selection of the area to be "
+"processed.\n"
+"- 'Reference Object' - will process the area specified by another object."
+
+#: flatcamGUI/PreferencesUI.py:5601 flatcamGUI/PreferencesUI.py:6125
+#: flatcamTools/ToolNCC.py:578 flatcamTools/ToolPaint.py:522
+msgid "Shape"
+msgstr "Shape"
+
+#: flatcamGUI/PreferencesUI.py:5603 flatcamGUI/PreferencesUI.py:6127
+#: flatcamTools/ToolNCC.py:580 flatcamTools/ToolPaint.py:524
+msgid "The kind of selection shape used for area selection."
+msgstr "The kind of selection shape used for area selection."
+
+#: flatcamGUI/PreferencesUI.py:5618 flatcamGUI/PreferencesUI.py:6142
+msgid "Normal"
+msgstr "Normal"
+
+#: flatcamGUI/PreferencesUI.py:5619 flatcamGUI/PreferencesUI.py:6143
+msgid "Progressive"
+msgstr "Progressive"
+
+#: flatcamGUI/PreferencesUI.py:5620
+msgid "NCC Plotting"
+msgstr "NCC Plotting"
+
+#: flatcamGUI/PreferencesUI.py:5622
+msgid ""
+"- 'Normal' -  normal plotting, done at the end of the NCC job\n"
+"- 'Progressive' - after each shape is generated it will be plotted."
+msgstr ""
+"- 'Normal' -  normal plotting, done at the end of the NCC job\n"
+"- 'Progressive' - after each shape is generated it will be plotted."
+
+#: flatcamGUI/PreferencesUI.py:5636
+msgid "Cutout Tool Options"
+msgstr "Cutout Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:5651 flatcamTools/ToolCalculators.py:123
+#: flatcamTools/ToolCutOut.py:129
+msgid "Tool Diameter"
+msgstr "Tool Diameter"
+
+#: flatcamGUI/PreferencesUI.py:5653 flatcamTools/ToolCutOut.py:131
+msgid ""
+"Diameter of the tool used to cutout\n"
+"the PCB shape out of the surrounding material."
+msgstr ""
+"Diameter of the tool used to cutout\n"
+"the PCB shape out of the surrounding material."
+
+#: flatcamGUI/PreferencesUI.py:5708
+msgid "Object kind"
+msgstr "Object kind"
+
+#: flatcamGUI/PreferencesUI.py:5710 flatcamTools/ToolCutOut.py:77
+msgid ""
+"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"
+"out of many individual PCB outlines."
+msgstr ""
+"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"
+"out of many individual PCB outlines."
+
+#: flatcamGUI/PreferencesUI.py:5717 flatcamTools/ToolCutOut.py:83
+msgid "Single"
+msgstr "Single"
+
+#: flatcamGUI/PreferencesUI.py:5718 flatcamTools/ToolCutOut.py:84
+msgid "Panel"
+msgstr "Panel"
+
+#: flatcamGUI/PreferencesUI.py:5725 flatcamTools/ToolCutOut.py:192
+msgid ""
+"Margin over bounds. A positive value here\n"
+"will make the cutout of the PCB further from\n"
+"the actual PCB border"
+msgstr ""
+"Margin over bounds. A positive value here\n"
+"will make the cutout of the PCB further from\n"
+"the actual PCB border"
+
+#: flatcamGUI/PreferencesUI.py:5738 flatcamTools/ToolCutOut.py:203
+msgid "Gap size"
+msgstr "Gap size"
+
+#: flatcamGUI/PreferencesUI.py:5740 flatcamTools/ToolCutOut.py:205
+msgid ""
+"The size of the bridge gaps in the cutout\n"
+"used to keep the board connected to\n"
+"the surrounding material (the one \n"
+"from which the PCB is cutout)."
+msgstr ""
+"The size of the bridge gaps in the cutout\n"
+"used to keep the board connected to\n"
+"the surrounding material (the one \n"
+"from which the PCB is cutout)."
+
+#: flatcamGUI/PreferencesUI.py:5754 flatcamTools/ToolCutOut.py:249
+msgid "Gaps"
+msgstr "Gaps"
+
+#: flatcamGUI/PreferencesUI.py:5756
+msgid ""
+"Number of gaps used for the cutout.\n"
+"There can be maximum 8 bridges/gaps.\n"
+"The choices are:\n"
+"- None  - no gaps\n"
+"- lr    - left + right\n"
+"- tb    - top + bottom\n"
+"- 4     - left + right +top + bottom\n"
+"- 2lr   - 2*left + 2*right\n"
+"- 2tb  - 2*top + 2*bottom\n"
+"- 8     - 2*left + 2*right +2*top + 2*bottom"
+msgstr ""
+"Number of gaps used for the cutout.\n"
+"There can be maximum 8 bridges/gaps.\n"
+"The choices are:\n"
+"- None  - no gaps\n"
+"- lr    - left + right\n"
+"- tb    - top + bottom\n"
+"- 4     - left + right +top + bottom\n"
+"- 2lr   - 2*left + 2*right\n"
+"- 2tb  - 2*top + 2*bottom\n"
+"- 8     - 2*left + 2*right +2*top + 2*bottom"
+
+#: flatcamGUI/PreferencesUI.py:5778 flatcamTools/ToolCutOut.py:222
+msgid "Convex Shape"
+msgstr "Convex Shape"
+
+#: flatcamGUI/PreferencesUI.py:5780 flatcamTools/ToolCutOut.py:225
+msgid ""
+"Create a convex shape surrounding the entire PCB.\n"
+"Used only if the source object type is Gerber."
+msgstr ""
+"Create a convex shape surrounding the entire PCB.\n"
+"Used only if the source object type is Gerber."
+
+#: flatcamGUI/PreferencesUI.py:5793
+msgid "2Sided Tool Options"
+msgstr "2Sided Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:5799
+msgid ""
+"A tool to help in creating a double sided\n"
+"PCB using alignment holes."
+msgstr ""
+"A tool to help in creating a double sided\n"
+"PCB using alignment holes."
+
+#: flatcamGUI/PreferencesUI.py:5813
+msgid "Drill dia"
+msgstr "Drill dia"
+
+#: flatcamGUI/PreferencesUI.py:5815 flatcamTools/ToolDblSided.py:363
+#: flatcamTools/ToolDblSided.py:368
+msgid "Diameter of the drill for the alignment holes."
+msgstr "Diameter of the drill for the alignment holes."
+
+#: flatcamGUI/PreferencesUI.py:5822 flatcamTools/ToolDblSided.py:377
+msgid "Align Axis"
+msgstr "Align Axis"
+
+#: flatcamGUI/PreferencesUI.py:5824 flatcamGUI/PreferencesUI.py:5837
+#: flatcamTools/ToolDblSided.py:165 flatcamTools/ToolDblSided.py:379
+msgid "Mirror vertically (X) or horizontally (Y)."
+msgstr "Mirror vertically (X) or horizontally (Y)."
+
+#: flatcamGUI/PreferencesUI.py:5835
+msgid "Mirror Axis:"
+msgstr "Mirror Axis:"
+
+#: flatcamGUI/PreferencesUI.py:5846 flatcamTools/ToolDblSided.py:181
+msgid "Point"
+msgstr "Point"
+
+#: flatcamGUI/PreferencesUI.py:5847 flatcamTools/ToolDblSided.py:182
+msgid "Box"
+msgstr "Box"
+
+#: flatcamGUI/PreferencesUI.py:5848
+msgid "Axis Ref"
+msgstr "Axis Ref"
+
+#: flatcamGUI/PreferencesUI.py:5850
+msgid ""
+"The axis should pass through a <b>point</b> or cut\n"
+" a specified <b>box</b> (in a FlatCAM object) through \n"
+"the center."
+msgstr ""
+"The axis should pass through a <b>point</b> or cut\n"
+" a specified <b>box</b> (in a FlatCAM object) through \n"
+"the center."
+
+#: flatcamGUI/PreferencesUI.py:5866
+msgid "Paint Tool Options"
+msgstr "Paint Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:5872
+msgid "<b>Parameters:</b>"
+msgstr "<b>Parameters:</b>"
+
+#: flatcamGUI/PreferencesUI.py:6086 flatcamTools/ToolPaint.py:445
+msgid ""
+"If checked, use 'rest machining'.\n"
+"Basically it will clear 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"
+"If not checked, use the standard algorithm."
+msgstr ""
+"If checked, use 'rest machining'.\n"
+"Basically it will clear 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"
+"If not checked, use the standard algorithm."
+
+#: flatcamGUI/PreferencesUI.py:6099 flatcamTools/ToolPaint.py:458
+msgid ""
+"Selection of area to be processed.\n"
+"- 'Polygon Selection' - left mouse click to add/remove polygons to be "
+"processed.\n"
+"- 'Area Selection' - left mouse click to start selection of the area to be "
+"processed.\n"
+"Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple "
+"areas.\n"
+"- 'All Polygons' - the process will start after click.\n"
+"- 'Reference Object' - will process the area specified by another object."
+msgstr ""
+"Selection of area to be processed.\n"
+"- 'Polygon Selection' - left mouse click to add/remove polygons to be "
+"processed.\n"
+"- 'Area Selection' - left mouse click to start selection of the area to be "
+"processed.\n"
+"Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple "
+"areas.\n"
+"- 'All Polygons' - the process will start after click.\n"
+"- 'Reference Object' - will process the area specified by another object."
+
+#: flatcamGUI/PreferencesUI.py:6119 flatcamTools/ToolPaint.py:486
+#: flatcamTools/ToolPaint.py:942 flatcamTools/ToolPaint.py:1427
+#: tclCommands/TclCommandPaint.py:164
+msgid "Polygon Selection"
+msgstr "Polygon Selection"
+
+#: flatcamGUI/PreferencesUI.py:6144
+msgid "Paint Plotting"
+msgstr "Paint Plotting"
+
+#: flatcamGUI/PreferencesUI.py:6146
+msgid ""
+"- 'Normal' -  normal plotting, done at the end of the Paint job\n"
+"- 'Progressive' - after each shape is generated it will be plotted."
+msgstr ""
+"- 'Normal' -  normal plotting, done at the end of the Paint job\n"
+"- 'Progressive' - after each shape is generated it will be plotted."
+
+#: flatcamGUI/PreferencesUI.py:6160
+msgid "Film Tool Options"
+msgstr "Film Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:6166
+msgid ""
+"Create a PCB film from a Gerber or Geometry\n"
+"FlatCAM object.\n"
+"The file is saved in SVG format."
+msgstr ""
+"Create a PCB film from a Gerber or Geometry\n"
+"FlatCAM object.\n"
+"The file is saved in SVG format."
+
+#: flatcamGUI/PreferencesUI.py:6177
+msgid "Film Type"
+msgstr "Film Type"
+
+#: flatcamGUI/PreferencesUI.py:6179 flatcamTools/ToolFilm.py:300
+msgid ""
+"Generate a Positive black film or a Negative film.\n"
+"Positive means that it will print the features\n"
+"with black on a white canvas.\n"
+"Negative means that it will print the features\n"
+"with white on a black canvas.\n"
+"The Film format is SVG."
+msgstr ""
+"Generate a Positive black film or a Negative film.\n"
+"Positive means that it will print the features\n"
+"with black on a white canvas.\n"
+"Negative means that it will print the features\n"
+"with white on a black canvas.\n"
+"The Film format is SVG."
+
+#: flatcamGUI/PreferencesUI.py:6190
+msgid "Film Color"
+msgstr "Film Color"
+
+#: flatcamGUI/PreferencesUI.py:6192
+msgid "Set the film color when positive film is selected."
+msgstr "Set the film color when positive film is selected."
+
+#: flatcamGUI/PreferencesUI.py:6215 flatcamTools/ToolFilm.py:316
+msgid "Border"
+msgstr "Border"
+
+#: flatcamGUI/PreferencesUI.py:6217 flatcamTools/ToolFilm.py:318
+msgid ""
+"Specify a border around the object.\n"
+"Only for negative film.\n"
+"It helps if we use as a Box Object the same \n"
+"object as in Film Object. It will create a thick\n"
+"black bar around the actual print allowing for a\n"
+"better delimitation of the outline features which are of\n"
+"white color like the rest and which may confound with the\n"
+"surroundings if not for this border."
+msgstr ""
+"Specify a border around the object.\n"
+"Only for negative film.\n"
+"It helps if we use as a Box Object the same \n"
+"object as in Film Object. It will create a thick\n"
+"black bar around the actual print allowing for a\n"
+"better delimitation of the outline features which are of\n"
+"white color like the rest and which may confound with the\n"
+"surroundings if not for this border."
+
+#: flatcamGUI/PreferencesUI.py:6234 flatcamTools/ToolFilm.py:283
+msgid "Scale Stroke"
+msgstr "Scale Stroke"
+
+#: flatcamGUI/PreferencesUI.py:6236 flatcamTools/ToolFilm.py:285
+msgid ""
+"Scale the line stroke thickness of each feature in the SVG file.\n"
+"It means that the line that envelope each SVG feature will be thicker or "
+"thinner,\n"
+"therefore the fine features may be more affected by this parameter."
+msgstr ""
+"Scale the line stroke thickness of each feature in the SVG file.\n"
+"It means that the line that envelope each SVG feature will be thicker or "
+"thinner,\n"
+"therefore the fine features may be more affected by this parameter."
+
+#: flatcamGUI/PreferencesUI.py:6243 flatcamTools/ToolFilm.py:141
+msgid "Film Adjustments"
+msgstr "Film Adjustments"
+
+#: flatcamGUI/PreferencesUI.py:6245 flatcamTools/ToolFilm.py:143
+msgid ""
+"Sometime the printers will distort the print shape, especially the Laser "
+"types.\n"
+"This section provide the tools to compensate for the print distortions."
+msgstr ""
+"Sometime the printers will distort the print shape, especially the Laser "
+"types.\n"
+"This section provide the tools to compensate for the print distortions."
+
+#: flatcamGUI/PreferencesUI.py:6252 flatcamTools/ToolFilm.py:150
+msgid "Scale Film geometry"
+msgstr "Scale Film geometry"
+
+#: flatcamGUI/PreferencesUI.py:6254 flatcamTools/ToolFilm.py:152
+msgid ""
+"A value greater than 1 will stretch the film\n"
+"while a value less than 1 will jolt it."
+msgstr ""
+"A value greater than 1 will stretch the film\n"
+"while a value less than 1 will jolt it."
+
+#: flatcamGUI/PreferencesUI.py:6264 flatcamGUI/PreferencesUI.py:6783
+#: flatcamTools/ToolFilm.py:162 flatcamTools/ToolTransform.py:148
+msgid "X factor"
+msgstr "X factor"
+
+#: flatcamGUI/PreferencesUI.py:6273 flatcamGUI/PreferencesUI.py:6796
+#: flatcamTools/ToolFilm.py:171 flatcamTools/ToolTransform.py:168
+msgid "Y factor"
+msgstr "Y factor"
+
+#: flatcamGUI/PreferencesUI.py:6283 flatcamTools/ToolFilm.py:189
+msgid "Skew Film geometry"
+msgstr "Skew Film geometry"
+
+#: flatcamGUI/PreferencesUI.py:6285 flatcamTools/ToolFilm.py:191
+msgid ""
+"Positive values will skew to the right\n"
+"while negative values will skew to the left."
+msgstr ""
+"Positive values will skew to the right\n"
+"while negative values will skew to the left."
+
+#: flatcamGUI/PreferencesUI.py:6295 flatcamGUI/PreferencesUI.py:6752
+#: flatcamTools/ToolFilm.py:201 flatcamTools/ToolTransform.py:97
+msgid "X angle"
+msgstr "X angle"
+
+#: flatcamGUI/PreferencesUI.py:6304 flatcamGUI/PreferencesUI.py:6766
+#: flatcamTools/ToolFilm.py:210 flatcamTools/ToolTransform.py:118
+msgid "Y angle"
+msgstr "Y angle"
+
+#: flatcamGUI/PreferencesUI.py:6315 flatcamTools/ToolFilm.py:221
+msgid ""
+"The reference point to be used as origin for the skew.\n"
+"It can be one of the four points of the geometry bounding box."
+msgstr ""
+"The reference point to be used as origin for the skew.\n"
+"It can be one of the four points of the geometry bounding box."
+
+#: flatcamGUI/PreferencesUI.py:6318 flatcamTools/ToolFiducials.py:87
+#: flatcamTools/ToolFilm.py:224
+msgid "Bottom Left"
+msgstr "Bottom Left"
+
+#: flatcamGUI/PreferencesUI.py:6319 flatcamTools/ToolFilm.py:225
+msgid "Top Left"
+msgstr "Top Left"
+
+#: flatcamGUI/PreferencesUI.py:6320 flatcamTools/ToolFilm.py:226
+msgid "Bottom Right"
+msgstr "Bottom Right"
+
+#: flatcamGUI/PreferencesUI.py:6321 flatcamTools/ToolFilm.py:227
+msgid "Top right"
+msgstr "Top right"
+
+#: flatcamGUI/PreferencesUI.py:6329 flatcamTools/ToolFilm.py:244
+msgid "Mirror Film geometry"
+msgstr "Mirror Film geometry"
+
+#: flatcamGUI/PreferencesUI.py:6331 flatcamTools/ToolFilm.py:246
+msgid "Mirror the film geometry on the selected axis or on both."
+msgstr "Mirror the film geometry on the selected axis or on both."
+
+#: flatcamGUI/PreferencesUI.py:6345 flatcamTools/ToolFilm.py:260
+msgid "Mirror axis"
+msgstr "Mirror axis"
+
+#: flatcamGUI/PreferencesUI.py:6355 flatcamTools/ToolFilm.py:405
+msgid "SVG"
+msgstr "SVG"
+
+#: flatcamGUI/PreferencesUI.py:6356 flatcamTools/ToolFilm.py:406
+msgid "PNG"
+msgstr "PNG"
+
+#: flatcamGUI/PreferencesUI.py:6357 flatcamTools/ToolFilm.py:407
+msgid "PDF"
+msgstr "PDF"
+
+#: flatcamGUI/PreferencesUI.py:6360 flatcamTools/ToolFilm.py:298
+#: flatcamTools/ToolFilm.py:410
+msgid "Film Type:"
+msgstr "Film Type:"
+
+#: flatcamGUI/PreferencesUI.py:6362 flatcamTools/ToolFilm.py:412
+msgid ""
+"The file type of the saved film. Can be:\n"
+"- 'SVG' -> open-source vectorial format\n"
+"- 'PNG' -> raster image\n"
+"- 'PDF' -> portable document format"
+msgstr ""
+"The file type of the saved film. Can be:\n"
+"- 'SVG' -> open-source vectorial format\n"
+"- 'PNG' -> raster image\n"
+"- 'PDF' -> portable document format"
+
+#: flatcamGUI/PreferencesUI.py:6371 flatcamTools/ToolFilm.py:421
+msgid "Page Orientation"
+msgstr "Page Orientation"
+
+#: flatcamGUI/PreferencesUI.py:6384 flatcamTools/ToolFilm.py:434
+msgid "Page Size"
+msgstr "Page Size"
+
+#: flatcamGUI/PreferencesUI.py:6385 flatcamTools/ToolFilm.py:435
+msgid "A selection of standard ISO 216 page sizes."
+msgstr "A selection of standard ISO 216 page sizes."
+
+#: flatcamGUI/PreferencesUI.py:6457
+msgid "Panelize Tool Options"
+msgstr "Panelize Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:6463
+msgid ""
+"Create an object that contains an array of (x, y) elements,\n"
+"each element is a copy of the source object spaced\n"
+"at a X distance, Y distance of each other."
+msgstr ""
+"Create an object that contains an array of (x, y) elements,\n"
+"each element is a copy of the source object spaced\n"
+"at a X distance, Y distance of each other."
+
+#: flatcamGUI/PreferencesUI.py:6480 flatcamTools/ToolPanelize.py:161
+msgid "Spacing cols"
+msgstr "Spacing cols"
+
+#: flatcamGUI/PreferencesUI.py:6482 flatcamTools/ToolPanelize.py:163
+msgid ""
+"Spacing between columns of the desired panel.\n"
+"In current units."
+msgstr ""
+"Spacing between columns of the desired panel.\n"
+"In current units."
+
+#: flatcamGUI/PreferencesUI.py:6494 flatcamTools/ToolPanelize.py:173
+msgid "Spacing rows"
+msgstr "Spacing rows"
+
+#: flatcamGUI/PreferencesUI.py:6496 flatcamTools/ToolPanelize.py:175
+msgid ""
+"Spacing between rows of the desired panel.\n"
+"In current units."
+msgstr ""
+"Spacing between rows of the desired panel.\n"
+"In current units."
+
+#: flatcamGUI/PreferencesUI.py:6507 flatcamTools/ToolPanelize.py:184
+msgid "Columns"
+msgstr "Columns"
+
+#: flatcamGUI/PreferencesUI.py:6509 flatcamTools/ToolPanelize.py:186
+msgid "Number of columns of the desired panel"
+msgstr "Number of columns of the desired panel"
+
+#: flatcamGUI/PreferencesUI.py:6519 flatcamTools/ToolPanelize.py:194
+msgid "Rows"
+msgstr "Rows"
+
+#: flatcamGUI/PreferencesUI.py:6521 flatcamTools/ToolPanelize.py:196
+msgid "Number of rows of the desired panel"
+msgstr "Number of rows of the desired panel"
+
+#: flatcamGUI/PreferencesUI.py:6528 flatcamTools/ToolPanelize.py:203
+msgid "Geo"
+msgstr "Geo"
+
+#: flatcamGUI/PreferencesUI.py:6529 flatcamTools/ToolPanelize.py:204
+msgid "Panel Type"
+msgstr "Panel Type"
+
+#: flatcamGUI/PreferencesUI.py:6531
+msgid ""
+"Choose the type of object for the panel object:\n"
+"- Gerber\n"
+"- Geometry"
+msgstr ""
+"Choose the type of object for the panel object:\n"
+"- Gerber\n"
+"- Geometry"
+
+#: flatcamGUI/PreferencesUI.py:6540
+msgid "Constrain within"
+msgstr "Constrain within"
+
+#: flatcamGUI/PreferencesUI.py:6542 flatcamTools/ToolPanelize.py:216
+msgid ""
+"Area define by DX and DY within to constrain the panel.\n"
+"DX and DY values are in current units.\n"
+"Regardless of how many columns and rows are desired,\n"
+"the final panel will have as many columns and rows as\n"
+"they fit completely within selected area."
+msgstr ""
+"Area define by DX and DY within to constrain the panel.\n"
+"DX and DY values are in current units.\n"
+"Regardless of how many columns and rows are desired,\n"
+"the final panel will have as many columns and rows as\n"
+"they fit completely within selected area."
+
+#: flatcamGUI/PreferencesUI.py:6555 flatcamTools/ToolPanelize.py:228
+msgid "Width (DX)"
+msgstr "Width (DX)"
+
+#: flatcamGUI/PreferencesUI.py:6557 flatcamTools/ToolPanelize.py:230
+msgid ""
+"The width (DX) within which the panel must fit.\n"
+"In current units."
+msgstr ""
+"The width (DX) within which the panel must fit.\n"
+"In current units."
+
+#: flatcamGUI/PreferencesUI.py:6568 flatcamTools/ToolPanelize.py:239
+msgid "Height (DY)"
+msgstr "Height (DY)"
+
+#: flatcamGUI/PreferencesUI.py:6570 flatcamTools/ToolPanelize.py:241
+msgid ""
+"The height (DY)within which the panel must fit.\n"
+"In current units."
+msgstr ""
+"The height (DY)within which the panel must fit.\n"
+"In current units."
+
+#: flatcamGUI/PreferencesUI.py:6584
+msgid "Calculators Tool Options"
+msgstr "Calculators Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:6588 flatcamTools/ToolCalculators.py:25
+msgid "V-Shape Tool Calculator"
+msgstr "V-Shape Tool Calculator"
+
+#: flatcamGUI/PreferencesUI.py:6590
+msgid ""
+"Calculate the tool diameter for a given V-shape tool,\n"
+"having the tip diameter, tip angle and\n"
+"depth-of-cut as parameters."
+msgstr ""
+"Calculate the tool diameter for a given V-shape tool,\n"
+"having the tip diameter, tip angle and\n"
+"depth-of-cut as parameters."
+
+#: flatcamGUI/PreferencesUI.py:6607 flatcamTools/ToolCalculators.py:94
+msgid "Tip Diameter"
+msgstr "Tip Diameter"
+
+#: flatcamGUI/PreferencesUI.py:6609 flatcamTools/ToolCalculators.py:102
+msgid ""
+"This is the tool tip diameter.\n"
+"It is specified by manufacturer."
+msgstr ""
+"This is the tool tip diameter.\n"
+"It is specified by manufacturer."
+
+#: flatcamGUI/PreferencesUI.py:6621 flatcamTools/ToolCalculators.py:105
+msgid "Tip Angle"
+msgstr "Tip Angle"
+
+#: flatcamGUI/PreferencesUI.py:6623
+msgid ""
+"This is the angle on the tip of the tool.\n"
+"It is specified by manufacturer."
+msgstr ""
+"This is the angle on the tip of the tool.\n"
+"It is specified by manufacturer."
+
+#: flatcamGUI/PreferencesUI.py:6637
+msgid ""
+"This is depth to cut into material.\n"
+"In the CNCJob object it is the CutZ parameter."
+msgstr ""
+"This is depth to cut into material.\n"
+"In the CNCJob object it is the CutZ parameter."
+
+#: flatcamGUI/PreferencesUI.py:6644 flatcamTools/ToolCalculators.py:27
+msgid "ElectroPlating Calculator"
+msgstr "ElectroPlating Calculator"
+
+#: flatcamGUI/PreferencesUI.py:6646 flatcamTools/ToolCalculators.py:158
+msgid ""
+"This calculator is useful for those who plate the via/pad/drill holes,\n"
+"using a method like grahite ink or calcium hypophosphite ink or palladium "
+"chloride."
+msgstr ""
+"This calculator is useful for those who plate the via/pad/drill holes,\n"
+"using a method like grahite ink or calcium hypophosphite ink or palladium "
+"chloride."
+
+#: flatcamGUI/PreferencesUI.py:6657 flatcamTools/ToolCalculators.py:167
+msgid "Board Length"
+msgstr "Board Length"
+
+#: flatcamGUI/PreferencesUI.py:6659 flatcamTools/ToolCalculators.py:173
+msgid "This is the board length. In centimeters."
+msgstr "This is the board length. In centimeters."
+
+#: flatcamGUI/PreferencesUI.py:6669 flatcamTools/ToolCalculators.py:175
+msgid "Board Width"
+msgstr "Board Width"
+
+#: flatcamGUI/PreferencesUI.py:6671 flatcamTools/ToolCalculators.py:181
+msgid "This is the board width.In centimeters."
+msgstr "This is the board width.In centimeters."
+
+#: flatcamGUI/PreferencesUI.py:6676 flatcamTools/ToolCalculators.py:183
+msgid "Current Density"
+msgstr "Current Density"
+
+#: flatcamGUI/PreferencesUI.py:6682 flatcamTools/ToolCalculators.py:190
+msgid ""
+"Current density to pass through the board. \n"
+"In Amps per Square Feet ASF."
+msgstr ""
+"Current density to pass through the board. \n"
+"In Amps per Square Feet ASF."
+
+#: flatcamGUI/PreferencesUI.py:6688 flatcamTools/ToolCalculators.py:193
+msgid "Copper Growth"
+msgstr "Copper Growth"
+
+#: flatcamGUI/PreferencesUI.py:6694 flatcamTools/ToolCalculators.py:200
+msgid ""
+"How thick the copper growth is intended to be.\n"
+"In microns."
+msgstr ""
+"How thick the copper growth is intended to be.\n"
+"In microns."
+
+#: flatcamGUI/PreferencesUI.py:6707
+msgid "Transform Tool Options"
+msgstr "Transform Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:6713
+msgid ""
+"Various transformations that can be applied\n"
+"on a FlatCAM object."
+msgstr ""
+"Various transformations that can be applied\n"
+"on a FlatCAM object."
+
+#: flatcamGUI/PreferencesUI.py:6744
+msgid "Skew"
+msgstr "Skew"
+
+#: flatcamGUI/PreferencesUI.py:6785 flatcamTools/ToolTransform.py:150
+msgid "Factor for scaling on X axis."
+msgstr "Factor for scaling on X axis."
+
+#: flatcamGUI/PreferencesUI.py:6798 flatcamTools/ToolTransform.py:170
+msgid "Factor for scaling on Y axis."
+msgstr "Factor for scaling on Y axis."
+
+#: flatcamGUI/PreferencesUI.py:6806 flatcamTools/ToolTransform.py:191
+msgid ""
+"Scale the selected object(s)\n"
+"using the Scale_X factor for both axis."
+msgstr ""
+"Scale the selected object(s)\n"
+"using the Scale_X factor for both axis."
+
+#: flatcamGUI/PreferencesUI.py:6814 flatcamTools/ToolTransform.py:198
+msgid ""
+"Scale the selected object(s)\n"
+"using the origin reference when checked,\n"
+"and the center of the biggest bounding box\n"
+"of the selected objects when unchecked."
+msgstr ""
+"Scale the selected object(s)\n"
+"using the origin reference when checked,\n"
+"and the center of the biggest bounding box\n"
+"of the selected objects when unchecked."
+
+#: flatcamGUI/PreferencesUI.py:6830 flatcamTools/ToolTransform.py:217
+msgid "X val"
+msgstr "X val"
+
+#: flatcamGUI/PreferencesUI.py:6832 flatcamTools/ToolTransform.py:219
+msgid "Distance to offset on X axis. In current units."
+msgstr "Distance to offset on X axis. In current units."
+
+#: flatcamGUI/PreferencesUI.py:6843 flatcamTools/ToolTransform.py:237
+msgid "Y val"
+msgstr "Y val"
+
+#: flatcamGUI/PreferencesUI.py:6845 flatcamTools/ToolTransform.py:239
+msgid "Distance to offset on Y axis. In current units."
+msgstr "Distance to offset on Y axis. In current units."
+
+#: flatcamGUI/PreferencesUI.py:6851 flatcamTools/ToolDblSided.py:67
+#: flatcamTools/ToolDblSided.py:95 flatcamTools/ToolDblSided.py:125
+msgid "Mirror"
+msgstr "Mirror"
+
+#: flatcamGUI/PreferencesUI.py:6855 flatcamTools/ToolTransform.py:283
+msgid "Mirror Reference"
+msgstr "Mirror Reference"
+
+#: flatcamGUI/PreferencesUI.py:6857 flatcamTools/ToolTransform.py:285
+msgid ""
+"Flip the selected object(s)\n"
+"around the point in Point Entry Field.\n"
+"\n"
+"The point coordinates can be captured by\n"
+"left click on canvas together with pressing\n"
+"SHIFT key. \n"
+"Then click Add button to insert coordinates.\n"
+"Or enter the coords in format (x, y) in the\n"
+"Point Entry field and click Flip on X(Y)"
+msgstr ""
+"Flip the selected object(s)\n"
+"around the point in Point Entry Field.\n"
+"\n"
+"The point coordinates can be captured by\n"
+"left click on canvas together with pressing\n"
+"SHIFT key. \n"
+"Then click Add button to insert coordinates.\n"
+"Or enter the coords in format (x, y) in the\n"
+"Point Entry field and click Flip on X(Y)"
+
+#: flatcamGUI/PreferencesUI.py:6868
+msgid "Mirror Reference point"
+msgstr "Mirror Reference point"
+
+#: flatcamGUI/PreferencesUI.py:6870
+msgid ""
+"Coordinates in format (x, y) used as reference for mirroring.\n"
+"The 'x' in (x, y) will be used when using Flip on X and\n"
+"the 'y' in (x, y) will be used when using Flip on Y and"
+msgstr ""
+"Coordinates in format (x, y) used as reference for mirroring.\n"
+"The 'x' in (x, y) will be used when using Flip on X and\n"
+"the 'y' in (x, y) will be used when using Flip on Y and"
+
+#: flatcamGUI/PreferencesUI.py:6883 flatcamTools/ToolDistance.py:496
+#: flatcamTools/ToolDistanceMin.py:287 flatcamTools/ToolTransform.py:332
+msgid "Distance"
+msgstr "Distance"
+
+#: flatcamGUI/PreferencesUI.py:6885 flatcamTools/ToolTransform.py:334
+msgid ""
+"A positive value will create the effect of dilation,\n"
+"while a negative value will create the effect of erosion.\n"
+"Each geometry element of the object will be increased\n"
+"or decreased with the 'distance'."
+msgstr ""
+"A positive value will create the effect of dilation,\n"
+"while a negative value will create the effect of erosion.\n"
+"Each geometry element of the object will be increased\n"
+"or decreased with the 'distance'."
+
+#: flatcamGUI/PreferencesUI.py:6902 flatcamTools/ToolTransform.py:359
+msgid ""
+"A positive value will create the effect of dilation,\n"
+"while a negative value will create the effect of erosion.\n"
+"Each geometry element of the object will be increased\n"
+"or decreased to fit the 'Value'. Value is a percentage\n"
+"of the initial dimension."
+msgstr ""
+"A positive value will create the effect of dilation,\n"
+"while a negative value will create the effect of erosion.\n"
+"Each geometry element of the object will be increased\n"
+"or decreased to fit the 'Value'. Value is a percentage\n"
+"of the initial dimension."
+
+#: flatcamGUI/PreferencesUI.py:6919 flatcamGUI/PreferencesUI.py:7563
+#: flatcamTools/ToolQRCode.py:197 flatcamTools/ToolTransform.py:383
+msgid "Rounded"
+msgstr "Rounded"
+
+#: flatcamGUI/PreferencesUI.py:6921 flatcamTools/ToolTransform.py:385
+msgid ""
+"If checked then the buffer will surround the buffered shape,\n"
+"every corner will be rounded.\n"
+"If not checked then the buffer will follow the exact geometry\n"
+"of the buffered shape."
+msgstr ""
+"If checked then the buffer will surround the buffered shape,\n"
+"every corner will be rounded.\n"
+"If not checked then the buffer will follow the exact geometry\n"
+"of the buffered shape."
+
+#: flatcamGUI/PreferencesUI.py:6938
+msgid "SolderPaste Tool Options"
+msgstr "SolderPaste Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:6944
+msgid ""
+"A tool to create GCode for dispensing\n"
+"solder paste onto a PCB."
+msgstr ""
+"A tool to create GCode for dispensing\n"
+"solder paste onto a PCB."
+
+#: flatcamGUI/PreferencesUI.py:6965
+msgid "New Nozzle Dia"
+msgstr "New Nozzle Dia"
+
+#: flatcamGUI/PreferencesUI.py:6967 flatcamTools/ToolSolderPaste.py:107
+msgid "Diameter for the new Nozzle tool to add in the Tool Table"
+msgstr "Diameter for the new Nozzle tool to add in the Tool Table"
+
+#: flatcamGUI/PreferencesUI.py:6983 flatcamTools/ToolSolderPaste.py:183
+msgid "Z Dispense Start"
+msgstr "Z Dispense Start"
+
+#: flatcamGUI/PreferencesUI.py:6985 flatcamTools/ToolSolderPaste.py:185
+msgid "The height (Z) when solder paste dispensing starts."
+msgstr "The height (Z) when solder paste dispensing starts."
+
+#: flatcamGUI/PreferencesUI.py:6996 flatcamTools/ToolSolderPaste.py:195
+msgid "Z Dispense"
+msgstr "Z Dispense"
+
+#: flatcamGUI/PreferencesUI.py:6998 flatcamTools/ToolSolderPaste.py:197
+msgid "The height (Z) when doing solder paste dispensing."
+msgstr "The height (Z) when doing solder paste dispensing."
+
+#: flatcamGUI/PreferencesUI.py:7009 flatcamTools/ToolSolderPaste.py:207
+msgid "Z Dispense Stop"
+msgstr "Z Dispense Stop"
+
+#: flatcamGUI/PreferencesUI.py:7011 flatcamTools/ToolSolderPaste.py:209
+msgid "The height (Z) when solder paste dispensing stops."
+msgstr "The height (Z) when solder paste dispensing stops."
+
+#: flatcamGUI/PreferencesUI.py:7022 flatcamTools/ToolSolderPaste.py:219
+msgid "Z Travel"
+msgstr "Z Travel"
+
+#: flatcamGUI/PreferencesUI.py:7024 flatcamTools/ToolSolderPaste.py:221
+msgid ""
+"The height (Z) for travel between pads\n"
+"(without dispensing solder paste)."
+msgstr ""
+"The height (Z) for travel between pads\n"
+"(without dispensing solder paste)."
+
+#: flatcamGUI/PreferencesUI.py:7036 flatcamTools/ToolSolderPaste.py:232
+msgid "Z Toolchange"
+msgstr "Z Toolchange"
+
+#: flatcamGUI/PreferencesUI.py:7038 flatcamTools/ToolSolderPaste.py:234
+msgid "The height (Z) for tool (nozzle) change."
+msgstr "The height (Z) for tool (nozzle) change."
+
+#: flatcamGUI/PreferencesUI.py:7047 flatcamTools/ToolSolderPaste.py:242
+msgid ""
+"The X,Y location for tool (nozzle) change.\n"
+"The format is (x, y) where x and y are real numbers."
+msgstr ""
+"The X,Y location for tool (nozzle) change.\n"
+"The format is (x, y) where x and y are real numbers."
+
+#: flatcamGUI/PreferencesUI.py:7061 flatcamTools/ToolSolderPaste.py:255
+msgid "Feedrate (speed) while moving on the X-Y plane."
+msgstr "Feedrate (speed) while moving on the X-Y plane."
+
+#: flatcamGUI/PreferencesUI.py:7074 flatcamTools/ToolSolderPaste.py:267
+msgid ""
+"Feedrate (speed) while moving vertically\n"
+"(on Z plane)."
+msgstr ""
+"Feedrate (speed) while moving vertically\n"
+"(on Z plane)."
+
+#: flatcamGUI/PreferencesUI.py:7086 flatcamTools/ToolSolderPaste.py:278
+msgid "Feedrate Z Dispense"
+msgstr "Feedrate Z Dispense"
+
+#: flatcamGUI/PreferencesUI.py:7088
+msgid ""
+"Feedrate (speed) while moving up vertically\n"
+"to Dispense position (on Z plane)."
+msgstr ""
+"Feedrate (speed) while moving up vertically\n"
+"to Dispense position (on Z plane)."
+
+#: flatcamGUI/PreferencesUI.py:7099 flatcamTools/ToolSolderPaste.py:290
+msgid "Spindle Speed FWD"
+msgstr "Spindle Speed FWD"
+
+#: flatcamGUI/PreferencesUI.py:7101 flatcamTools/ToolSolderPaste.py:292
+msgid ""
+"The dispenser speed while pushing solder paste\n"
+"through the dispenser nozzle."
+msgstr ""
+"The dispenser speed while pushing solder paste\n"
+"through the dispenser nozzle."
+
+#: flatcamGUI/PreferencesUI.py:7113 flatcamTools/ToolSolderPaste.py:303
+msgid "Dwell FWD"
+msgstr "Dwell FWD"
+
+#: flatcamGUI/PreferencesUI.py:7115 flatcamTools/ToolSolderPaste.py:305
+msgid "Pause after solder dispensing."
+msgstr "Pause after solder dispensing."
+
+#: flatcamGUI/PreferencesUI.py:7125 flatcamTools/ToolSolderPaste.py:314
+msgid "Spindle Speed REV"
+msgstr "Spindle Speed REV"
+
+#: flatcamGUI/PreferencesUI.py:7127 flatcamTools/ToolSolderPaste.py:316
+msgid ""
+"The dispenser speed while retracting solder paste\n"
+"through the dispenser nozzle."
+msgstr ""
+"The dispenser speed while retracting solder paste\n"
+"through the dispenser nozzle."
+
+#: flatcamGUI/PreferencesUI.py:7139 flatcamTools/ToolSolderPaste.py:327
+msgid "Dwell REV"
+msgstr "Dwell REV"
+
+#: flatcamGUI/PreferencesUI.py:7141 flatcamTools/ToolSolderPaste.py:329
+msgid ""
+"Pause after solder paste dispenser retracted,\n"
+"to allow pressure equilibrium."
+msgstr ""
+"Pause after solder paste dispenser retracted,\n"
+"to allow pressure equilibrium."
+
+#: flatcamGUI/PreferencesUI.py:7150 flatcamTools/ToolSolderPaste.py:337
+msgid "Files that control the GCode generation."
+msgstr "Files that control the GCode generation."
+
+#: flatcamGUI/PreferencesUI.py:7165
+msgid "Substractor Tool Options"
+msgstr "Substractor Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:7171
+msgid ""
+"A tool to substract one Gerber or Geometry object\n"
+"from another of the same type."
+msgstr ""
+"A tool to substract one Gerber or Geometry object\n"
+"from another of the same type."
+
+#: flatcamGUI/PreferencesUI.py:7176 flatcamTools/ToolSub.py:155
+msgid "Close paths"
+msgstr "Close paths"
+
+#: flatcamGUI/PreferencesUI.py:7177
+msgid ""
+"Checking this will close the paths cut by the Geometry substractor object."
+msgstr ""
+"Checking this will close the paths cut by the Geometry substractor object."
+
+#: flatcamGUI/PreferencesUI.py:7188
+msgid "Check Rules Tool Options"
+msgstr "Check Rules Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:7193
+msgid ""
+"A tool to check if Gerber files are within a set\n"
+"of Manufacturing Rules."
+msgstr ""
+"A tool to check if Gerber files are within a set\n"
+"of Manufacturing Rules."
+
+#: flatcamGUI/PreferencesUI.py:7203 flatcamTools/ToolRulesCheck.py:265
+#: flatcamTools/ToolRulesCheck.py:929
+msgid "Trace Size"
+msgstr "Trace Size"
+
+#: flatcamGUI/PreferencesUI.py:7205 flatcamTools/ToolRulesCheck.py:267
+msgid "This checks if the minimum size for traces is met."
+msgstr "This checks if the minimum size for traces is met."
+
+#: flatcamGUI/PreferencesUI.py:7215 flatcamGUI/PreferencesUI.py:7235
+#: flatcamGUI/PreferencesUI.py:7255 flatcamGUI/PreferencesUI.py:7275
+#: flatcamGUI/PreferencesUI.py:7295 flatcamGUI/PreferencesUI.py:7315
+#: flatcamGUI/PreferencesUI.py:7335 flatcamGUI/PreferencesUI.py:7355
+#: flatcamGUI/PreferencesUI.py:7377 flatcamGUI/PreferencesUI.py:7397
+#: flatcamTools/ToolRulesCheck.py:277 flatcamTools/ToolRulesCheck.py:299
+#: flatcamTools/ToolRulesCheck.py:322 flatcamTools/ToolRulesCheck.py:345
+#: flatcamTools/ToolRulesCheck.py:368 flatcamTools/ToolRulesCheck.py:391
+#: flatcamTools/ToolRulesCheck.py:414 flatcamTools/ToolRulesCheck.py:437
+#: flatcamTools/ToolRulesCheck.py:462 flatcamTools/ToolRulesCheck.py:485
+msgid "Min value"
+msgstr "Min value"
+
+#: flatcamGUI/PreferencesUI.py:7217 flatcamTools/ToolRulesCheck.py:279
+msgid "Minimum acceptable trace size."
+msgstr "Minimum acceptable trace size."
+
+#: flatcamGUI/PreferencesUI.py:7222 flatcamTools/ToolRulesCheck.py:286
+#: flatcamTools/ToolRulesCheck.py:1157 flatcamTools/ToolRulesCheck.py:1187
+msgid "Copper to Copper clearance"
+msgstr "Copper to Copper clearance"
+
+#: flatcamGUI/PreferencesUI.py:7224 flatcamTools/ToolRulesCheck.py:288
+msgid ""
+"This checks if the minimum clearance between copper\n"
+"features is met."
+msgstr ""
+"This checks if the minimum clearance between copper\n"
+"features is met."
+
+#: flatcamGUI/PreferencesUI.py:7237 flatcamGUI/PreferencesUI.py:7257
+#: flatcamGUI/PreferencesUI.py:7277 flatcamGUI/PreferencesUI.py:7297
+#: flatcamGUI/PreferencesUI.py:7317 flatcamGUI/PreferencesUI.py:7337
+#: flatcamGUI/PreferencesUI.py:7399 flatcamTools/ToolRulesCheck.py:301
+#: flatcamTools/ToolRulesCheck.py:324 flatcamTools/ToolRulesCheck.py:347
+#: flatcamTools/ToolRulesCheck.py:370 flatcamTools/ToolRulesCheck.py:393
+#: flatcamTools/ToolRulesCheck.py:416 flatcamTools/ToolRulesCheck.py:464
+msgid "Minimum acceptable clearance value."
+msgstr "Minimum acceptable clearance value."
+
+#: flatcamGUI/PreferencesUI.py:7242 flatcamTools/ToolRulesCheck.py:309
+#: flatcamTools/ToolRulesCheck.py:1217 flatcamTools/ToolRulesCheck.py:1223
+#: flatcamTools/ToolRulesCheck.py:1236 flatcamTools/ToolRulesCheck.py:1243
+msgid "Copper to Outline clearance"
+msgstr "Copper to Outline clearance"
+
+#: flatcamGUI/PreferencesUI.py:7244 flatcamTools/ToolRulesCheck.py:311
+msgid ""
+"This checks if the minimum clearance between copper\n"
+"features and the outline is met."
+msgstr ""
+"This checks if the minimum clearance between copper\n"
+"features and the outline is met."
+
+#: flatcamGUI/PreferencesUI.py:7262 flatcamTools/ToolRulesCheck.py:332
+msgid "Silk to Silk Clearance"
+msgstr "Silk to Silk Clearance"
+
+#: flatcamGUI/PreferencesUI.py:7264 flatcamTools/ToolRulesCheck.py:334
+msgid ""
+"This checks if the minimum clearance between silkscreen\n"
+"features and silkscreen features is met."
+msgstr ""
+"This checks if the minimum clearance between silkscreen\n"
+"features and silkscreen features is met."
+
+#: flatcamGUI/PreferencesUI.py:7282 flatcamTools/ToolRulesCheck.py:355
+#: flatcamTools/ToolRulesCheck.py:1326 flatcamTools/ToolRulesCheck.py:1332
+#: flatcamTools/ToolRulesCheck.py:1350
+msgid "Silk to Solder Mask Clearance"
+msgstr "Silk to Solder Mask Clearance"
+
+#: flatcamGUI/PreferencesUI.py:7284 flatcamTools/ToolRulesCheck.py:357
+msgid ""
+"This checks if the minimum clearance between silkscreen\n"
+"features and soldermask features is met."
+msgstr ""
+"This checks if the minimum clearance between silkscreen\n"
+"features and soldermask features is met."
+
+#: flatcamGUI/PreferencesUI.py:7302 flatcamTools/ToolRulesCheck.py:378
+#: flatcamTools/ToolRulesCheck.py:1380 flatcamTools/ToolRulesCheck.py:1386
+#: flatcamTools/ToolRulesCheck.py:1400 flatcamTools/ToolRulesCheck.py:1407
+msgid "Silk to Outline Clearance"
+msgstr "Silk to Outline Clearance"
+
+#: flatcamGUI/PreferencesUI.py:7304 flatcamTools/ToolRulesCheck.py:380
+msgid ""
+"This checks if the minimum clearance between silk\n"
+"features and the outline is met."
+msgstr ""
+"This checks if the minimum clearance between silk\n"
+"features and the outline is met."
+
+#: flatcamGUI/PreferencesUI.py:7322 flatcamTools/ToolRulesCheck.py:401
+#: flatcamTools/ToolRulesCheck.py:1418 flatcamTools/ToolRulesCheck.py:1445
+msgid "Minimum Solder Mask Sliver"
+msgstr "Minimum Solder Mask Sliver"
+
+#: flatcamGUI/PreferencesUI.py:7324 flatcamTools/ToolRulesCheck.py:403
+msgid ""
+"This checks if the minimum clearance between soldermask\n"
+"features and soldermask features is met."
+msgstr ""
+"This checks if the minimum clearance between soldermask\n"
+"features and soldermask features is met."
+
+#: flatcamGUI/PreferencesUI.py:7342 flatcamTools/ToolRulesCheck.py:424
+#: flatcamTools/ToolRulesCheck.py:1483 flatcamTools/ToolRulesCheck.py:1489
+#: flatcamTools/ToolRulesCheck.py:1505 flatcamTools/ToolRulesCheck.py:1512
+msgid "Minimum Annular Ring"
+msgstr "Minimum Annular Ring"
+
+#: flatcamGUI/PreferencesUI.py:7344 flatcamTools/ToolRulesCheck.py:426
+msgid ""
+"This checks if the minimum copper ring left by drilling\n"
+"a hole into a pad is met."
+msgstr ""
+"This checks if the minimum copper ring left by drilling\n"
+"a hole into a pad is met."
+
+#: flatcamGUI/PreferencesUI.py:7357 flatcamTools/ToolRulesCheck.py:439
+msgid "Minimum acceptable ring value."
+msgstr "Minimum acceptable ring value."
+
+#: flatcamGUI/PreferencesUI.py:7364 flatcamTools/ToolRulesCheck.py:449
+#: flatcamTools/ToolRulesCheck.py:873
+msgid "Hole to Hole Clearance"
+msgstr "Hole to Hole Clearance"
+
+#: flatcamGUI/PreferencesUI.py:7366 flatcamTools/ToolRulesCheck.py:451
+msgid ""
+"This checks if the minimum clearance between a drill hole\n"
+"and another drill hole is met."
+msgstr ""
+"This checks if the minimum clearance between a drill hole\n"
+"and another drill hole is met."
+
+#: flatcamGUI/PreferencesUI.py:7379 flatcamTools/ToolRulesCheck.py:487
+msgid "Minimum acceptable drill size."
+msgstr "Minimum acceptable drill size."
+
+#: flatcamGUI/PreferencesUI.py:7384 flatcamTools/ToolRulesCheck.py:472
+#: flatcamTools/ToolRulesCheck.py:847
+msgid "Hole Size"
+msgstr "Hole Size"
+
+#: flatcamGUI/PreferencesUI.py:7386 flatcamTools/ToolRulesCheck.py:474
+msgid ""
+"This checks if the drill holes\n"
+"sizes are above the threshold."
+msgstr ""
+"This checks if the drill holes\n"
+"sizes are above the threshold."
+
+#: flatcamGUI/PreferencesUI.py:7411
+msgid "Optimal Tool Options"
+msgstr "Optimal Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:7417
+msgid ""
+"A tool to find the minimum distance between\n"
+"every two Gerber geometric elements"
+msgstr ""
+"A tool to find the minimum distance between\n"
+"every two Gerber geometric elements"
+
+#: flatcamGUI/PreferencesUI.py:7432 flatcamTools/ToolOptimal.py:78
+msgid "Precision"
+msgstr "Precision"
+
+#: flatcamGUI/PreferencesUI.py:7434
+msgid "Number of decimals for the distances and coordinates in this tool."
+msgstr "Number of decimals for the distances and coordinates in this tool."
+
+#: flatcamGUI/PreferencesUI.py:7448
+msgid "QRCode Tool Options"
+msgstr "QRCode Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:7454
+msgid ""
+"A tool to create a QRCode that can be inserted\n"
+"into a selected Gerber file, or it can be exported as a file."
+msgstr ""
+"A tool to create a QRCode that can be inserted\n"
+"into a selected Gerber file, or it can be exported as a file."
+
+#: flatcamGUI/PreferencesUI.py:7466 flatcamTools/ToolQRCode.py:100
+msgid "Version"
+msgstr "Version"
+
+#: flatcamGUI/PreferencesUI.py:7468 flatcamTools/ToolQRCode.py:102
+msgid ""
+"QRCode version can have values from 1 (21x21 boxes)\n"
+"to 40 (177x177 boxes)."
+msgstr ""
+"QRCode version can have values from 1 (21x21 boxes)\n"
+"to 40 (177x177 boxes)."
+
+#: flatcamGUI/PreferencesUI.py:7479 flatcamTools/ToolQRCode.py:113
+msgid "Error correction"
+msgstr "Error correction"
+
+#: flatcamGUI/PreferencesUI.py:7481 flatcamGUI/PreferencesUI.py:7492
+#: flatcamTools/ToolQRCode.py:115 flatcamTools/ToolQRCode.py:126
+#, python-format
+msgid ""
+"Parameter that controls the error correction used for the QR Code.\n"
+"L = maximum 7%% errors can be corrected\n"
+"M = maximum 15%% errors can be corrected\n"
+"Q = maximum 25%% errors can be corrected\n"
+"H = maximum 30%% errors can be corrected."
+msgstr ""
+"Parameter that controls the error correction used for the QR Code.\n"
+"L = maximum 7%% errors can be corrected\n"
+"M = maximum 15%% errors can be corrected\n"
+"Q = maximum 25%% errors can be corrected\n"
+"H = maximum 30%% errors can be corrected."
+
+#: flatcamGUI/PreferencesUI.py:7502 flatcamTools/ToolQRCode.py:136
+msgid "Box Size"
+msgstr "Box Size"
+
+#: flatcamGUI/PreferencesUI.py:7504 flatcamTools/ToolQRCode.py:138
+msgid ""
+"Box size control the overall size of the QRcode\n"
+"by adjusting the size of each box in the code."
+msgstr ""
+"Box size control the overall size of the QRcode\n"
+"by adjusting the size of each box in the code."
+
+#: flatcamGUI/PreferencesUI.py:7515 flatcamTools/ToolQRCode.py:149
+msgid "Border Size"
+msgstr "Border Size"
+
+#: flatcamGUI/PreferencesUI.py:7517 flatcamTools/ToolQRCode.py:151
+msgid ""
+"Size of the QRCode border. How many boxes thick is the border.\n"
+"Default value is 4. The width of the clearance around the QRCode."
+msgstr ""
+"Size of the QRCode border. How many boxes thick is the border.\n"
+"Default value is 4. The width of the clearance around the QRCode."
+
+#: flatcamGUI/PreferencesUI.py:7528 flatcamTools/ToolQRCode.py:162
+msgid "QRCode Data"
+msgstr "QRCode Data"
+
+#: flatcamGUI/PreferencesUI.py:7530 flatcamTools/ToolQRCode.py:164
+msgid "QRCode Data. Alphanumeric text to be encoded in the QRCode."
+msgstr "QRCode Data. Alphanumeric text to be encoded in the QRCode."
+
+#: flatcamGUI/PreferencesUI.py:7534 flatcamTools/ToolQRCode.py:168
+msgid "Add here the text to be included in the QRCode..."
+msgstr "Add here the text to be included in the QRCode..."
+
+#: flatcamGUI/PreferencesUI.py:7540 flatcamTools/ToolQRCode.py:174
+msgid "Polarity"
+msgstr "Polarity"
+
+#: flatcamGUI/PreferencesUI.py:7542 flatcamTools/ToolQRCode.py:176
+msgid ""
+"Choose the polarity of the QRCode.\n"
+"It can be drawn in a negative way (squares are clear)\n"
+"or in a positive way (squares are opaque)."
+msgstr ""
+"Choose the polarity of the QRCode.\n"
+"It can be drawn in a negative way (squares are clear)\n"
+"or in a positive way (squares are opaque)."
+
+#: flatcamGUI/PreferencesUI.py:7546 flatcamTools/ToolFilm.py:296
+#: flatcamTools/ToolQRCode.py:180
+msgid "Negative"
+msgstr "Negative"
+
+#: flatcamGUI/PreferencesUI.py:7547 flatcamTools/ToolFilm.py:295
+#: flatcamTools/ToolQRCode.py:181
+msgid "Positive"
+msgstr "Positive"
+
+#: flatcamGUI/PreferencesUI.py:7549 flatcamTools/ToolQRCode.py:183
+msgid ""
+"Choose the type of QRCode to be created.\n"
+"If added on a Silkscreen Gerber file the QRCode may\n"
+"be added as positive. If it is added to a Copper Gerber\n"
+"file then perhaps the QRCode can be added as negative."
+msgstr ""
+"Choose the type of QRCode to be created.\n"
+"If added on a Silkscreen Gerber file the QRCode may\n"
+"be added as positive. If it is added to a Copper Gerber\n"
+"file then perhaps the QRCode can be added as negative."
+
+#: flatcamGUI/PreferencesUI.py:7560 flatcamGUI/PreferencesUI.py:7566
+#: flatcamTools/ToolQRCode.py:194 flatcamTools/ToolQRCode.py:200
+msgid ""
+"The bounding box, meaning the empty space that surrounds\n"
+"the QRCode geometry, can have a rounded or a square shape."
+msgstr ""
+"The bounding box, meaning the empty space that surrounds\n"
+"the QRCode geometry, can have a rounded or a square shape."
+
+#: flatcamGUI/PreferencesUI.py:7573 flatcamTools/ToolQRCode.py:228
+msgid "Fill Color"
+msgstr "Fill Color"
+
+#: flatcamGUI/PreferencesUI.py:7575 flatcamTools/ToolQRCode.py:230
+msgid "Set the QRCode fill color (squares color)."
+msgstr "Set the QRCode fill color (squares color)."
+
+#: flatcamGUI/PreferencesUI.py:7594 flatcamTools/ToolQRCode.py:252
+msgid "Back Color"
+msgstr "Back Color"
+
+#: flatcamGUI/PreferencesUI.py:7596 flatcamTools/ToolQRCode.py:254
+msgid "Set the QRCode background color."
+msgstr "Set the QRCode background color."
+
+#: flatcamGUI/PreferencesUI.py:7636
+msgid "Copper Thieving Tool Options"
+msgstr "Copper Thieving Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:7648
+msgid ""
+"A tool to generate a Copper Thieving that can be added\n"
+"to a selected Gerber file."
+msgstr ""
+"A tool to generate a Copper Thieving that can be added\n"
+"to a selected Gerber file."
+
+#: flatcamGUI/PreferencesUI.py:7656
+msgid "Number of steps (lines) used to interpolate circles."
+msgstr "Number of steps (lines) used to interpolate circles."
+
+#: flatcamGUI/PreferencesUI.py:7666 flatcamGUI/PreferencesUI.py:7870
+#: flatcamTools/ToolCopperThieving.py:96 flatcamTools/ToolCopperThieving.py:431
+msgid "Clearance"
+msgstr "Clearance"
+
+#: flatcamGUI/PreferencesUI.py:7668
+msgid ""
+"This set the distance between the copper Thieving components\n"
+"(the polygon fill may be split in multiple polygons)\n"
+"and the copper traces in the Gerber file."
+msgstr ""
+"This set the distance between the copper Thieving components\n"
+"(the polygon fill may be split in multiple polygons)\n"
+"and the copper traces in the Gerber file."
+
+#: flatcamGUI/PreferencesUI.py:7699 flatcamTools/ToolCopperThieving.py:129
+msgid "Reference:"
+msgstr "Reference:"
+
+#: flatcamGUI/PreferencesUI.py:7701
+msgid ""
+"- '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."
+msgstr ""
+"- '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."
+
+#: flatcamGUI/PreferencesUI.py:7710 flatcamGUI/PreferencesUI.py:8175
+#: flatcamGUI/PreferencesUI.py:8287 flatcamGUI/PreferencesUI.py:8387
+#: flatcamGUI/PreferencesUI.py:8501 flatcamTools/ToolCopperThieving.py:171
+#: flatcamTools/ToolExtractDrills.py:102 flatcamTools/ToolExtractDrills.py:240
+#: flatcamTools/ToolPunchGerber.py:113 flatcamTools/ToolPunchGerber.py:268
+msgid "Rectangular"
+msgstr "Rectangular"
+
+#: flatcamGUI/PreferencesUI.py:7711 flatcamTools/ToolCopperThieving.py:172
+msgid "Minimal"
+msgstr "Minimal"
+
+#: flatcamGUI/PreferencesUI.py:7713 flatcamTools/ToolCopperThieving.py:174
+#: flatcamTools/ToolFilm.py:113
+msgid "Box Type:"
+msgstr "Box Type:"
+
+#: flatcamGUI/PreferencesUI.py:7715 flatcamTools/ToolCopperThieving.py:176
+msgid ""
+"- 'Rectangular' - the bounding box will be of rectangular shape.\n"
+"- 'Minimal' - the bounding box will be the convex hull shape."
+msgstr ""
+"- 'Rectangular' - the bounding box will be of rectangular shape.\n"
+"- 'Minimal' - the bounding box will be the convex hull shape."
+
+#: flatcamGUI/PreferencesUI.py:7729 flatcamTools/ToolCopperThieving.py:192
+msgid "Dots Grid"
+msgstr "Dots Grid"
+
+#: flatcamGUI/PreferencesUI.py:7730 flatcamTools/ToolCopperThieving.py:193
+msgid "Squares Grid"
+msgstr "Squares Grid"
+
+#: flatcamGUI/PreferencesUI.py:7731 flatcamTools/ToolCopperThieving.py:194
+msgid "Lines Grid"
+msgstr "Lines Grid"
+
+#: flatcamGUI/PreferencesUI.py:7733 flatcamTools/ToolCopperThieving.py:196
+msgid "Fill Type:"
+msgstr "Fill Type:"
+
+#: flatcamGUI/PreferencesUI.py:7735 flatcamTools/ToolCopperThieving.py:198
+msgid ""
+"- '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."
+msgstr ""
+"- '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."
+
+#: flatcamGUI/PreferencesUI.py:7743 flatcamTools/ToolCopperThieving.py:217
+msgid "Dots Grid Parameters"
+msgstr "Dots Grid Parameters"
+
+#: flatcamGUI/PreferencesUI.py:7749 flatcamTools/ToolCopperThieving.py:223
+msgid "Dot diameter in Dots Grid."
+msgstr "Dot diameter in Dots Grid."
+
+#: flatcamGUI/PreferencesUI.py:7760 flatcamGUI/PreferencesUI.py:7789
+#: flatcamGUI/PreferencesUI.py:7818 flatcamTools/ToolCopperThieving.py:234
+#: flatcamTools/ToolCopperThieving.py:274
+#: flatcamTools/ToolCopperThieving.py:314
+msgid "Spacing"
+msgstr "Spacing"
+
+#: flatcamGUI/PreferencesUI.py:7762 flatcamTools/ToolCopperThieving.py:236
+msgid "Distance between each two dots in Dots Grid."
+msgstr "Distance between each two dots in Dots Grid."
+
+#: flatcamGUI/PreferencesUI.py:7772 flatcamTools/ToolCopperThieving.py:257
+msgid "Squares Grid Parameters"
+msgstr "Squares Grid Parameters"
+
+#: flatcamGUI/PreferencesUI.py:7778 flatcamTools/ToolCopperThieving.py:263
+msgid "Square side size in Squares Grid."
+msgstr "Square side size in Squares Grid."
+
+#: flatcamGUI/PreferencesUI.py:7791 flatcamTools/ToolCopperThieving.py:276
+msgid "Distance between each two squares in Squares Grid."
+msgstr "Distance between each two squares in Squares Grid."
+
+#: flatcamGUI/PreferencesUI.py:7801 flatcamTools/ToolCopperThieving.py:297
+msgid "Lines Grid Parameters"
+msgstr "Lines Grid Parameters"
+
+#: flatcamGUI/PreferencesUI.py:7807 flatcamTools/ToolCopperThieving.py:303
+msgid "Line thickness size in Lines Grid."
+msgstr "Line thickness size in Lines Grid."
+
+#: flatcamGUI/PreferencesUI.py:7820 flatcamTools/ToolCopperThieving.py:316
+msgid "Distance between each two lines in Lines Grid."
+msgstr "Distance between each two lines in Lines Grid."
+
+#: flatcamGUI/PreferencesUI.py:7830 flatcamTools/ToolCopperThieving.py:354
+msgid "Robber Bar Parameters"
+msgstr "Robber Bar Parameters"
+
+#: flatcamGUI/PreferencesUI.py:7832 flatcamTools/ToolCopperThieving.py:356
+msgid ""
+"Parameters used for the robber bar.\n"
+"Robber bar = copper border to help in pattern hole plating."
+msgstr ""
+"Parameters used for the robber bar.\n"
+"Robber bar = copper border to help in pattern hole plating."
+
+#: flatcamGUI/PreferencesUI.py:7840 flatcamTools/ToolCopperThieving.py:364
+msgid "Bounding box margin for robber bar."
+msgstr "Bounding box margin for robber bar."
+
+#: flatcamGUI/PreferencesUI.py:7851 flatcamTools/ToolCopperThieving.py:375
+msgid "Thickness"
+msgstr "Thickness"
+
+#: flatcamGUI/PreferencesUI.py:7853 flatcamTools/ToolCopperThieving.py:377
+msgid "The robber bar thickness."
+msgstr "The robber bar thickness."
+
+#: flatcamGUI/PreferencesUI.py:7863 flatcamTools/ToolCopperThieving.py:408
+msgid "Pattern Plating Mask"
+msgstr "Pattern Plating Mask"
+
+#: flatcamGUI/PreferencesUI.py:7865 flatcamTools/ToolCopperThieving.py:410
+msgid "Generate a mask for pattern plating."
+msgstr "Generate a mask for pattern plating."
+
+#: flatcamGUI/PreferencesUI.py:7872 flatcamTools/ToolCopperThieving.py:433
+msgid ""
+"The distance between the possible copper thieving elements\n"
+"and/or robber bar and the actual openings in the mask."
+msgstr ""
+"The distance between the possible copper thieving elements\n"
+"and/or robber bar and the actual openings in the mask."
+
+#: flatcamGUI/PreferencesUI.py:7891
+msgid "Fiducials Tool Options"
+msgstr "Fiducials Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:7902 flatcamGUI/PreferencesUI.py:8018
+#: flatcamGUI/PreferencesUI.py:8137 flatcamGUI/PreferencesUI.py:8349
+#: flatcamTools/ToolCopperThieving.py:91 flatcamTools/ToolFiducials.py:151
+msgid "Parameters used for this tool."
+msgstr "Parameters used for this tool."
+
+#: flatcamGUI/PreferencesUI.py:7909 flatcamTools/ToolFiducials.py:158
+msgid ""
+"This set the fiducial diameter if fiducial type is circular,\n"
+"otherwise is the size of the fiducial.\n"
+"The soldermask opening is double than that."
+msgstr ""
+"This set the fiducial diameter if fiducial type is circular,\n"
+"otherwise is the size of the fiducial.\n"
+"The soldermask opening is double than that."
+
+#: flatcamGUI/PreferencesUI.py:7937 flatcamTools/ToolFiducials.py:186
+msgid "Auto"
+msgstr "Auto"
+
+#: flatcamGUI/PreferencesUI.py:7938 flatcamTools/ToolFiducials.py:187
+msgid "Manual"
+msgstr "Manual"
+
+#: flatcamGUI/PreferencesUI.py:7940 flatcamTools/ToolFiducials.py:189
+msgid "Mode:"
+msgstr "Mode:"
+
+#: flatcamGUI/PreferencesUI.py:7942
+msgid ""
+"- 'Auto' - automatic placement of fiducials in the corners of the bounding "
+"box.\n"
+"- 'Manual' - manual placement of fiducials."
+msgstr ""
+"- 'Auto' - automatic placement of fiducials in the corners of the bounding "
+"box.\n"
+"- 'Manual' - manual placement of fiducials."
+
+#: flatcamGUI/PreferencesUI.py:7950 flatcamTools/ToolFiducials.py:199
+msgid "Up"
+msgstr "Up"
+
+#: flatcamGUI/PreferencesUI.py:7951 flatcamTools/ToolFiducials.py:200
+msgid "Down"
+msgstr "Down"
+
+#: flatcamGUI/PreferencesUI.py:7954 flatcamTools/ToolFiducials.py:203
+msgid "Second fiducial"
+msgstr "Second fiducial"
+
+#: flatcamGUI/PreferencesUI.py:7956 flatcamTools/ToolFiducials.py:205
+msgid ""
+"The position for the second fiducial.\n"
+"- 'Up' - the order is: bottom-left, top-left, top-right.\n"
+"- 'Down' - the order is: bottom-left, bottom-right, top-right.\n"
+"- 'None' - there is no second fiducial. The order is: bottom-left, top-right."
+msgstr ""
+"The position for the second fiducial.\n"
+"- 'Up' - the order is: bottom-left, top-left, top-right.\n"
+"- 'Down' - the order is: bottom-left, bottom-right, top-right.\n"
+"- 'None' - there is no second fiducial. The order is: bottom-left, top-right."
+
+#: flatcamGUI/PreferencesUI.py:7972 flatcamTools/ToolFiducials.py:221
+msgid "Cross"
+msgstr "Cross"
+
+#: flatcamGUI/PreferencesUI.py:7973 flatcamTools/ToolFiducials.py:222
+msgid "Chess"
+msgstr "Chess"
+
+#: flatcamGUI/PreferencesUI.py:7976 flatcamTools/ToolFiducials.py:224
+msgid "Fiducial Type"
+msgstr "Fiducial Type"
+
+#: flatcamGUI/PreferencesUI.py:7978 flatcamTools/ToolFiducials.py:226
+msgid ""
+"The type of fiducial.\n"
+"- 'Circular' - this is the regular fiducial.\n"
+"- 'Cross' - cross lines fiducial.\n"
+"- 'Chess' - chess pattern fiducial."
+msgstr ""
+"The type of fiducial.\n"
+"- 'Circular' - this is the regular fiducial.\n"
+"- 'Cross' - cross lines fiducial.\n"
+"- 'Chess' - chess pattern fiducial."
+
+#: flatcamGUI/PreferencesUI.py:7987 flatcamTools/ToolFiducials.py:235
+msgid "Line thickness"
+msgstr "Line thickness"
+
+#: flatcamGUI/PreferencesUI.py:8007
+msgid "Calibration Tool Options"
+msgstr "Calibration Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:8023 flatcamTools/ToolCalibration.py:181
+msgid "Source Type"
+msgstr "Source Type"
+
+#: flatcamGUI/PreferencesUI.py:8024 flatcamTools/ToolCalibration.py:182
+msgid ""
+"The source of calibration points.\n"
+"It can be:\n"
+"- Object -> click a hole geo for Excellon or a pad for Gerber\n"
+"- Free -> click freely on canvas to acquire the calibration points"
+msgstr ""
+"The source of calibration points.\n"
+"It can be:\n"
+"- Object -> click a hole geo for Excellon or a pad for Gerber\n"
+"- Free -> click freely on canvas to acquire the calibration points"
+
+#: flatcamGUI/PreferencesUI.py:8029 flatcamTools/ToolCalibration.py:187
+msgid "Free"
+msgstr "Free"
+
+#: flatcamGUI/PreferencesUI.py:8043 flatcamTools/ToolCalibration.py:76
+msgid "Height (Z) for travelling between the points."
+msgstr "Height (Z) for travelling between the points."
+
+#: flatcamGUI/PreferencesUI.py:8055 flatcamTools/ToolCalibration.py:88
+msgid "Verification Z"
+msgstr "Verification Z"
+
+#: flatcamGUI/PreferencesUI.py:8057 flatcamTools/ToolCalibration.py:90
+msgid "Height (Z) for checking the point."
+msgstr "Height (Z) for checking the point."
+
+#: flatcamGUI/PreferencesUI.py:8069 flatcamTools/ToolCalibration.py:102
+msgid "Zero Z tool"
+msgstr "Zero Z tool"
+
+#: flatcamGUI/PreferencesUI.py:8071 flatcamTools/ToolCalibration.py:104
+msgid ""
+"Include a sequence to zero the height (Z)\n"
+"of the verification tool."
+msgstr ""
+"Include a sequence to zero the height (Z)\n"
+"of the verification tool."
+
+#: flatcamGUI/PreferencesUI.py:8080 flatcamTools/ToolCalibration.py:113
+msgid "Height (Z) for mounting the verification probe."
+msgstr "Height (Z) for mounting the verification probe."
+
+#: flatcamGUI/PreferencesUI.py:8094 flatcamTools/ToolCalibration.py:127
+msgid ""
+"Toolchange X,Y position.\n"
+"If no value is entered then the current\n"
+"(x, y) point will be used,"
+msgstr ""
+"Toolchange X,Y position.\n"
+"If no value is entered then the current\n"
+"(x, y) point will be used,"
+
+#: flatcamGUI/PreferencesUI.py:8105 flatcamTools/ToolCalibration.py:153
+msgid "Second point"
+msgstr "Second point"
+
+#: flatcamGUI/PreferencesUI.py:8107 flatcamTools/ToolCalibration.py:155
+msgid ""
+"Second point in the Gcode verification can be:\n"
+"- top-left -> the user will align the PCB vertically\n"
+"- bottom-right -> the user will align the PCB horizontally"
+msgstr ""
+"Second point in the Gcode verification can be:\n"
+"- top-left -> the user will align the PCB vertically\n"
+"- bottom-right -> the user will align the PCB horizontally"
+
+#: flatcamGUI/PreferencesUI.py:8126
+msgid "Extract Drills Options"
+msgstr "Extract Drills Options"
+
+#: flatcamGUI/PreferencesUI.py:8141 flatcamGUI/PreferencesUI.py:8353
+#: flatcamTools/ToolExtractDrills.py:68 flatcamTools/ToolPunchGerber.py:75
+msgid "Processed Pads Type"
+msgstr "Processed Pads Type"
+
+#: flatcamGUI/PreferencesUI.py:8143 flatcamGUI/PreferencesUI.py:8355
+#: flatcamTools/ToolExtractDrills.py:70 flatcamTools/ToolPunchGerber.py:77
+msgid ""
+"The type of pads shape to be processed.\n"
+"If the PCB has many SMD pads with rectangular pads,\n"
+"disable the Rectangular aperture."
+msgstr ""
+"The type of pads shape to be processed.\n"
+"If the PCB has many SMD pads with rectangular pads,\n"
+"disable the Rectangular aperture."
+
+#: flatcamGUI/PreferencesUI.py:8153 flatcamGUI/PreferencesUI.py:8365
+#: flatcamTools/ToolExtractDrills.py:80 flatcamTools/ToolPunchGerber.py:91
+msgid "Process Circular Pads."
+msgstr "Process Circular Pads."
+
+#: flatcamGUI/PreferencesUI.py:8159 flatcamGUI/PreferencesUI.py:8261
+#: flatcamGUI/PreferencesUI.py:8371 flatcamGUI/PreferencesUI.py:8475
+#: flatcamTools/ToolExtractDrills.py:86 flatcamTools/ToolExtractDrills.py:214
+#: flatcamTools/ToolPunchGerber.py:97 flatcamTools/ToolPunchGerber.py:242
+msgid "Oblong"
+msgstr "Oblong"
+
+#: flatcamGUI/PreferencesUI.py:8161 flatcamGUI/PreferencesUI.py:8373
+#: flatcamTools/ToolExtractDrills.py:88 flatcamTools/ToolPunchGerber.py:99
+msgid "Process Oblong Pads."
+msgstr "Process Oblong Pads."
+
+#: flatcamGUI/PreferencesUI.py:8169 flatcamGUI/PreferencesUI.py:8381
+#: flatcamTools/ToolExtractDrills.py:96 flatcamTools/ToolPunchGerber.py:107
+msgid "Process Square Pads."
+msgstr "Process Square Pads."
+
+#: flatcamGUI/PreferencesUI.py:8177 flatcamGUI/PreferencesUI.py:8389
+#: flatcamTools/ToolExtractDrills.py:104 flatcamTools/ToolPunchGerber.py:115
+msgid "Process Rectangular Pads."
+msgstr "Process Rectangular Pads."
+
+#: flatcamGUI/PreferencesUI.py:8183 flatcamGUI/PreferencesUI.py:8300
+#: flatcamGUI/PreferencesUI.py:8395 flatcamGUI/PreferencesUI.py:8514
+#: flatcamTools/ToolExtractDrills.py:110 flatcamTools/ToolExtractDrills.py:253
+#: flatcamTools/ToolProperties.py:172 flatcamTools/ToolPunchGerber.py:121
+#: flatcamTools/ToolPunchGerber.py:281
+msgid "Others"
+msgstr "Others"
+
+#: flatcamGUI/PreferencesUI.py:8185 flatcamGUI/PreferencesUI.py:8397
+#: flatcamTools/ToolExtractDrills.py:112 flatcamTools/ToolPunchGerber.py:123
+msgid "Process pads not in the categories above."
+msgstr "Process pads not in the categories above."
+
+#: flatcamGUI/PreferencesUI.py:8198 flatcamGUI/PreferencesUI.py:8222
+#: flatcamGUI/PreferencesUI.py:8411 flatcamGUI/PreferencesUI.py:8436
+#: flatcamTools/ToolExtractDrills.py:139 flatcamTools/ToolExtractDrills.py:156
+#: flatcamTools/ToolPunchGerber.py:150 flatcamTools/ToolPunchGerber.py:184
+msgid "Fixed Diameter"
+msgstr "Fixed Diameter"
+
+#: flatcamGUI/PreferencesUI.py:8199 flatcamGUI/PreferencesUI.py:8239
+#: flatcamGUI/PreferencesUI.py:8412 flatcamGUI/PreferencesUI.py:8453
+#: flatcamTools/ToolExtractDrills.py:140 flatcamTools/ToolExtractDrills.py:192
+#: flatcamTools/ToolPunchGerber.py:151 flatcamTools/ToolPunchGerber.py:214
+msgid "Fixed Annular Ring"
+msgstr "Fixed Annular Ring"
+
+#: flatcamGUI/PreferencesUI.py:8200 flatcamGUI/PreferencesUI.py:8413
+#: flatcamTools/ToolExtractDrills.py:141 flatcamTools/ToolPunchGerber.py:152
+msgid "Proportional"
+msgstr "Proportional"
+
+#: flatcamGUI/PreferencesUI.py:8206 flatcamTools/ToolExtractDrills.py:130
+msgid ""
+"The method for processing pads. Can be:\n"
+"- Fixed Diameter -> all holes will have a set size\n"
+"- Fixed Annular Ring -> all holes will have a set annular ring\n"
+"- Proportional -> each hole size will be a fraction of the pad size"
+msgstr ""
+"The method for processing pads. Can be:\n"
+"- Fixed Diameter -> all holes will have a set size\n"
+"- Fixed Annular Ring -> all holes will have a set annular ring\n"
+"- Proportional -> each hole size will be a fraction of the pad size"
+
+#: flatcamGUI/PreferencesUI.py:8232 flatcamGUI/PreferencesUI.py:8446
+#: flatcamTools/ToolExtractDrills.py:166 flatcamTools/ToolPunchGerber.py:194
+msgid "Fixed hole diameter."
+msgstr "Fixed hole diameter."
+
+#: flatcamGUI/PreferencesUI.py:8241 flatcamGUI/PreferencesUI.py:8455
+#: flatcamTools/ToolExtractDrills.py:194 flatcamTools/ToolPunchGerber.py:216
+msgid ""
+"The size of annular ring.\n"
+"The copper sliver between the hole exterior\n"
+"and the margin of the copper pad."
+msgstr ""
+"The size of annular ring.\n"
+"The copper sliver between the hole exterior\n"
+"and the margin of the copper pad."
+
+#: flatcamGUI/PreferencesUI.py:8250 flatcamGUI/PreferencesUI.py:8464
+#: flatcamTools/ToolExtractDrills.py:203 flatcamTools/ToolPunchGerber.py:231
+msgid "The size of annular ring for circular pads."
+msgstr "The size of annular ring for circular pads."
+
+#: flatcamGUI/PreferencesUI.py:8263 flatcamGUI/PreferencesUI.py:8477
+#: flatcamTools/ToolExtractDrills.py:216 flatcamTools/ToolPunchGerber.py:244
+msgid "The size of annular ring for oblong pads."
+msgstr "The size of annular ring for oblong pads."
+
+#: flatcamGUI/PreferencesUI.py:8276 flatcamGUI/PreferencesUI.py:8490
+#: flatcamTools/ToolExtractDrills.py:229 flatcamTools/ToolPunchGerber.py:257
+msgid "The size of annular ring for square pads."
+msgstr "The size of annular ring for square pads."
+
+#: flatcamGUI/PreferencesUI.py:8289 flatcamGUI/PreferencesUI.py:8503
+#: flatcamTools/ToolExtractDrills.py:242 flatcamTools/ToolPunchGerber.py:270
+msgid "The size of annular ring for rectangular pads."
+msgstr "The size of annular ring for rectangular pads."
+
+#: flatcamGUI/PreferencesUI.py:8302 flatcamGUI/PreferencesUI.py:8516
+#: flatcamTools/ToolExtractDrills.py:255 flatcamTools/ToolPunchGerber.py:283
+msgid "The size of annular ring for other pads."
+msgstr "The size of annular ring for other pads."
+
+#: flatcamGUI/PreferencesUI.py:8312 flatcamGUI/PreferencesUI.py:8526
+#: flatcamTools/ToolExtractDrills.py:276 flatcamTools/ToolPunchGerber.py:299
+msgid "Proportional Diameter"
+msgstr "Proportional Diameter"
+
+#: flatcamGUI/PreferencesUI.py:8321 flatcamGUI/PreferencesUI.py:8535
+msgid "Factor"
+msgstr "Factor"
+
+#: flatcamGUI/PreferencesUI.py:8323 flatcamGUI/PreferencesUI.py:8537
+#: flatcamTools/ToolExtractDrills.py:287 flatcamTools/ToolPunchGerber.py:310
+msgid ""
+"Proportional Diameter.\n"
+"The hole diameter will be a fraction of the pad size."
+msgstr ""
+"Proportional Diameter.\n"
+"The hole diameter will be a fraction of the pad size."
+
+#: flatcamGUI/PreferencesUI.py:8338
+msgid "Punch Gerber Options"
+msgstr "Punch Gerber Options"
+
+#: flatcamGUI/PreferencesUI.py:8419 flatcamTools/ToolPunchGerber.py:141
+#| msgid ""
+#| "The punch hole source can be:\n"
+#| "- Excellon Object-> the Excellon object drills center will serve as "
+#| "reference.\n"
+#| "- Fixed Diameter -> will try to use the pads center as reference adding "
+#| "fixed diameter holes.\n"
+#| "- Fixed Annular Ring -> will try to keep a set annular ring.\n"
+#| "- Proportional -> will make a Gerber punch hole having the diameter a "
+#| "percentage of the pad diameter.\n"
+msgid ""
+"The punch hole source can be:\n"
+"- Excellon Object-> the Excellon object drills center will serve as "
+"reference.\n"
+"- Fixed Diameter -> will try to use the pads center as reference adding "
+"fixed diameter holes.\n"
+"- Fixed Annular Ring -> will try to keep a set annular ring.\n"
+"- Proportional -> will make a Gerber punch hole having the diameter a "
+"percentage of the pad diameter."
+msgstr ""
+"The punch hole source can be:\n"
+"- Excellon Object-> the Excellon object drills center will serve as "
+"reference.\n"
+"- Fixed Diameter -> will try to use the pads center as reference adding "
+"fixed diameter holes.\n"
+"- Fixed Annular Ring -> will try to keep a set annular ring.\n"
+"- Proportional -> will make a Gerber punch hole having the diameter a "
+"percentage of the pad diameter."
+
+#: flatcamGUI/PreferencesUI.py:8552
+msgid "Invert Gerber Tool Options"
+msgstr "Invert Gerber Tool Options"
+
+#: flatcamGUI/PreferencesUI.py:8558
+msgid ""
+"A tool to invert Gerber geometry from positive to negative\n"
+"and in revers."
+msgstr ""
+"A tool to invert Gerber geometry from positive to negative\n"
+"and in revers."
+
+#: flatcamGUI/PreferencesUI.py:8572 flatcamTools/ToolInvertGerber.py:90
+msgid ""
+"Distance by which to avoid\n"
+"the edges of the Gerber object."
+msgstr ""
+"Distance by which to avoid\n"
+"the edges of the Gerber object."
+
+#: flatcamGUI/PreferencesUI.py:8583 flatcamTools/ToolInvertGerber.py:101
+msgid "Lines Join Style"
+msgstr "Lines Join Style"
+
+#: flatcamGUI/PreferencesUI.py:8585 flatcamTools/ToolInvertGerber.py:103
+msgid ""
+"The way that the lines in the object outline will be joined.\n"
+"Can be:\n"
+"- rounded -> an arc is added between two joining lines\n"
+"- square -> the lines meet in 90 degrees angle\n"
+"- bevel -> the lines are joined by a third line"
+msgstr ""
+"The way that the lines in the object outline will be joined.\n"
+"Can be:\n"
+"- rounded -> an arc is added between two joining lines\n"
+"- square -> the lines meet in 90 degrees angle\n"
+"- bevel -> the lines are joined by a third line"
+
+#: flatcamGUI/PreferencesUI.py:8608
+msgid "Excellon File associations"
+msgstr "Excellon File associations"
+
+#: flatcamGUI/PreferencesUI.py:8621 flatcamGUI/PreferencesUI.py:8694
+#: flatcamGUI/PreferencesUI.py:8764 flatcamGUI/PreferencesUI.py:8834
+msgid "Restore"
+msgstr "Restore"
+
+#: flatcamGUI/PreferencesUI.py:8622 flatcamGUI/PreferencesUI.py:8695
+#: flatcamGUI/PreferencesUI.py:8765
+msgid "Restore the extension list to the default state."
+msgstr "Restore the extension list to the default state."
+
+#: flatcamGUI/PreferencesUI.py:8623 flatcamGUI/PreferencesUI.py:8696
+#: flatcamGUI/PreferencesUI.py:8766 flatcamGUI/PreferencesUI.py:8836
+msgid "Delete All"
+msgstr "Delete All"
+
+#: flatcamGUI/PreferencesUI.py:8624 flatcamGUI/PreferencesUI.py:8697
+#: flatcamGUI/PreferencesUI.py:8767
+msgid "Delete all extensions from the list."
+msgstr "Delete all extensions from the list."
+
+#: flatcamGUI/PreferencesUI.py:8632 flatcamGUI/PreferencesUI.py:8705
+#: flatcamGUI/PreferencesUI.py:8775
+msgid "Extensions list"
+msgstr "Extensions list"
+
+#: flatcamGUI/PreferencesUI.py:8634 flatcamGUI/PreferencesUI.py:8707
+#: flatcamGUI/PreferencesUI.py:8777
+msgid ""
+"List of file extensions to be\n"
+"associated with FlatCAM."
+msgstr ""
+"List of file extensions to be\n"
+"associated with FlatCAM."
+
+#: flatcamGUI/PreferencesUI.py:8654 flatcamGUI/PreferencesUI.py:8727
+#: flatcamGUI/PreferencesUI.py:8796 flatcamGUI/PreferencesUI.py:8868
+msgid "Extension"
+msgstr "Extension"
+
+#: flatcamGUI/PreferencesUI.py:8655 flatcamGUI/PreferencesUI.py:8728
+#: flatcamGUI/PreferencesUI.py:8797
+msgid "A file extension to be added or deleted to the list."
+msgstr "A file extension to be added or deleted to the list."
+
+#: flatcamGUI/PreferencesUI.py:8663 flatcamGUI/PreferencesUI.py:8736
+#: flatcamGUI/PreferencesUI.py:8805
+msgid "Add Extension"
+msgstr "Add Extension"
+
+#: flatcamGUI/PreferencesUI.py:8664 flatcamGUI/PreferencesUI.py:8737
+#: flatcamGUI/PreferencesUI.py:8806
+msgid "Add a file extension to the list"
+msgstr "Add a file extension to the list"
+
+#: flatcamGUI/PreferencesUI.py:8665 flatcamGUI/PreferencesUI.py:8738
+#: flatcamGUI/PreferencesUI.py:8807
+msgid "Delete Extension"
+msgstr "Delete Extension"
+
+#: flatcamGUI/PreferencesUI.py:8666 flatcamGUI/PreferencesUI.py:8739
+#: flatcamGUI/PreferencesUI.py:8808
+msgid "Delete a file extension from the list"
+msgstr "Delete a file extension from the list"
+
+#: flatcamGUI/PreferencesUI.py:8673 flatcamGUI/PreferencesUI.py:8746
+#: flatcamGUI/PreferencesUI.py:8815
+msgid "Apply Association"
+msgstr "Apply Association"
+
+#: flatcamGUI/PreferencesUI.py:8674 flatcamGUI/PreferencesUI.py:8747
+#: flatcamGUI/PreferencesUI.py:8816
+msgid ""
+"Apply the file associations between\n"
+"FlatCAM and the files with above extensions.\n"
+"They will be active after next logon.\n"
+"This work only in Windows."
+msgstr ""
+"Apply the file associations between\n"
+"FlatCAM and the files with above extensions.\n"
+"They will be active after next logon.\n"
+"This work only in Windows."
+
+#: flatcamGUI/PreferencesUI.py:8691
+msgid "GCode File associations"
+msgstr "GCode File associations"
+
+#: flatcamGUI/PreferencesUI.py:8761
+msgid "Gerber File associations"
+msgstr "Gerber File associations"
+
+#: flatcamGUI/PreferencesUI.py:8831
+msgid "Autocompleter Keywords"
+msgstr "Autocompleter Keywords"
+
+#: flatcamGUI/PreferencesUI.py:8835
+msgid "Restore the autocompleter keywords list to the default state."
+msgstr "Restore the autocompleter keywords list to the default state."
+
+#: flatcamGUI/PreferencesUI.py:8837
+msgid "Delete all autocompleter keywords from the list."
+msgstr "Delete all autocompleter keywords from the list."
+
+#: flatcamGUI/PreferencesUI.py:8845
+msgid "Keywords list"
+msgstr "Keywords list"
+
+#: flatcamGUI/PreferencesUI.py:8847
+msgid ""
+"List of keywords used by\n"
+"the autocompleter in FlatCAM.\n"
+"The autocompleter is installed\n"
+"in the Code Editor and for the Tcl Shell."
+msgstr ""
+"List of keywords used by\n"
+"the autocompleter in FlatCAM.\n"
+"The autocompleter is installed\n"
+"in the Code Editor and for the Tcl Shell."
+
+#: flatcamGUI/PreferencesUI.py:8869
+msgid "A keyword to be added or deleted to the list."
+msgstr "A keyword to be added or deleted to the list."
+
+#: flatcamGUI/PreferencesUI.py:8877
+msgid "Add keyword"
+msgstr "Add keyword"
+
+#: flatcamGUI/PreferencesUI.py:8878
+msgid "Add a keyword to the list"
+msgstr "Add a keyword to the list"
+
+#: flatcamGUI/PreferencesUI.py:8879
+msgid "Delete keyword"
+msgstr "Delete keyword"
+
+#: flatcamGUI/PreferencesUI.py:8880
+msgid "Delete a keyword from the list"
+msgstr "Delete a keyword from the list"
+
+#: flatcamObjects/FlatCAMCNCJob.py:429 flatcamObjects/FlatCAMDocument.py:71
+#: flatcamObjects/FlatCAMScript.py:83
+msgid "<span style=\"color:green;\"><b>Basic</b></span>"
+msgstr "<span style=\"color:green;\"><b>Basic</b></span>"
+
+#: flatcamObjects/FlatCAMCNCJob.py:435 flatcamObjects/FlatCAMDocument.py:75
+#: flatcamObjects/FlatCAMScript.py:87
+msgid "<span style=\"color:red;\"><b>Advanced</b></span>"
+msgstr "<span style=\"color:red;\"><b>Advanced</b></span>"
+
+#: flatcamObjects/FlatCAMCNCJob.py:478
+msgid "Plotting..."
+msgstr "Plotting..."
+
+#: flatcamObjects/FlatCAMCNCJob.py:507 flatcamObjects/FlatCAMCNCJob.py:512
+#: flatcamTools/ToolSolderPaste.py:1499
+msgid "Export Machine Code ..."
+msgstr "Export Machine Code ..."
+
+#: flatcamObjects/FlatCAMCNCJob.py:517 flatcamTools/ToolSolderPaste.py:1503
+msgid "Export Machine Code cancelled ..."
+msgstr "Export Machine Code cancelled ..."
+
+#: flatcamObjects/FlatCAMCNCJob.py:538
+msgid "Machine Code file saved to"
+msgstr "Machine Code file saved to"
+
+#: flatcamObjects/FlatCAMCNCJob.py:599 flatcamTools/ToolCalibration.py:1097
+msgid "Loaded Machine Code into Code Editor"
+msgstr "Loaded Machine Code into Code Editor"
+
+#: flatcamObjects/FlatCAMCNCJob.py:739
+msgid "This CNCJob object can't be processed because it is a"
+msgstr "This CNCJob object can't be processed because it is a"
+
+#: flatcamObjects/FlatCAMCNCJob.py:741
+msgid "CNCJob object"
+msgstr "CNCJob object"
+
+#: flatcamObjects/FlatCAMCNCJob.py:921
+msgid ""
+"G-code does not have a G94 code and we will not include the code in the "
+"'Prepend to GCode' text box"
+msgstr ""
+"G-code does not have a G94 code and we will not include the code in the "
+"'Prepend to GCode' text box"
+
+#: flatcamObjects/FlatCAMCNCJob.py:932
+msgid "Cancelled. The Toolchange Custom code is enabled but it's empty."
+msgstr "Cancelled. The Toolchange Custom code is enabled but it's empty."
+
+#: flatcamObjects/FlatCAMCNCJob.py:937
+msgid "Toolchange G-code was replaced by a custom code."
+msgstr "Toolchange G-code was replaced by a custom code."
+
+#: flatcamObjects/FlatCAMCNCJob.py:985 flatcamObjects/FlatCAMCNCJob.py:995
+msgid ""
+"The used preprocessor file has to have in it's name: 'toolchange_custom'"
+msgstr ""
+"The used preprocessor file has to have in it's name: 'toolchange_custom'"
+
+#: flatcamObjects/FlatCAMCNCJob.py:998
+msgid "There is no preprocessor file."
+msgstr "There is no preprocessor file."
+
+#: flatcamObjects/FlatCAMDocument.py:175
+msgid "Document Editor"
+msgstr "Document Editor"
+
+#: flatcamObjects/FlatCAMExcellon.py:514 flatcamObjects/FlatCAMExcellon.py:746
+#: flatcamObjects/FlatCAMGeometry.py:306 flatcamObjects/FlatCAMGeometry.py:767
+#: flatcamTools/ToolNCC.py:811 flatcamTools/ToolNCC.py:1191
+#: flatcamTools/ToolPaint.py:779 flatcamTools/ToolPaint.py:1166
+msgid "Multiple Tools"
+msgstr "Multiple Tools"
+
+#: flatcamObjects/FlatCAMExcellon.py:726
+msgid "No Tool Selected"
+msgstr "No Tool Selected"
+
+#: flatcamObjects/FlatCAMExcellon.py:1076
+#: flatcamObjects/FlatCAMExcellon.py:1168
+#: flatcamObjects/FlatCAMExcellon.py:1355
+msgid "Please select one or more tools from the list and try again."
+msgstr "Please select one or more tools from the list and try again."
+
+#: flatcamObjects/FlatCAMExcellon.py:1083
+msgid "Milling tool for DRILLS is larger than hole size. Cancelled."
+msgstr "Milling tool for DRILLS is larger than hole size. Cancelled."
+
+#: flatcamObjects/FlatCAMExcellon.py:1098
+#: flatcamObjects/FlatCAMExcellon.py:1188
+#: flatcamObjects/FlatCAMExcellon.py:1373
+#: tclCommands/TclCommandDrillcncjob.py:193
+msgid "Tool_nr"
+msgstr "Tool_nr"
+
+#: flatcamObjects/FlatCAMExcellon.py:1098
+#: flatcamObjects/FlatCAMExcellon.py:1188
+#: flatcamObjects/FlatCAMExcellon.py:1373
+#: tclCommands/TclCommandDrillcncjob.py:193
+msgid "Drills_Nr"
+msgstr "Drills_Nr"
+
+#: flatcamObjects/FlatCAMExcellon.py:1098
+#: flatcamObjects/FlatCAMExcellon.py:1188
+#: flatcamObjects/FlatCAMExcellon.py:1373
+#: tclCommands/TclCommandDrillcncjob.py:193
+msgid "Slots_Nr"
+msgstr "Slots_Nr"
+
+#: flatcamObjects/FlatCAMExcellon.py:1177
+msgid "Milling tool for SLOTS is larger than hole size. Cancelled."
+msgstr "Milling tool for SLOTS is larger than hole size. Cancelled."
+
+#: flatcamObjects/FlatCAMExcellon.py:1281
+#: flatcamObjects/FlatCAMGeometry.py:1517
+msgid "Focus Z"
+msgstr "Focus Z"
+
+#: flatcamObjects/FlatCAMExcellon.py:1300
+#: flatcamObjects/FlatCAMGeometry.py:1536
+msgid "Laser Power"
+msgstr "Laser Power"
+
+#: flatcamObjects/FlatCAMExcellon.py:1430
+#: flatcamObjects/FlatCAMGeometry.py:1973
+#: flatcamObjects/FlatCAMGeometry.py:1977
+#: flatcamObjects/FlatCAMGeometry.py:2122
+msgid "Generating CNC Code"
+msgstr "Generating CNC Code"
+
+#: flatcamObjects/FlatCAMExcellon.py:1620 flatcamTools/ToolNCC.py:918
+#: flatcamTools/ToolPaint.py:844
+msgid "Current Tool parameters were applied to all tools."
+msgstr "Current Tool parameters were applied to all tools."
+
+#: flatcamObjects/FlatCAMGeometry.py:119 flatcamObjects/FlatCAMGeometry.py:1181
+#: flatcamObjects/FlatCAMGeometry.py:1182
+#: flatcamObjects/FlatCAMGeometry.py:1191
+msgid "Iso"
+msgstr "Iso"
+
+#: flatcamObjects/FlatCAMGeometry.py:119 flatcamObjects/FlatCAMGeometry.py:439
+#: flatcamObjects/FlatCAMGeometry.py:826 flatcamObjects/FlatCAMGerber.py:890
+#: flatcamObjects/FlatCAMGerber.py:1038
+msgid "Rough"
+msgstr "Rough"
+
+#: flatcamObjects/FlatCAMGeometry.py:119
+msgid "Finish"
+msgstr "Finish"
+
+#: flatcamObjects/FlatCAMGeometry.py:474
+msgid "Add from Tool DB"
+msgstr "Add from Tool DB"
+
+#: flatcamObjects/FlatCAMGeometry.py:845
+msgid "Tool added in Tool Table."
+msgstr "Tool added in Tool Table."
+
+#: flatcamObjects/FlatCAMGeometry.py:954 flatcamObjects/FlatCAMGeometry.py:963
+msgid "Failed. Select a tool to copy."
+msgstr "Failed. Select a tool to copy."
+
+#: flatcamObjects/FlatCAMGeometry.py:992
+msgid "Tool was copied in Tool Table."
+msgstr "Tool was copied in Tool Table."
+
+#: flatcamObjects/FlatCAMGeometry.py:1019
+msgid "Tool was edited in Tool Table."
+msgstr "Tool was edited in Tool Table."
+
+#: flatcamObjects/FlatCAMGeometry.py:1048
+#: flatcamObjects/FlatCAMGeometry.py:1057
+msgid "Failed. Select a tool to delete."
+msgstr "Failed. Select a tool to delete."
+
+#: flatcamObjects/FlatCAMGeometry.py:1081
+msgid "Tool was deleted in Tool Table."
+msgstr "Tool was deleted in Tool Table."
+
+#: flatcamObjects/FlatCAMGeometry.py:1589
+msgid "This Geometry can't be processed because it is"
+msgstr "This Geometry can't be processed because it is"
+
+#: flatcamObjects/FlatCAMGeometry.py:1589
+msgid "geometry"
+msgstr "geometry"
+
+#: flatcamObjects/FlatCAMGeometry.py:1630
+msgid "Failed. No tool selected in the tool table ..."
+msgstr "Failed. No tool selected in the tool table ..."
+
+#: flatcamObjects/FlatCAMGeometry.py:1732
+#: flatcamObjects/FlatCAMGeometry.py:1882
+msgid ""
+"Tool Offset is selected in Tool Table but no value is provided.\n"
+"Add a Tool Offset or change the Offset Type."
+msgstr ""
+"Tool Offset is selected in Tool Table but no value is provided.\n"
+"Add a Tool Offset or change the Offset Type."
+
+#: flatcamObjects/FlatCAMGeometry.py:1798
+#: flatcamObjects/FlatCAMGeometry.py:1944
+msgid "G-Code parsing in progress..."
+msgstr "G-Code parsing in progress..."
+
+#: flatcamObjects/FlatCAMGeometry.py:1800
+#: flatcamObjects/FlatCAMGeometry.py:1946
+msgid "G-Code parsing finished..."
+msgstr "G-Code parsing finished..."
+
+#: flatcamObjects/FlatCAMGeometry.py:1808
+msgid "Finished G-Code processing"
+msgstr "Finished G-Code processing"
+
+#: flatcamObjects/FlatCAMGeometry.py:1810
+#: flatcamObjects/FlatCAMGeometry.py:1958
+msgid "G-Code processing failed with error"
+msgstr "G-Code processing failed with error"
+
+#: flatcamObjects/FlatCAMGeometry.py:1852 flatcamTools/ToolSolderPaste.py:1301
+msgid "Cancelled. Empty file, it has no geometry"
+msgstr "Cancelled. Empty file, it has no geometry"
+
+#: flatcamObjects/FlatCAMGeometry.py:1956
+#: flatcamObjects/FlatCAMGeometry.py:2117
+msgid "Finished G-Code processing..."
+msgstr "Finished G-Code processing..."
+
+#: flatcamObjects/FlatCAMGeometry.py:1975
+#: flatcamObjects/FlatCAMGeometry.py:1979
+#: flatcamObjects/FlatCAMGeometry.py:2124
+msgid "CNCjob created"
+msgstr "CNCjob created"
+
+#: flatcamObjects/FlatCAMGeometry.py:2155
+#: flatcamObjects/FlatCAMGeometry.py:2164 flatcamParsers/ParseGerber.py:1867
+#: flatcamParsers/ParseGerber.py:1877
+msgid "Scale factor has to be a number: integer or float."
+msgstr "Scale factor has to be a number: integer or float."
+
+#: flatcamObjects/FlatCAMGeometry.py:2227
+msgid "Geometry Scale done."
+msgstr "Geometry Scale done."
+
+#: flatcamObjects/FlatCAMGeometry.py:2244 flatcamParsers/ParseGerber.py:1993
+msgid ""
+"An (x,y) pair of values are needed. Probable you entered only one value in "
+"the Offset field."
+msgstr ""
+"An (x,y) pair of values are needed. Probable you entered only one value in "
+"the Offset field."
+
+#: flatcamObjects/FlatCAMGeometry.py:2300
+msgid "Geometry Offset done."
+msgstr "Geometry Offset done."
+
+#: flatcamObjects/FlatCAMGeometry.py:2329
+msgid ""
+"The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, "
+"y)\n"
+"but now there is only one value, not two."
+msgstr ""
+"The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, "
+"y)\n"
+"but now there is only one value, not two."
+
+#: flatcamObjects/FlatCAMGerber.py:493
+msgid "Buffering solid geometry"
+msgstr "Buffering solid geometry"
+
+#: flatcamObjects/FlatCAMGerber.py:502
+msgid "Done"
+msgstr "Done"
+
+#: flatcamObjects/FlatCAMGerber.py:528 flatcamObjects/FlatCAMGerber.py:554
+msgid "Operation could not be done."
+msgstr "Operation could not be done."
+
+#: flatcamObjects/FlatCAMGerber.py:571
+msgid "Isolating..."
+msgstr "Isolating..."
+
+#: flatcamObjects/FlatCAMGerber.py:630
+msgid "Click on a polygon to isolate it."
+msgstr "Click on a polygon to isolate it."
+
+#: flatcamObjects/FlatCAMGerber.py:669 flatcamObjects/FlatCAMGerber.py:773
+#: flatcamTools/ToolPaint.py:1511
+msgid "Added polygon"
+msgstr "Added polygon"
+
+#: flatcamObjects/FlatCAMGerber.py:670 flatcamObjects/FlatCAMGerber.py:775
+msgid "Click to add next polygon or right click to start isolation."
+msgstr "Click to add next polygon or right click to start isolation."
+
+#: flatcamObjects/FlatCAMGerber.py:682 flatcamTools/ToolPaint.py:1525
+msgid "Removed polygon"
+msgstr "Removed polygon"
+
+#: flatcamObjects/FlatCAMGerber.py:683
+msgid "Click to add/remove next polygon or right click to start isolation."
+msgstr "Click to add/remove next polygon or right click to start isolation."
+
+#: flatcamObjects/FlatCAMGerber.py:688 flatcamTools/ToolPaint.py:1531
+msgid "No polygon detected under click position."
+msgstr "No polygon detected under click position."
+
+#: flatcamObjects/FlatCAMGerber.py:709 flatcamTools/ToolPaint.py:1560
+msgid "List of single polygons is empty. Aborting."
+msgstr "List of single polygons is empty. Aborting."
+
+#: flatcamObjects/FlatCAMGerber.py:778
+msgid "No polygon in selection."
+msgstr "No polygon in selection."
+
+#: flatcamObjects/FlatCAMGerber.py:906 flatcamObjects/FlatCAMGerber.py:985
+#: flatcamTools/ToolNCC.py:2081 flatcamTools/ToolNCC.py:3132
+#: flatcamTools/ToolNCC.py:3511
+msgid "Isolation geometry could not be generated."
+msgstr "Isolation geometry could not be generated."
+
+#: flatcamObjects/FlatCAMGerber.py:931 flatcamObjects/FlatCAMGerber.py:1063
+msgid "Isolation geometry created"
+msgstr "Isolation geometry created"
+
+#: flatcamObjects/FlatCAMGerber.py:940 flatcamObjects/FlatCAMGerber.py:1070
+msgid "Subtracting Geo"
+msgstr "Subtracting Geo"
+
+#: flatcamObjects/FlatCAMGerber.py:1395
+msgid "Plotting Apertures"
+msgstr "Plotting Apertures"
+
+#: flatcamObjects/FlatCAMObj.py:234
+msgid "Name changed from"
+msgstr "Name changed from"
+
+#: flatcamObjects/FlatCAMObj.py:234
+msgid "to"
+msgstr "to"
+
+#: flatcamObjects/FlatCAMObj.py:245
+msgid "Offsetting..."
+msgstr "Offsetting..."
+
+#: flatcamObjects/FlatCAMObj.py:259 flatcamObjects/FlatCAMObj.py:264
+msgid "Scaling could not be executed."
+msgstr "Scaling could not be executed."
+
+#: flatcamObjects/FlatCAMObj.py:268 flatcamObjects/FlatCAMObj.py:276
+msgid "Scale done."
+msgstr "Scale done."
+
+#: flatcamObjects/FlatCAMObj.py:274
+msgid "Scaling..."
+msgstr "Scaling..."
+
+#: flatcamObjects/FlatCAMObj.py:292
+msgid "Skewing..."
+msgstr "Skewing..."
+
+#: flatcamObjects/FlatCAMScript.py:102
+msgid "Script Editor"
+msgstr "Script Editor"
+
+#: flatcamParsers/ParseExcellon.py:316
+msgid "This is GCODE mark"
+msgstr "This is GCODE mark"
+
+#: flatcamParsers/ParseExcellon.py:433
+msgid ""
+"No tool diameter info's. See shell.\n"
+"A tool change event: T"
+msgstr ""
+"No tool diameter info's. See shell.\n"
+"A tool change event: T"
+
+#: flatcamParsers/ParseExcellon.py:436
+msgid ""
+"was found but the Excellon file have no informations regarding the tool "
+"diameters therefore the application will try to load it by using some 'fake' "
+"diameters.\n"
+"The user needs to edit the resulting Excellon object and change the "
+"diameters to reflect the real diameters."
+msgstr ""
+"was found but the Excellon file have no informations regarding the tool "
+"diameters therefore the application will try to load it by using some 'fake' "
+"diameters.\n"
+"The user needs to edit the resulting Excellon object and change the "
+"diameters to reflect the real diameters."
+
+#: flatcamParsers/ParseExcellon.py:900
+msgid ""
+"Excellon Parser error.\n"
+"Parsing Failed. Line"
+msgstr ""
+"Excellon Parser error.\n"
+"Parsing Failed. Line"
+
+#: flatcamParsers/ParseExcellon.py:982
+msgid ""
+"Excellon.create_geometry() -> a drill location was skipped due of not having "
+"a tool associated.\n"
+"Check the resulting GCode."
+msgstr ""
+"Excellon.create_geometry() -> a drill location was skipped due of not having "
+"a tool associated.\n"
+"Check the resulting GCode."
+
+#: flatcamParsers/ParseFont.py:303
+msgid "Font not supported, try another one."
+msgstr "Font not supported, try another one."
+
+#: flatcamParsers/ParseGerber.py:426
+msgid "Gerber processing. Parsing"
+msgstr "Gerber processing. Parsing"
+
+#: flatcamParsers/ParseGerber.py:426 flatcamParsers/ParseHPGL2.py:178
+msgid "lines"
+msgstr "lines"
+
+#: flatcamParsers/ParseGerber.py:1002 flatcamParsers/ParseGerber.py:1102
+#: flatcamParsers/ParseHPGL2.py:271 flatcamParsers/ParseHPGL2.py:285
+#: flatcamParsers/ParseHPGL2.py:304 flatcamParsers/ParseHPGL2.py:328
+#: flatcamParsers/ParseHPGL2.py:363
+msgid "Coordinates missing, line ignored"
+msgstr "Coordinates missing, line ignored"
+
+#: flatcamParsers/ParseGerber.py:1004 flatcamParsers/ParseGerber.py:1104
+msgid "GERBER file might be CORRUPT. Check the file !!!"
+msgstr "GERBER file might be CORRUPT. Check the file !!!"
+
+#: flatcamParsers/ParseGerber.py:1058
+msgid ""
+"Region does not have enough points. File will be processed but there are "
+"parser errors. Line number"
+msgstr ""
+"Region does not have enough points. File will be processed but there are "
+"parser errors. Line number"
+
+#: flatcamParsers/ParseGerber.py:1488 flatcamParsers/ParseHPGL2.py:398
+msgid "Gerber processing. Joining polygons"
+msgstr "Gerber processing. Joining polygons"
+
+#: flatcamParsers/ParseGerber.py:1505
+msgid "Gerber processing. Applying Gerber polarity."
+msgstr "Gerber processing. Applying Gerber polarity."
+
+#: flatcamParsers/ParseGerber.py:1565
+msgid "Gerber Line"
+msgstr "Gerber Line"
+
+#: flatcamParsers/ParseGerber.py:1565
+msgid "Gerber Line Content"
+msgstr "Gerber Line Content"
+
+#: flatcamParsers/ParseGerber.py:1567
+msgid "Gerber Parser ERROR"
+msgstr "Gerber Parser ERROR"
+
+#: flatcamParsers/ParseGerber.py:1957
+msgid "Gerber Scale done."
+msgstr "Gerber Scale done."
+
+#: flatcamParsers/ParseGerber.py:2049
+msgid "Gerber Offset done."
+msgstr "Gerber Offset done."
+
+#: flatcamParsers/ParseGerber.py:2125
+msgid "Gerber Mirror done."
+msgstr "Gerber Mirror done."
+
+#: flatcamParsers/ParseGerber.py:2199
+msgid "Gerber Skew done."
+msgstr "Gerber Skew done."
+
+#: flatcamParsers/ParseGerber.py:2261
+msgid "Gerber Rotate done."
+msgstr "Gerber Rotate done."
+
+#: flatcamParsers/ParseGerber.py:2418
+msgid "Gerber Buffer done."
+msgstr "Gerber Buffer done."
+
+#: flatcamParsers/ParseHPGL2.py:178
+msgid "HPGL2 processing. Parsing"
+msgstr "HPGL2 processing. Parsing"
+
+#: flatcamParsers/ParseHPGL2.py:410
+msgid "HPGL2 Line"
+msgstr "HPGL2 Line"
+
+#: flatcamParsers/ParseHPGL2.py:410
+msgid "HPGL2 Line Content"
+msgstr "HPGL2 Line Content"
+
+#: flatcamParsers/ParseHPGL2.py:411
+msgid "HPGL2 Parser ERROR"
+msgstr "HPGL2 Parser ERROR"
+
+#: flatcamTools/ToolAlignObjects.py:32
+msgid "Align Objects"
+msgstr "Align Objects"
+
+#: flatcamTools/ToolAlignObjects.py:61
+msgid "MOVING object"
+msgstr "MOVING object"
+
+#: flatcamTools/ToolAlignObjects.py:65
+msgid ""
+"Specify the type of object to be aligned.\n"
+"It can be of type: Gerber or Excellon.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Object combobox."
+msgstr ""
+"Specify the type of object to be aligned.\n"
+"It can be of type: Gerber or Excellon.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Object combobox."
+
+#: flatcamTools/ToolAlignObjects.py:86
+msgid "Object to be aligned."
+msgstr "Object to be aligned."
+
+#: flatcamTools/ToolAlignObjects.py:98
+msgid "TARGET object"
+msgstr "TARGET object"
+
+#: flatcamTools/ToolAlignObjects.py:100
+msgid ""
+"Specify the type of object to be aligned to.\n"
+"It can be of type: Gerber or Excellon.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Object combobox."
+msgstr ""
+"Specify the type of object to be aligned to.\n"
+"It can be of type: Gerber or Excellon.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Object combobox."
+
+#: flatcamTools/ToolAlignObjects.py:122
+msgid "Object to be aligned to. Aligner."
+msgstr "Object to be aligned to. Aligner."
+
+#: flatcamTools/ToolAlignObjects.py:135
+msgid "Alignment Type"
+msgstr "Alignment Type"
+
+#: flatcamTools/ToolAlignObjects.py:137
+msgid ""
+"The type of alignment can be:\n"
+"- Single Point -> it require a single point of sync, the action will be a "
+"translation\n"
+"- Dual Point -> it require two points of sync, the action will be "
+"translation followed by rotation"
+msgstr ""
+"The type of alignment can be:\n"
+"- Single Point -> it require a single point of sync, the action will be a "
+"translation\n"
+"- Dual Point -> it require two points of sync, the action will be "
+"translation followed by rotation"
+
+#: flatcamTools/ToolAlignObjects.py:143
+msgid "Single Point"
+msgstr "Single Point"
+
+#: flatcamTools/ToolAlignObjects.py:144
+msgid "Dual Point"
+msgstr "Dual Point"
+
+#: flatcamTools/ToolAlignObjects.py:159
+msgid "Align Object"
+msgstr "Align Object"
+
+#: flatcamTools/ToolAlignObjects.py:161
+msgid ""
+"Align the specified object to the aligner object.\n"
+"If only one point is used then it assumes translation.\n"
+"If tho points are used it assume translation and rotation."
+msgstr ""
+"Align the specified object to the aligner object.\n"
+"If only one point is used then it assumes translation.\n"
+"If tho points are used it assume translation and rotation."
+
+#: flatcamTools/ToolAlignObjects.py:176 flatcamTools/ToolCalculators.py:246
+#: flatcamTools/ToolCalibration.py:683 flatcamTools/ToolCopperThieving.py:484
+#: flatcamTools/ToolCutOut.py:371 flatcamTools/ToolDblSided.py:471
+#: flatcamTools/ToolExtractDrills.py:310 flatcamTools/ToolFiducials.py:318
+#: flatcamTools/ToolFilm.py:520 flatcamTools/ToolInvertGerber.py:140
+#: flatcamTools/ToolNCC.py:612 flatcamTools/ToolOptimal.py:237
+#: flatcamTools/ToolPaint.py:556 flatcamTools/ToolPanelize.py:267
+#: flatcamTools/ToolPunchGerber.py:339 flatcamTools/ToolQRCode.py:314
+#: flatcamTools/ToolRulesCheck.py:516 flatcamTools/ToolSolderPaste.py:473
+#: flatcamTools/ToolSub.py:176 flatcamTools/ToolTransform.py:398
+msgid "Reset Tool"
+msgstr "Reset Tool"
+
+#: flatcamTools/ToolAlignObjects.py:178 flatcamTools/ToolCalculators.py:248
+#: flatcamTools/ToolCalibration.py:685 flatcamTools/ToolCopperThieving.py:486
+#: flatcamTools/ToolCutOut.py:373 flatcamTools/ToolDblSided.py:473
+#: flatcamTools/ToolExtractDrills.py:312 flatcamTools/ToolFiducials.py:320
+#: flatcamTools/ToolFilm.py:522 flatcamTools/ToolInvertGerber.py:142
+#: flatcamTools/ToolNCC.py:614 flatcamTools/ToolOptimal.py:239
+#: flatcamTools/ToolPaint.py:558 flatcamTools/ToolPanelize.py:269
+#: flatcamTools/ToolPunchGerber.py:341 flatcamTools/ToolQRCode.py:316
+#: flatcamTools/ToolRulesCheck.py:518 flatcamTools/ToolSolderPaste.py:475
+#: flatcamTools/ToolSub.py:178 flatcamTools/ToolTransform.py:400
+msgid "Will reset the tool parameters."
+msgstr "Will reset the tool parameters."
+
+#: flatcamTools/ToolAlignObjects.py:244
+msgid "Align Tool"
+msgstr "Align Tool"
+
+#: flatcamTools/ToolAlignObjects.py:289
+msgid "There is no aligned FlatCAM object selected..."
+msgstr "There is no aligned FlatCAM object selected..."
+
+#: flatcamTools/ToolAlignObjects.py:299
+msgid "There is no aligner FlatCAM object selected..."
+msgstr "There is no aligner FlatCAM object selected..."
+
+#: flatcamTools/ToolAlignObjects.py:325 flatcamTools/ToolAlignObjects.py:385
+msgid "First Point"
+msgstr "First Point"
+
+#: flatcamTools/ToolAlignObjects.py:325 flatcamTools/ToolAlignObjects.py:400
+msgid "Click on the START point."
+msgstr "Click on the START point."
+
+#: flatcamTools/ToolAlignObjects.py:380 flatcamTools/ToolCalibration.py:920
+msgid "Cancelled by user request."
+msgstr "Cancelled by user request."
+
+#: flatcamTools/ToolAlignObjects.py:385 flatcamTools/ToolAlignObjects.py:407
+msgid "Click on the DESTINATION point."
+msgstr "Click on the DESTINATION point."
+
+#: flatcamTools/ToolAlignObjects.py:385 flatcamTools/ToolAlignObjects.py:400
+#: flatcamTools/ToolAlignObjects.py:407
+#| msgid " Or right click to cancel."
+msgid "Or right click to cancel."
+msgstr "Or right click to cancel."
+
+#: flatcamTools/ToolAlignObjects.py:400 flatcamTools/ToolAlignObjects.py:407
+#: flatcamTools/ToolFiducials.py:111
+msgid "Second Point"
+msgstr "Second Point"
+
+#: flatcamTools/ToolCalculators.py:24
+msgid "Calculators"
+msgstr "Calculators"
+
+#: flatcamTools/ToolCalculators.py:26
+msgid "Units Calculator"
+msgstr "Units Calculator"
+
+#: flatcamTools/ToolCalculators.py:70
+msgid "Here you enter the value to be converted from INCH to MM"
+msgstr "Here you enter the value to be converted from INCH to MM"
+
+#: flatcamTools/ToolCalculators.py:75
+msgid "Here you enter the value to be converted from MM to INCH"
+msgstr "Here you enter the value to be converted from MM to INCH"
+
+#: flatcamTools/ToolCalculators.py:111
+msgid ""
+"This is the angle of the tip of the tool.\n"
+"It is specified by manufacturer."
+msgstr ""
+"This is the angle of the tip of the tool.\n"
+"It is specified by manufacturer."
+
+#: flatcamTools/ToolCalculators.py:120
+msgid ""
+"This is the depth to cut into the material.\n"
+"In the CNCJob is the CutZ parameter."
+msgstr ""
+"This is the depth to cut into the material.\n"
+"In the CNCJob is the CutZ parameter."
+
+#: flatcamTools/ToolCalculators.py:128
+msgid ""
+"This is the tool diameter to be entered into\n"
+"FlatCAM Gerber section.\n"
+"In the CNCJob section it is called >Tool dia<."
+msgstr ""
+"This is the tool diameter to be entered into\n"
+"FlatCAM Gerber section.\n"
+"In the CNCJob section it is called >Tool dia<."
+
+#: flatcamTools/ToolCalculators.py:139 flatcamTools/ToolCalculators.py:235
+msgid "Calculate"
+msgstr "Calculate"
+
+#: flatcamTools/ToolCalculators.py:142
+msgid ""
+"Calculate either the Cut Z or the effective tool diameter,\n"
+"  depending on which is desired and which is known. "
+msgstr ""
+"Calculate either the Cut Z or the effective tool diameter,\n"
+"  depending on which is desired and which is known. "
+
+#: flatcamTools/ToolCalculators.py:205
+msgid "Current Value"
+msgstr "Current Value"
+
+#: flatcamTools/ToolCalculators.py:212
+msgid ""
+"This is the current intensity value\n"
+"to be set on the Power Supply. In Amps."
+msgstr ""
+"This is the current intensity value\n"
+"to be set on the Power Supply. In Amps."
+
+#: flatcamTools/ToolCalculators.py:216
+msgid "Time"
+msgstr "Time"
+
+#: flatcamTools/ToolCalculators.py:223
+msgid ""
+"This is the calculated time required for the procedure.\n"
+"In minutes."
+msgstr ""
+"This is the calculated time required for the procedure.\n"
+"In minutes."
+
+#: flatcamTools/ToolCalculators.py:238
+msgid ""
+"Calculate the current intensity value and the procedure time,\n"
+"depending on the parameters above"
+msgstr ""
+"Calculate the current intensity value and the procedure time,\n"
+"depending on the parameters above"
+
+#: flatcamTools/ToolCalculators.py:299
+msgid "Calc. Tool"
+msgstr "Calc. Tool"
+
+#: flatcamTools/ToolCalibration.py:67
+msgid "GCode Parameters"
+msgstr "GCode Parameters"
+
+#: flatcamTools/ToolCalibration.py:69
+msgid "Parameters used when creating the GCode in this tool."
+msgstr "Parameters used when creating the GCode in this tool."
+
+#: flatcamTools/ToolCalibration.py:173
+msgid "STEP 1: Acquire Calibration Points"
+msgstr "STEP 1: Acquire Calibration Points"
+
+#: flatcamTools/ToolCalibration.py:175
+msgid ""
+"Pick four points by clicking on canvas.\n"
+"Those four points should be in the four\n"
+"(as much as possible) corners of the object."
+msgstr ""
+"Pick four points by clicking on canvas.\n"
+"Those four points should be in the four\n"
+"(as much as possible) corners of the object."
+
+#: flatcamTools/ToolCalibration.py:193 flatcamTools/ToolFilm.py:76
+#: flatcamTools/ToolImage.py:54 flatcamTools/ToolPanelize.py:76
+#: flatcamTools/ToolProperties.py:177
+msgid "Object Type"
+msgstr "Object Type"
+
+#: flatcamTools/ToolCalibration.py:210
+msgid "Source object selection"
+msgstr "Source object selection"
+
+#: flatcamTools/ToolCalibration.py:212
+msgid "FlatCAM Object to be used as a source for reference points."
+msgstr "FlatCAM Object to be used as a source for reference points."
+
+#: flatcamTools/ToolCalibration.py:218
+msgid "Calibration Points"
+msgstr "Calibration Points"
+
+#: flatcamTools/ToolCalibration.py:220
+msgid ""
+"Contain the expected calibration points and the\n"
+"ones measured."
+msgstr ""
+"Contain the expected calibration points and the\n"
+"ones measured."
+
+#: flatcamTools/ToolCalibration.py:235 flatcamTools/ToolSub.py:76
+#: flatcamTools/ToolSub.py:131
+msgid "Target"
+msgstr "Target"
+
+#: flatcamTools/ToolCalibration.py:236
+msgid "Found Delta"
+msgstr "Found Delta"
+
+#: flatcamTools/ToolCalibration.py:248
+msgid "Bot Left X"
+msgstr "Bot Left X"
+
+#: flatcamTools/ToolCalibration.py:257
+msgid "Bot Left Y"
+msgstr "Bot Left Y"
+
+#: flatcamTools/ToolCalibration.py:275
+msgid "Bot Right X"
+msgstr "Bot Right X"
+
+#: flatcamTools/ToolCalibration.py:285
+msgid "Bot Right Y"
+msgstr "Bot Right Y"
+
+#: flatcamTools/ToolCalibration.py:300
+msgid "Top Left X"
+msgstr "Top Left X"
+
+#: flatcamTools/ToolCalibration.py:309
+msgid "Top Left Y"
+msgstr "Top Left Y"
+
+#: flatcamTools/ToolCalibration.py:324
+msgid "Top Right X"
+msgstr "Top Right X"
+
+#: flatcamTools/ToolCalibration.py:334
+msgid "Top Right Y"
+msgstr "Top Right Y"
+
+#: flatcamTools/ToolCalibration.py:367
+msgid "Get Points"
+msgstr "Get Points"
+
+#: flatcamTools/ToolCalibration.py:369
+msgid ""
+"Pick four points by clicking on canvas if the source choice\n"
+"is 'free' or inside the object geometry if the source is 'object'.\n"
+"Those four points should be in the four squares of\n"
+"the object."
+msgstr ""
+"Pick four points by clicking on canvas if the source choice\n"
+"is 'free' or inside the object geometry if the source is 'object'.\n"
+"Those four points should be in the four squares of\n"
+"the object."
+
+#: flatcamTools/ToolCalibration.py:390
+msgid "STEP 2: Verification GCode"
+msgstr "STEP 2: Verification GCode"
+
+#: flatcamTools/ToolCalibration.py:392 flatcamTools/ToolCalibration.py:405
+msgid ""
+"Generate GCode file to locate and align the PCB by using\n"
+"the four points acquired above.\n"
+"The points sequence is:\n"
+"- first point -> set the origin\n"
+"- second point -> alignment point. Can be: top-left or bottom-right.\n"
+"- third point -> check point. Can be: top-left or bottom-right.\n"
+"- forth point -> final verification point. Just for evaluation."
+msgstr ""
+"Generate GCode file to locate and align the PCB by using\n"
+"the four points acquired above.\n"
+"The points sequence is:\n"
+"- first point -> set the origin\n"
+"- second point -> alignment point. Can be: top-left or bottom-right.\n"
+"- third point -> check point. Can be: top-left or bottom-right.\n"
+"- forth point -> final verification point. Just for evaluation."
+
+#: flatcamTools/ToolCalibration.py:403 flatcamTools/ToolSolderPaste.py:348
+msgid "Generate GCode"
+msgstr "Generate GCode"
+
+#: flatcamTools/ToolCalibration.py:429
+msgid "STEP 3: Adjustments"
+msgstr "STEP 3: Adjustments"
+
+#: flatcamTools/ToolCalibration.py:431 flatcamTools/ToolCalibration.py:440
+msgid ""
+"Calculate Scale and Skew factors based on the differences (delta)\n"
+"found when checking the PCB pattern. The differences must be filled\n"
+"in the fields Found (Delta)."
+msgstr ""
+"Calculate Scale and Skew factors based on the differences (delta)\n"
+"found when checking the PCB pattern. The differences must be filled\n"
+"in the fields Found (Delta)."
+
+#: flatcamTools/ToolCalibration.py:438
+msgid "Calculate Factors"
+msgstr "Calculate Factors"
+
+#: flatcamTools/ToolCalibration.py:460
+msgid "STEP 4: Adjusted GCode"
+msgstr "STEP 4: Adjusted GCode"
+
+#: flatcamTools/ToolCalibration.py:462
+msgid ""
+"Generate verification GCode file adjusted with\n"
+"the factors above."
+msgstr ""
+"Generate verification GCode file adjusted with\n"
+"the factors above."
+
+#: flatcamTools/ToolCalibration.py:467
+msgid "Scale Factor X:"
+msgstr "Scale Factor X:"
+
+#: flatcamTools/ToolCalibration.py:479
+msgid "Scale Factor Y:"
+msgstr "Scale Factor Y:"
+
+#: flatcamTools/ToolCalibration.py:491
+msgid "Apply Scale Factors"
+msgstr "Apply Scale Factors"
+
+#: flatcamTools/ToolCalibration.py:493
+msgid "Apply Scale factors on the calibration points."
+msgstr "Apply Scale factors on the calibration points."
+
+#: flatcamTools/ToolCalibration.py:503
+msgid "Skew Angle X:"
+msgstr "Skew Angle X:"
+
+#: flatcamTools/ToolCalibration.py:516
+msgid "Skew Angle Y:"
+msgstr "Skew Angle Y:"
+
+#: flatcamTools/ToolCalibration.py:529
+msgid "Apply Skew Factors"
+msgstr "Apply Skew Factors"
+
+#: flatcamTools/ToolCalibration.py:531
+msgid "Apply Skew factors on the calibration points."
+msgstr "Apply Skew factors on the calibration points."
+
+#: flatcamTools/ToolCalibration.py:600
+msgid "Generate Adjusted GCode"
+msgstr "Generate Adjusted GCode"
+
+#: flatcamTools/ToolCalibration.py:602
+msgid ""
+"Generate verification GCode file adjusted with\n"
+"the factors set above.\n"
+"The GCode parameters can be readjusted\n"
+"before clicking this button."
+msgstr ""
+"Generate verification GCode file adjusted with\n"
+"the factors set above.\n"
+"The GCode parameters can be readjusted\n"
+"before clicking this button."
+
+#: flatcamTools/ToolCalibration.py:623
+msgid "STEP 5: Calibrate FlatCAM Objects"
+msgstr "STEP 5: Calibrate FlatCAM Objects"
+
+#: flatcamTools/ToolCalibration.py:625
+msgid ""
+"Adjust the FlatCAM objects\n"
+"with the factors determined and verified above."
+msgstr ""
+"Adjust the FlatCAM objects\n"
+"with the factors determined and verified above."
+
+#: flatcamTools/ToolCalibration.py:637
+msgid "Adjusted object type"
+msgstr "Adjusted object type"
+
+#: flatcamTools/ToolCalibration.py:638
+msgid "Type of the FlatCAM Object to be adjusted."
+msgstr "Type of the FlatCAM Object to be adjusted."
+
+#: flatcamTools/ToolCalibration.py:651
+msgid "Adjusted object selection"
+msgstr "Adjusted object selection"
+
+#: flatcamTools/ToolCalibration.py:653
+msgid "The FlatCAM Object to be adjusted."
+msgstr "The FlatCAM Object to be adjusted."
+
+#: flatcamTools/ToolCalibration.py:660
+msgid "Calibrate"
+msgstr "Calibrate"
+
+#: flatcamTools/ToolCalibration.py:662
+msgid ""
+"Adjust (scale and/or skew) the objects\n"
+"with the factors determined above."
+msgstr ""
+"Adjust (scale and/or skew) the objects\n"
+"with the factors determined above."
+
+#: flatcamTools/ToolCalibration.py:770 flatcamTools/ToolCalibration.py:771
+msgid "Origin"
+msgstr "Origin"
+
+#: flatcamTools/ToolCalibration.py:800
+msgid "Tool initialized"
+msgstr "Tool initialized"
+
+#: flatcamTools/ToolCalibration.py:838
+msgid "There is no source FlatCAM object selected..."
+msgstr "There is no source FlatCAM object selected..."
+
+#: flatcamTools/ToolCalibration.py:859
+msgid "Get First calibration point. Bottom Left..."
+msgstr "Get First calibration point. Bottom Left..."
+
+#: flatcamTools/ToolCalibration.py:926
+msgid "Get Second calibration point. Bottom Right (Top Left)..."
+msgstr "Get Second calibration point. Bottom Right (Top Left)..."
+
+#: flatcamTools/ToolCalibration.py:930
+msgid "Get Third calibration point. Top Left (Bottom Right)..."
+msgstr "Get Third calibration point. Top Left (Bottom Right)..."
+
+#: flatcamTools/ToolCalibration.py:934
+msgid "Get Forth calibration point. Top Right..."
+msgstr "Get Forth calibration point. Top Right..."
+
+#: flatcamTools/ToolCalibration.py:938
+msgid "Done. All four points have been acquired."
+msgstr "Done. All four points have been acquired."
+
+#: flatcamTools/ToolCalibration.py:969
+msgid "Verification GCode for FlatCAM Calibration Tool"
+msgstr "Verification GCode for FlatCAM Calibration Tool"
+
+#: flatcamTools/ToolCalibration.py:981 flatcamTools/ToolCalibration.py:1067
+msgid "Gcode Viewer"
+msgstr "Gcode Viewer"
+
+#: flatcamTools/ToolCalibration.py:997
+msgid "Cancelled. Four points are needed for GCode generation."
+msgstr "Cancelled. Four points are needed for GCode generation."
+
+#: flatcamTools/ToolCalibration.py:1253 flatcamTools/ToolCalibration.py:1349
+msgid "There is no FlatCAM object selected..."
+msgstr "There is no FlatCAM object selected..."
+
+#: flatcamTools/ToolCopperThieving.py:76 flatcamTools/ToolFiducials.py:261
+msgid "Gerber Object to which will be added a copper thieving."
+msgstr "Gerber Object to which will be added a copper thieving."
+
+#: flatcamTools/ToolCopperThieving.py:98
+msgid ""
+"This set the distance between the copper thieving components\n"
+"(the polygon fill may be split in multiple polygons)\n"
+"and the copper traces in the Gerber file."
+msgstr ""
+"This set the distance between the copper thieving components\n"
+"(the polygon fill may be split in multiple polygons)\n"
+"and the copper traces in the Gerber file."
+
+#: flatcamTools/ToolCopperThieving.py:131
+msgid ""
+"- '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."
+msgstr ""
+"- '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."
+
+#: flatcamTools/ToolCopperThieving.py:138 flatcamTools/ToolNCC.py:552
+#: flatcamTools/ToolPaint.py:496
+msgid "Ref. Type"
+msgstr "Ref. Type"
+
+#: flatcamTools/ToolCopperThieving.py:140
+msgid ""
+"The type of FlatCAM object to be used as copper thieving reference.\n"
+"It can be Gerber, Excellon or Geometry."
+msgstr ""
+"The type of FlatCAM object to be used as copper thieving reference.\n"
+"It can be Gerber, Excellon or Geometry."
+
+#: flatcamTools/ToolCopperThieving.py:149 flatcamTools/ToolNCC.py:562
+#: flatcamTools/ToolPaint.py:506
+msgid "Ref. Object"
+msgstr "Ref. Object"
+
+#: flatcamTools/ToolCopperThieving.py:151 flatcamTools/ToolNCC.py:564
+#: flatcamTools/ToolPaint.py:508
+msgid "The FlatCAM object to be used as non copper clearing reference."
+msgstr "The FlatCAM object to be used as non copper clearing reference."
+
+#: flatcamTools/ToolCopperThieving.py:327
+msgid "Insert Copper thieving"
+msgstr "Insert Copper thieving"
+
+#: flatcamTools/ToolCopperThieving.py:329
+msgid ""
+"Will add a polygon (may be split in multiple parts)\n"
+"that will surround the actual Gerber traces at a certain distance."
+msgstr ""
+"Will add a polygon (may be split in multiple parts)\n"
+"that will surround the actual Gerber traces at a certain distance."
+
+#: flatcamTools/ToolCopperThieving.py:388
+msgid "Insert Robber Bar"
+msgstr "Insert Robber Bar"
+
+#: flatcamTools/ToolCopperThieving.py:390
+msgid ""
+"Will add a polygon with a defined thickness\n"
+"that will surround the actual Gerber object\n"
+"at a certain distance.\n"
+"Required when doing holes pattern plating."
+msgstr ""
+"Will add a polygon with a defined thickness\n"
+"that will surround the actual Gerber object\n"
+"at a certain distance.\n"
+"Required when doing holes pattern plating."
+
+#: flatcamTools/ToolCopperThieving.py:414
+msgid "Select Soldermask object"
+msgstr "Select Soldermask object"
+
+#: flatcamTools/ToolCopperThieving.py:416
+msgid ""
+"Gerber Object with the soldermask.\n"
+"It will be used as a base for\n"
+"the pattern plating mask."
+msgstr ""
+"Gerber Object with the soldermask.\n"
+"It will be used as a base for\n"
+"the pattern plating mask."
+
+#: flatcamTools/ToolCopperThieving.py:445
+msgid "Plated area"
+msgstr "Plated area"
+
+#: flatcamTools/ToolCopperThieving.py:447
+msgid ""
+"The area to be plated by pattern plating.\n"
+"Basically is made from the openings in the plating mask.\n"
+"\n"
+"<<WARNING>> - the calculated area is actually a bit larger\n"
+"due of the fact that the soldermask openings are by design\n"
+"a bit larger than the copper pads, and this area is\n"
+"calculated from the soldermask openings."
+msgstr ""
+"The area to be plated by pattern plating.\n"
+"Basically is made from the openings in the plating mask.\n"
+"\n"
+"<<WARNING>> - the calculated area is actually a bit larger\n"
+"due of the fact that the soldermask openings are by design\n"
+"a bit larger than the copper pads, and this area is\n"
+"calculated from the soldermask openings."
+
+#: flatcamTools/ToolCopperThieving.py:458
+msgid "mm"
+msgstr "mm"
+
+#: flatcamTools/ToolCopperThieving.py:460
+msgid "in"
+msgstr "in"
+
+#: flatcamTools/ToolCopperThieving.py:467
+msgid "Generate pattern plating mask"
+msgstr "Generate pattern plating mask"
+
+#: flatcamTools/ToolCopperThieving.py:469
+msgid ""
+"Will add to the soldermask gerber geometry\n"
+"the geometries of the copper thieving and/or\n"
+"the robber bar if those were generated."
+msgstr ""
+"Will add to the soldermask gerber geometry\n"
+"the geometries of the copper thieving and/or\n"
+"the robber bar if those were generated."
+
+#: flatcamTools/ToolCopperThieving.py:625
+#: flatcamTools/ToolCopperThieving.py:650
+msgid "Lines Grid works only for 'itself' reference ..."
+msgstr "Lines Grid works only for 'itself' reference ..."
+
+#: flatcamTools/ToolCopperThieving.py:636
+msgid "Solid fill selected."
+msgstr "Solid fill selected."
+
+#: flatcamTools/ToolCopperThieving.py:641
+msgid "Dots grid fill selected."
+msgstr "Dots grid fill selected."
+
+#: flatcamTools/ToolCopperThieving.py:646
+msgid "Squares grid fill selected."
+msgstr "Squares grid fill selected."
+
+#: flatcamTools/ToolCopperThieving.py:667
+#: flatcamTools/ToolCopperThieving.py:749
+#: flatcamTools/ToolCopperThieving.py:1345 flatcamTools/ToolDblSided.py:657
+#: flatcamTools/ToolExtractDrills.py:436 flatcamTools/ToolFiducials.py:466
+#: flatcamTools/ToolFiducials.py:743 flatcamTools/ToolOptimal.py:342
+#: flatcamTools/ToolPunchGerber.py:512 flatcamTools/ToolQRCode.py:426
+msgid "There is no Gerber object loaded ..."
+msgstr "There is no Gerber object loaded ..."
+
+#: flatcamTools/ToolCopperThieving.py:680
+#: flatcamTools/ToolCopperThieving.py:1273
+msgid "Append geometry"
+msgstr "Append geometry"
+
+#: flatcamTools/ToolCopperThieving.py:724
+#: flatcamTools/ToolCopperThieving.py:1306
+#: flatcamTools/ToolCopperThieving.py:1459
+msgid "Append source file"
+msgstr "Append source file"
+
+#: flatcamTools/ToolCopperThieving.py:732
+#: flatcamTools/ToolCopperThieving.py:1314
+msgid "Copper Thieving Tool done."
+msgstr "Copper Thieving Tool done."
+
+#: flatcamTools/ToolCopperThieving.py:759
+#: flatcamTools/ToolCopperThieving.py:792 flatcamTools/ToolCutOut.py:479
+#: flatcamTools/ToolCutOut.py:666 flatcamTools/ToolInvertGerber.py:208
+#: flatcamTools/ToolNCC.py:1594 flatcamTools/ToolNCC.py:1635
+#: flatcamTools/ToolNCC.py:1664 flatcamTools/ToolPaint.py:1469
+#: flatcamTools/ToolPanelize.py:411 flatcamTools/ToolPanelize.py:426
+#: flatcamTools/ToolSub.py:294 flatcamTools/ToolSub.py:307
+#: flatcamTools/ToolSub.py:498 flatcamTools/ToolSub.py:513
+#: tclCommands/TclCommandCopperClear.py:97 tclCommands/TclCommandPaint.py:99
+msgid "Could not retrieve object"
+msgstr "Could not retrieve object"
+
+#: flatcamTools/ToolCopperThieving.py:769 flatcamTools/ToolNCC.py:1643
+msgid "Click the start point of the area."
+msgstr "Click the start point of the area."
+
+#: flatcamTools/ToolCopperThieving.py:820
+msgid "Click the end point of the filling area."
+msgstr "Click the end point of the filling area."
+
+#: flatcamTools/ToolCopperThieving.py:826 flatcamTools/ToolNCC.py:1705
+#: flatcamTools/ToolNCC.py:1757 flatcamTools/ToolPaint.py:1601
+#: flatcamTools/ToolPaint.py:1652
+msgid "Zone added. Click to start adding next zone or right click to finish."
+msgstr "Zone added. Click to start adding next zone or right click to finish."
+
+#: flatcamTools/ToolCopperThieving.py:942
+#: flatcamTools/ToolCopperThieving.py:946
+#: flatcamTools/ToolCopperThieving.py:1007
+msgid "Thieving"
+msgstr "Thieving"
+
+#: flatcamTools/ToolCopperThieving.py:953
+msgid "Copper Thieving Tool started. Reading parameters."
+msgstr "Copper Thieving Tool started. Reading parameters."
+
+#: flatcamTools/ToolCopperThieving.py:978
+msgid "Copper Thieving Tool. Preparing isolation polygons."
+msgstr "Copper Thieving Tool. Preparing isolation polygons."
+
+#: flatcamTools/ToolCopperThieving.py:1023
+msgid "Copper Thieving Tool. Preparing areas to fill with copper."
+msgstr "Copper Thieving Tool. Preparing areas to fill with copper."
+
+#: flatcamTools/ToolCopperThieving.py:1034 flatcamTools/ToolOptimal.py:349
+#: flatcamTools/ToolPanelize.py:800 flatcamTools/ToolRulesCheck.py:1127
+msgid "Working..."
+msgstr "Working..."
+
+#: flatcamTools/ToolCopperThieving.py:1061
+msgid "Geometry not supported for bounding box"
+msgstr "Geometry not supported for bounding box"
+
+#: flatcamTools/ToolCopperThieving.py:1067 flatcamTools/ToolNCC.py:1928
+#: flatcamTools/ToolNCC.py:1983 flatcamTools/ToolNCC.py:2987
+#: flatcamTools/ToolPaint.py:3375
+msgid "No object available."
+msgstr "No object available."
+
+#: flatcamTools/ToolCopperThieving.py:1104 flatcamTools/ToolNCC.py:1953
+#: flatcamTools/ToolNCC.py:2006 flatcamTools/ToolNCC.py:3029
+msgid "The reference object type is not supported."
+msgstr "The reference object type is not supported."
+
+#: flatcamTools/ToolCopperThieving.py:1109
+msgid "Copper Thieving Tool. Appending new geometry and buffering."
+msgstr "Copper Thieving Tool. Appending new geometry and buffering."
+
+#: flatcamTools/ToolCopperThieving.py:1125
+msgid "Create geometry"
+msgstr "Create geometry"
+
+#: flatcamTools/ToolCopperThieving.py:1325
+#: flatcamTools/ToolCopperThieving.py:1329
+msgid "P-Plating Mask"
+msgstr "P-Plating Mask"
+
+#: flatcamTools/ToolCopperThieving.py:1351
+msgid "Append PP-M geometry"
+msgstr "Append PP-M geometry"
+
+#: flatcamTools/ToolCopperThieving.py:1477
+msgid "Generating Pattern Plating Mask done."
+msgstr "Generating Pattern Plating Mask done."
+
+#: flatcamTools/ToolCopperThieving.py:1549
+msgid "Copper Thieving Tool exit."
+msgstr "Copper Thieving Tool exit."
+
+#: flatcamTools/ToolCutOut.py:41
+msgid "Cutout PCB"
+msgstr "Cutout PCB"
+
+#: flatcamTools/ToolCutOut.py:69 flatcamTools/ToolPanelize.py:52
+msgid "Source Object"
+msgstr "Source Object"
+
+#: flatcamTools/ToolCutOut.py:70
+msgid "Object to be cutout"
+msgstr "Object to be cutout"
+
+#: flatcamTools/ToolCutOut.py:75
+msgid "Kind"
+msgstr "Kind"
+
+#: flatcamTools/ToolCutOut.py:97
+msgid ""
+"Specify the type of object to be cutout.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+msgstr ""
+"Specify the type of object to be cutout.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+
+#: flatcamTools/ToolCutOut.py:121
+msgid "Tool Parameters"
+msgstr "Tool Parameters"
+
+#: flatcamTools/ToolCutOut.py:238
+msgid "A. Automatic Bridge Gaps"
+msgstr "A. Automatic Bridge Gaps"
+
+#: flatcamTools/ToolCutOut.py:240
+msgid "This section handle creation of automatic bridge gaps."
+msgstr "This section handle creation of automatic bridge gaps."
+
+#: flatcamTools/ToolCutOut.py:251
+msgid ""
+"Number of gaps used for the Automatic cutout.\n"
+"There can be maximum 8 bridges/gaps.\n"
+"The choices are:\n"
+"- None  - no gaps\n"
+"- lr    - left + right\n"
+"- tb    - top + bottom\n"
+"- 4     - left + right +top + bottom\n"
+"- 2lr   - 2*left + 2*right\n"
+"- 2tb  - 2*top + 2*bottom\n"
+"- 8     - 2*left + 2*right +2*top + 2*bottom"
+msgstr ""
+"Number of gaps used for the Automatic cutout.\n"
+"There can be maximum 8 bridges/gaps.\n"
+"The choices are:\n"
+"- None  - no gaps\n"
+"- lr    - left + right\n"
+"- tb    - top + bottom\n"
+"- 4     - left + right +top + bottom\n"
+"- 2lr   - 2*left + 2*right\n"
+"- 2tb  - 2*top + 2*bottom\n"
+"- 8     - 2*left + 2*right +2*top + 2*bottom"
+
+#: flatcamTools/ToolCutOut.py:272
+msgid "Generate Freeform Geometry"
+msgstr "Generate Freeform Geometry"
+
+#: flatcamTools/ToolCutOut.py:274
+msgid ""
+"Cutout the selected object.\n"
+"The cutout shape can be of any shape.\n"
+"Useful when the PCB has a non-rectangular shape."
+msgstr ""
+"Cutout the selected object.\n"
+"The cutout shape can be of any shape.\n"
+"Useful when the PCB has a non-rectangular shape."
+
+#: flatcamTools/ToolCutOut.py:286
+msgid "Generate Rectangular Geometry"
+msgstr "Generate Rectangular Geometry"
+
+#: flatcamTools/ToolCutOut.py:288
+msgid ""
+"Cutout the selected object.\n"
+"The resulting cutout shape is\n"
+"always a rectangle shape and it will be\n"
+"the bounding box of the Object."
+msgstr ""
+"Cutout the selected object.\n"
+"The resulting cutout shape is\n"
+"always a rectangle shape and it will be\n"
+"the bounding box of the Object."
+
+#: flatcamTools/ToolCutOut.py:307
+msgid "B. Manual Bridge Gaps"
+msgstr "B. Manual Bridge Gaps"
+
+#: flatcamTools/ToolCutOut.py:309
+msgid ""
+"This section handle creation of manual bridge gaps.\n"
+"This is done by mouse clicking on the perimeter of the\n"
+"Geometry object that is used as a cutout object. "
+msgstr ""
+"This section handle creation of manual bridge gaps.\n"
+"This is done by mouse clicking on the perimeter of the\n"
+"Geometry object that is used as a cutout object. "
+
+#: flatcamTools/ToolCutOut.py:328
+msgid "Geometry object used to create the manual cutout."
+msgstr "Geometry object used to create the manual cutout."
+
+#: flatcamTools/ToolCutOut.py:337
+msgid "Generate Manual Geometry"
+msgstr "Generate Manual Geometry"
+
+#: flatcamTools/ToolCutOut.py:339
+msgid ""
+"If the object to be cutout is a Gerber\n"
+"first create a Geometry that surrounds it,\n"
+"to be used as the cutout, if one doesn't exist yet.\n"
+"Select the source Gerber file in the top object combobox."
+msgstr ""
+"If the object to be cutout is a Gerber\n"
+"first create a Geometry that surrounds it,\n"
+"to be used as the cutout, if one doesn't exist yet.\n"
+"Select the source Gerber file in the top object combobox."
+
+#: flatcamTools/ToolCutOut.py:352
+msgid "Manual Add Bridge Gaps"
+msgstr "Manual Add Bridge Gaps"
+
+#: flatcamTools/ToolCutOut.py:354
+msgid ""
+"Use the left mouse button (LMB) click\n"
+"to create a bridge gap to separate the PCB from\n"
+"the surrounding material.\n"
+"The LMB click has to be done on the perimeter of\n"
+"the Geometry object used as a cutout geometry."
+msgstr ""
+"Use the left mouse button (LMB) click\n"
+"to create a bridge gap to separate the PCB from\n"
+"the surrounding material.\n"
+"The LMB click has to be done on the perimeter of\n"
+"the Geometry object used as a cutout geometry."
+
+#: flatcamTools/ToolCutOut.py:484
+msgid ""
+"There is no object selected for Cutout.\n"
+"Select one and try again."
+msgstr ""
+"There is no object selected for Cutout.\n"
+"Select one and try again."
+
+#: flatcamTools/ToolCutOut.py:490 flatcamTools/ToolCutOut.py:675
+#: flatcamTools/ToolCutOut.py:838 flatcamTools/ToolCutOut.py:920
+#: tclCommands/TclCommandGeoCutout.py:184
+msgid "Tool Diameter is zero value. Change it to a positive real number."
+msgstr "Tool Diameter is zero value. Change it to a positive real number."
+
+#: flatcamTools/ToolCutOut.py:504 flatcamTools/ToolCutOut.py:690
+msgid "Number of gaps value is missing. Add it and retry."
+msgstr "Number of gaps value is missing. Add it and retry."
+
+#: flatcamTools/ToolCutOut.py:509 flatcamTools/ToolCutOut.py:694
+msgid ""
+"Gaps value can be only one of: 'None', 'lr', 'tb', '2lr', '2tb', 4 or 8. "
+"Fill in a correct value and retry. "
+msgstr ""
+"Gaps value can be only one of: 'None', 'lr', 'tb', '2lr', '2tb', 4 or 8. "
+"Fill in a correct value and retry. "
+
+#: flatcamTools/ToolCutOut.py:514 flatcamTools/ToolCutOut.py:700
+msgid ""
+"Cutout operation cannot be done on a multi-geo Geometry.\n"
+"Optionally, this Multi-geo Geometry can be converted to Single-geo "
+"Geometry,\n"
+"and after that perform Cutout."
+msgstr ""
+"Cutout operation cannot be done on a multi-geo Geometry.\n"
+"Optionally, this Multi-geo Geometry can be converted to Single-geo "
+"Geometry,\n"
+"and after that perform Cutout."
+
+#: flatcamTools/ToolCutOut.py:649 flatcamTools/ToolCutOut.py:827
+msgid "Any form CutOut operation finished."
+msgstr "Any form CutOut operation finished."
+
+#: flatcamTools/ToolCutOut.py:670 flatcamTools/ToolInvertGerber.py:214
+#: flatcamTools/ToolNCC.py:1598 flatcamTools/ToolPaint.py:1392
+#: flatcamTools/ToolPanelize.py:416 tclCommands/TclCommandBbox.py:71
+#: tclCommands/TclCommandNregions.py:71
+msgid "Object not found"
+msgstr "Object not found"
+
+#: flatcamTools/ToolCutOut.py:813
+msgid "Rectangular cutout with negative margin is not possible."
+msgstr "Rectangular cutout with negative margin is not possible."
+
+#: flatcamTools/ToolCutOut.py:832
+msgid ""
+"Click on the selected geometry object perimeter to create a bridge gap ..."
+msgstr ""
+"Click on the selected geometry object perimeter to create a bridge gap ..."
+
+#: flatcamTools/ToolCutOut.py:849 flatcamTools/ToolCutOut.py:875
+msgid "Could not retrieve Geometry object"
+msgstr "Could not retrieve Geometry object"
+
+#: flatcamTools/ToolCutOut.py:880
+msgid "Geometry object for manual cutout not found"
+msgstr "Geometry object for manual cutout not found"
+
+#: flatcamTools/ToolCutOut.py:890
+msgid "Added manual Bridge Gap."
+msgstr "Added manual Bridge Gap."
+
+#: flatcamTools/ToolCutOut.py:902
+msgid "Could not retrieve Gerber object"
+msgstr "Could not retrieve Gerber object"
+
+#: flatcamTools/ToolCutOut.py:907
+msgid ""
+"There is no Gerber object selected for Cutout.\n"
+"Select one and try again."
+msgstr ""
+"There is no Gerber object selected for Cutout.\n"
+"Select one and try again."
+
+#: flatcamTools/ToolCutOut.py:913
+msgid ""
+"The selected object has to be of Gerber type.\n"
+"Select a Gerber file and try again."
+msgstr ""
+"The selected object has to be of Gerber type.\n"
+"Select a Gerber file and try again."
+
+#: flatcamTools/ToolCutOut.py:948
+msgid "Geometry not supported for cutout"
+msgstr "Geometry not supported for cutout"
+
+#: flatcamTools/ToolCutOut.py:1006
+msgid "Making manual bridge gap..."
+msgstr "Making manual bridge gap..."
+
+#: flatcamTools/ToolDblSided.py:26
+msgid "2-Sided PCB"
+msgstr "2-Sided PCB"
+
+#: flatcamTools/ToolDblSided.py:52
+msgid "Mirror Operation"
+msgstr "Mirror Operation"
+
+#: flatcamTools/ToolDblSided.py:53
+msgid "Objects to be mirrored"
+msgstr "Objects to be mirrored"
+
+#: flatcamTools/ToolDblSided.py:65
+msgid "Gerber to be mirrored"
+msgstr "Gerber to be mirrored"
+
+#: flatcamTools/ToolDblSided.py:69 flatcamTools/ToolDblSided.py:97
+#: flatcamTools/ToolDblSided.py:127
+msgid ""
+"Mirrors (flips) the specified object around \n"
+"the specified axis. Does not create a new \n"
+"object, but modifies it."
+msgstr ""
+"Mirrors (flips) the specified object around \n"
+"the specified axis. Does not create a new \n"
+"object, but modifies it."
+
+#: flatcamTools/ToolDblSided.py:93
+msgid "Excellon Object to be mirrored."
+msgstr "Excellon Object to be mirrored."
+
+#: flatcamTools/ToolDblSided.py:122
+msgid "Geometry Obj to be mirrored."
+msgstr "Geometry Obj to be mirrored."
+
+#: flatcamTools/ToolDblSided.py:158
+msgid "Mirror Parameters"
+msgstr "Mirror Parameters"
+
+#: flatcamTools/ToolDblSided.py:159
+msgid "Parameters for the mirror operation"
+msgstr "Parameters for the mirror operation"
+
+#: flatcamTools/ToolDblSided.py:164
+msgid "Mirror Axis"
+msgstr "Mirror Axis"
+
+#: flatcamTools/ToolDblSided.py:175
+msgid ""
+"The coordinates used as reference for the mirror operation.\n"
+"Can be:\n"
+"- Point -> a set of coordinates (x,y) around which the object is mirrored\n"
+"- Box -> a set of coordinates (x, y) obtained from the center of the\n"
+"bounding box of another object selected below"
+msgstr ""
+"The coordinates used as reference for the mirror operation.\n"
+"Can be:\n"
+"- Point -> a set of coordinates (x,y) around which the object is mirrored\n"
+"- Box -> a set of coordinates (x, y) obtained from the center of the\n"
+"bounding box of another object selected below"
+
+#: flatcamTools/ToolDblSided.py:189
+msgid "Point coordinates"
+msgstr "Point coordinates"
+
+#: flatcamTools/ToolDblSided.py:194
+#| msgid ""
+#| "Add the coordinates in format <b>(x, y)</b> through which the mirroring "
+#| "axis \n"
+#| " selected in 'MIRROR AXIS' pass.\n"
+#| "The (x, y) coordinates are captured by pressing SHIFT key\n"
+#| "and left mouse button click on canvas or you can enter the coordinates "
+#| "manually."
+msgid ""
+"Add the coordinates in format <b>(x, y)</b> through which the mirroring "
+"axis\n"
+" selected in 'MIRROR AXIS' pass.\n"
+"The (x, y) coordinates are captured by pressing SHIFT key\n"
+"and left mouse button click on canvas or you can enter the coordinates "
+"manually."
+msgstr ""
+"Add the coordinates in format <b>(x, y)</b> through which the mirroring "
+"axis\n"
+" selected in 'MIRROR AXIS' pass.\n"
+"The (x, y) coordinates are captured by pressing SHIFT key\n"
+"and left mouse button click on canvas or you can enter the coordinates "
+"manually."
+
+#: flatcamTools/ToolDblSided.py:218
+msgid ""
+"It can be of type: Gerber or Excellon or Geometry.\n"
+"The coordinates of the center of the bounding box are used\n"
+"as reference for mirror operation."
+msgstr ""
+"It can be of type: Gerber or Excellon or Geometry.\n"
+"The coordinates of the center of the bounding box are used\n"
+"as reference for mirror operation."
+
+#: flatcamTools/ToolDblSided.py:252
+msgid "Bounds Values"
+msgstr "Bounds Values"
+
+#: flatcamTools/ToolDblSided.py:254
+msgid ""
+"Select on canvas the object(s)\n"
+"for which to calculate bounds values."
+msgstr ""
+"Select on canvas the object(s)\n"
+"for which to calculate bounds values."
+
+#: flatcamTools/ToolDblSided.py:264
+msgid "X min"
+msgstr "X min"
+
+#: flatcamTools/ToolDblSided.py:266 flatcamTools/ToolDblSided.py:280
+msgid "Minimum location."
+msgstr "Minimum location."
+
+#: flatcamTools/ToolDblSided.py:278
+msgid "Y min"
+msgstr "Y min"
+
+#: flatcamTools/ToolDblSided.py:292
+msgid "X max"
+msgstr "X max"
+
+#: flatcamTools/ToolDblSided.py:294 flatcamTools/ToolDblSided.py:308
+msgid "Maximum location."
+msgstr "Maximum location."
+
+#: flatcamTools/ToolDblSided.py:306
+msgid "Y max"
+msgstr "Y max"
+
+#: flatcamTools/ToolDblSided.py:317
+msgid "Center point coordinates"
+msgstr "Center point coordinates"
+
+#: flatcamTools/ToolDblSided.py:319
+msgid "Centroid"
+msgstr "Centroid"
+
+#: flatcamTools/ToolDblSided.py:321
+msgid ""
+"The center point location for the rectangular\n"
+"bounding shape. Centroid. Format is (x, y)."
+msgstr ""
+"The center point location for the rectangular\n"
+"bounding shape. Centroid. Format is (x, y)."
+
+#: flatcamTools/ToolDblSided.py:330
+msgid "Calculate Bounds Values"
+msgstr "Calculate Bounds Values"
+
+#: flatcamTools/ToolDblSided.py:332
+msgid ""
+"Calculate the enveloping rectangular shape coordinates,\n"
+"for the selection of objects.\n"
+"The envelope shape is parallel with the X, Y axis."
+msgstr ""
+"Calculate the enveloping rectangular shape coordinates,\n"
+"for the selection of objects.\n"
+"The envelope shape is parallel with the X, Y axis."
+
+#: flatcamTools/ToolDblSided.py:352
+msgid "PCB Alignment"
+msgstr "PCB Alignment"
+
+#: flatcamTools/ToolDblSided.py:354 flatcamTools/ToolDblSided.py:456
+msgid ""
+"Creates an Excellon Object containing the\n"
+"specified alignment holes and their mirror\n"
+"images."
+msgstr ""
+"Creates an Excellon Object containing the\n"
+"specified alignment holes and their mirror\n"
+"images."
+
+#: flatcamTools/ToolDblSided.py:361
+msgid "Drill Diameter"
+msgstr "Drill Diameter"
+
+#: flatcamTools/ToolDblSided.py:390 flatcamTools/ToolDblSided.py:397
+msgid ""
+"The reference point used to create the second alignment drill\n"
+"from the first alignment drill, by doing mirror.\n"
+"It can be modified in the Mirror Parameters -> Reference section"
+msgstr ""
+"The reference point used to create the second alignment drill\n"
+"from the first alignment drill, by doing mirror.\n"
+"It can be modified in the Mirror Parameters -> Reference section"
+
+#: flatcamTools/ToolDblSided.py:410
+msgid "Alignment Drill Coordinates"
+msgstr "Alignment Drill Coordinates"
+
+#: flatcamTools/ToolDblSided.py:412
+msgid ""
+"Alignment holes (x1, y1), (x2, y2), ... on one side of the mirror axis. For "
+"each set of (x, y) coordinates\n"
+"entered here, a pair of drills will be created:\n"
+"\n"
+"- one drill at the coordinates from the field\n"
+"- one drill in mirror position over the axis selected above in the 'Align "
+"Axis'."
+msgstr ""
+"Alignment holes (x1, y1), (x2, y2), ... on one side of the mirror axis. For "
+"each set of (x, y) coordinates\n"
+"entered here, a pair of drills will be created:\n"
+"\n"
+"- one drill at the coordinates from the field\n"
+"- one drill in mirror position over the axis selected above in the 'Align "
+"Axis'."
+
+#: flatcamTools/ToolDblSided.py:420
+msgid "Drill coordinates"
+msgstr "Drill coordinates"
+
+#: flatcamTools/ToolDblSided.py:427
+msgid ""
+"Add alignment drill holes coordinates in the format: (x1, y1), (x2, "
+"y2), ... \n"
+"on one side of the alignment axis.\n"
+"\n"
+"The coordinates set can be obtained:\n"
+"- press SHIFT key and left mouse clicking on canvas. Then click Add.\n"
+"- press SHIFT key and left mouse clicking on canvas. Then Ctrl+V in the "
+"field.\n"
+"- press SHIFT key and left mouse clicking on canvas. Then RMB click in the "
+"field and click Paste.\n"
+"- by entering the coords manually in the format: (x1, y1), (x2, y2), ..."
+msgstr ""
+"Add alignment drill holes coordinates in the format: (x1, y1), (x2, "
+"y2), ... \n"
+"on one side of the alignment axis.\n"
+"\n"
+"The coordinates set can be obtained:\n"
+"- press SHIFT key and left mouse clicking on canvas. Then click Add.\n"
+"- press SHIFT key and left mouse clicking on canvas. Then Ctrl+V in the "
+"field.\n"
+"- press SHIFT key and left mouse clicking on canvas. Then RMB click in the "
+"field and click Paste.\n"
+"- by entering the coords manually in the format: (x1, y1), (x2, y2), ..."
+
+#: flatcamTools/ToolDblSided.py:442
+msgid "Delete Last"
+msgstr "Delete Last"
+
+#: flatcamTools/ToolDblSided.py:444
+msgid "Delete the last coordinates tuple in the list."
+msgstr "Delete the last coordinates tuple in the list."
+
+#: flatcamTools/ToolDblSided.py:454
+msgid "Create Excellon Object"
+msgstr "Create Excellon Object"
+
+#: flatcamTools/ToolDblSided.py:541
+msgid "2-Sided Tool"
+msgstr "2-Sided Tool"
+
+#: flatcamTools/ToolDblSided.py:581
+msgid ""
+"'Point' reference is selected and 'Point' coordinates are missing. Add them "
+"and retry."
+msgstr ""
+"'Point' reference is selected and 'Point' coordinates are missing. Add them "
+"and retry."
+
+#: flatcamTools/ToolDblSided.py:600
+msgid "There is no Box reference object loaded. Load one and retry."
+msgstr "There is no Box reference object loaded. Load one and retry."
+
+#: flatcamTools/ToolDblSided.py:612
+msgid "No value or wrong format in Drill Dia entry. Add it and retry."
+msgstr "No value or wrong format in Drill Dia entry. Add it and retry."
+
+#: flatcamTools/ToolDblSided.py:623
+msgid "There are no Alignment Drill Coordinates to use. Add them and retry."
+msgstr "There are no Alignment Drill Coordinates to use. Add them and retry."
+
+#: flatcamTools/ToolDblSided.py:648
+msgid "Excellon object with alignment drills created..."
+msgstr "Excellon object with alignment drills created..."
+
+#: flatcamTools/ToolDblSided.py:661 flatcamTools/ToolDblSided.py:704
+#: flatcamTools/ToolDblSided.py:748
+msgid "Only Gerber, Excellon and Geometry objects can be mirrored."
+msgstr "Only Gerber, Excellon and Geometry objects can be mirrored."
+
+#: flatcamTools/ToolDblSided.py:671 flatcamTools/ToolDblSided.py:715
+msgid ""
+"There are no Point coordinates in the Point field. Add coords and try "
+"again ..."
+msgstr ""
+"There are no Point coordinates in the Point field. Add coords and try "
+"again ..."
+
+#: flatcamTools/ToolDblSided.py:681 flatcamTools/ToolDblSided.py:725
+#: flatcamTools/ToolDblSided.py:762
+msgid "There is no Box object loaded ..."
+msgstr "There is no Box object loaded ..."
+
+#: flatcamTools/ToolDblSided.py:691 flatcamTools/ToolDblSided.py:735
+#: flatcamTools/ToolDblSided.py:772
+msgid "was mirrored"
+msgstr "was mirrored"
+
+#: flatcamTools/ToolDblSided.py:700 flatcamTools/ToolPunchGerber.py:533
+msgid "There is no Excellon object loaded ..."
+msgstr "There is no Excellon object loaded ..."
+
+#: flatcamTools/ToolDblSided.py:744
+msgid "There is no Geometry object loaded ..."
+msgstr "There is no Geometry object loaded ..."
+
+#: flatcamTools/ToolDistance.py:57 flatcamTools/ToolDistanceMin.py:51
+msgid "Those are the units in which the distance is measured."
+msgstr "Those are the units in which the distance is measured."
+
+#: flatcamTools/ToolDistance.py:58 flatcamTools/ToolDistanceMin.py:52
+msgid "METRIC (mm)"
+msgstr "METRIC (mm)"
+
+#: flatcamTools/ToolDistance.py:58 flatcamTools/ToolDistanceMin.py:52
+msgid "INCH (in)"
+msgstr "INCH (in)"
+
+#: flatcamTools/ToolDistance.py:64
+msgid "Snap to center"
+msgstr "Snap to center"
+
+#: flatcamTools/ToolDistance.py:66
+msgid ""
+"Mouse cursor will snap to the center of the pad/drill\n"
+"when it is hovering over the geometry of the pad/drill."
+msgstr ""
+"Mouse cursor will snap to the center of the pad/drill\n"
+"when it is hovering over the geometry of the pad/drill."
+
+#: flatcamTools/ToolDistance.py:76
+msgid "Start Coords"
+msgstr "Start Coords"
+
+#: flatcamTools/ToolDistance.py:77 flatcamTools/ToolDistance.py:82
+msgid "This is measuring Start point coordinates."
+msgstr "This is measuring Start point coordinates."
+
+#: flatcamTools/ToolDistance.py:87
+msgid "Stop Coords"
+msgstr "Stop Coords"
+
+#: flatcamTools/ToolDistance.py:88 flatcamTools/ToolDistance.py:93
+msgid "This is the measuring Stop point coordinates."
+msgstr "This is the measuring Stop point coordinates."
+
+#: flatcamTools/ToolDistance.py:98 flatcamTools/ToolDistanceMin.py:63
+msgid "Dx"
+msgstr "Dx"
+
+#: flatcamTools/ToolDistance.py:99 flatcamTools/ToolDistance.py:104
+#: flatcamTools/ToolDistanceMin.py:64 flatcamTools/ToolDistanceMin.py:93
+msgid "This is the distance measured over the X axis."
+msgstr "This is the distance measured over the X axis."
+
+#: flatcamTools/ToolDistance.py:109 flatcamTools/ToolDistanceMin.py:66
+msgid "Dy"
+msgstr "Dy"
+
+#: flatcamTools/ToolDistance.py:110 flatcamTools/ToolDistance.py:115
+#: flatcamTools/ToolDistanceMin.py:67 flatcamTools/ToolDistanceMin.py:98
+msgid "This is the distance measured over the Y axis."
+msgstr "This is the distance measured over the Y axis."
+
+#: flatcamTools/ToolDistance.py:121 flatcamTools/ToolDistance.py:126
+#: flatcamTools/ToolDistanceMin.py:70 flatcamTools/ToolDistanceMin.py:103
+msgid "This is orientation angle of the measuring line."
+msgstr "This is orientation angle of the measuring line."
+
+#: flatcamTools/ToolDistance.py:131 flatcamTools/ToolDistanceMin.py:72
+msgid "DISTANCE"
+msgstr "DISTANCE"
+
+#: flatcamTools/ToolDistance.py:132 flatcamTools/ToolDistance.py:137
+msgid "This is the point to point Euclidian distance."
+msgstr "This is the point to point Euclidian distance."
+
+#: flatcamTools/ToolDistance.py:142 flatcamTools/ToolDistance.py:337
+#: flatcamTools/ToolDistanceMin.py:115
+msgid "Measure"
+msgstr "Measure"
+
+#: flatcamTools/ToolDistance.py:272
+msgid "Working"
+msgstr "Working"
+
+#: flatcamTools/ToolDistance.py:277
+msgid "MEASURING: Click on the Start point ..."
+msgstr "MEASURING: Click on the Start point ..."
+
+#: flatcamTools/ToolDistance.py:387
+msgid "Distance Tool finished."
+msgstr "Distance Tool finished."
+
+#: flatcamTools/ToolDistance.py:455
+msgid "Pads overlapped. Aborting."
+msgstr "Pads overlapped. Aborting."
+
+#: flatcamTools/ToolDistance.py:485
+msgid "MEASURING: Click on the Destination point ..."
+msgstr "MEASURING: Click on the Destination point ..."
+
+#: flatcamTools/ToolDistance.py:494 flatcamTools/ToolDistanceMin.py:285
+msgid "MEASURING"
+msgstr "MEASURING"
+
+#: flatcamTools/ToolDistance.py:495 flatcamTools/ToolDistanceMin.py:286
+msgid "Result"
+msgstr "Result"
+
+#: flatcamTools/ToolDistanceMin.py:32 flatcamTools/ToolDistanceMin.py:144
+msgid "Minimum Distance Tool"
+msgstr "Minimum Distance Tool"
+
+#: flatcamTools/ToolDistanceMin.py:55
+msgid "First object point"
+msgstr "First object point"
+
+#: flatcamTools/ToolDistanceMin.py:56 flatcamTools/ToolDistanceMin.py:81
+msgid ""
+"This is first object point coordinates.\n"
+"This is the start point for measuring distance."
+msgstr ""
+"This is first object point coordinates.\n"
+"This is the start point for measuring distance."
+
+#: flatcamTools/ToolDistanceMin.py:59
+msgid "Second object point"
+msgstr "Second object point"
+
+#: flatcamTools/ToolDistanceMin.py:60 flatcamTools/ToolDistanceMin.py:87
+msgid ""
+"This is second object point coordinates.\n"
+"This is the end point for measuring distance."
+msgstr ""
+"This is second object point coordinates.\n"
+"This is the end point for measuring distance."
+
+#: flatcamTools/ToolDistanceMin.py:73 flatcamTools/ToolDistanceMin.py:108
+msgid "This is the point to point Euclidean distance."
+msgstr "This is the point to point Euclidean distance."
+
+#: flatcamTools/ToolDistanceMin.py:75
+msgid "Half Point"
+msgstr "Half Point"
+
+#: flatcamTools/ToolDistanceMin.py:76 flatcamTools/ToolDistanceMin.py:113
+msgid "This is the middle point of the point to point Euclidean distance."
+msgstr "This is the middle point of the point to point Euclidean distance."
+
+#: flatcamTools/ToolDistanceMin.py:118
+msgid "Jump to Half Point"
+msgstr "Jump to Half Point"
+
+#: flatcamTools/ToolDistanceMin.py:155
+msgid ""
+"Select two objects and no more, to measure the distance between them ..."
+msgstr ""
+"Select two objects and no more, to measure the distance between them ..."
+
+#: flatcamTools/ToolDistanceMin.py:196 flatcamTools/ToolDistanceMin.py:217
+#: flatcamTools/ToolDistanceMin.py:226 flatcamTools/ToolDistanceMin.py:247
+msgid "Select two objects and no more. Currently the selection has objects: "
+msgstr "Select two objects and no more. Currently the selection has objects: "
+
+#: flatcamTools/ToolDistanceMin.py:294
+msgid "Objects intersects or touch at"
+msgstr "Objects intersects or touch at"
+
+#: flatcamTools/ToolDistanceMin.py:300
+msgid "Jumped to the half point between the two selected objects"
+msgstr "Jumped to the half point between the two selected objects"
+
+#: flatcamTools/ToolExtractDrills.py:29 flatcamTools/ToolExtractDrills.py:295
+msgid "Extract Drills"
+msgstr "Extract Drills"
+
+#: flatcamTools/ToolExtractDrills.py:62
+msgid "Gerber from which to extract drill holes"
+msgstr "Gerber from which to extract drill holes"
+
+#: flatcamTools/ToolExtractDrills.py:297
+msgid "Extract drills from a given Gerber file."
+msgstr "Extract drills from a given Gerber file."
+
+#: flatcamTools/ToolExtractDrills.py:478 flatcamTools/ToolExtractDrills.py:563
+#: flatcamTools/ToolExtractDrills.py:648
+msgid "No drills extracted. Try different parameters."
+msgstr "No drills extracted. Try different parameters."
+
+#: flatcamTools/ToolFiducials.py:56
+msgid "Fiducials Coordinates"
+msgstr "Fiducials Coordinates"
+
+#: flatcamTools/ToolFiducials.py:58
+msgid ""
+"A table with the fiducial points coordinates,\n"
+"in the format (x, y)."
+msgstr ""
+"A table with the fiducial points coordinates,\n"
+"in the format (x, y)."
+
+#: flatcamTools/ToolFiducials.py:99
+msgid "Top Right"
+msgstr "Top Right"
+
+#: flatcamTools/ToolFiducials.py:191
+msgid ""
+"- 'Auto' - automatic placement of fiducials in the corners of the bounding "
+"box.\n"
+" - 'Manual' - manual placement of fiducials."
+msgstr ""
+"- 'Auto' - automatic placement of fiducials in the corners of the bounding "
+"box.\n"
+" - 'Manual' - manual placement of fiducials."
+
+#: flatcamTools/ToolFiducials.py:259
+msgid "Copper Gerber"
+msgstr "Copper Gerber"
+
+#: flatcamTools/ToolFiducials.py:268
+msgid "Add Fiducial"
+msgstr "Add Fiducial"
+
+#: flatcamTools/ToolFiducials.py:270
+msgid "Will add a polygon on the copper layer to serve as fiducial."
+msgstr "Will add a polygon on the copper layer to serve as fiducial."
+
+#: flatcamTools/ToolFiducials.py:286
+msgid "Soldermask Gerber"
+msgstr "Soldermask Gerber"
+
+#: flatcamTools/ToolFiducials.py:288
+msgid "The Soldermask Gerber object."
+msgstr "The Soldermask Gerber object."
+
+#: flatcamTools/ToolFiducials.py:300
+msgid "Add Soldermask Opening"
+msgstr "Add Soldermask Opening"
+
+#: flatcamTools/ToolFiducials.py:302
+msgid ""
+"Will add a polygon on the soldermask layer\n"
+"to serve as fiducial opening.\n"
+"The diameter is always double of the diameter\n"
+"for the copper fiducial."
+msgstr ""
+"Will add a polygon on the soldermask layer\n"
+"to serve as fiducial opening.\n"
+"The diameter is always double of the diameter\n"
+"for the copper fiducial."
+
+#: flatcamTools/ToolFiducials.py:516
+msgid "Click to add first Fiducial. Bottom Left..."
+msgstr "Click to add first Fiducial. Bottom Left..."
+
+#: flatcamTools/ToolFiducials.py:780
+msgid "Click to add the last fiducial. Top Right..."
+msgstr "Click to add the last fiducial. Top Right..."
+
+#: flatcamTools/ToolFiducials.py:785
+msgid "Click to add the second fiducial. Top Left or Bottom Right..."
+msgstr "Click to add the second fiducial. Top Left or Bottom Right..."
+
+#: flatcamTools/ToolFiducials.py:788 flatcamTools/ToolFiducials.py:797
+msgid "Done. All fiducials have been added."
+msgstr "Done. All fiducials have been added."
+
+#: flatcamTools/ToolFiducials.py:874
+msgid "Fiducials Tool exit."
+msgstr "Fiducials Tool exit."
+
+#: flatcamTools/ToolFilm.py:42
+msgid "Film PCB"
+msgstr "Film PCB"
+
+#: flatcamTools/ToolFilm.py:78
+msgid ""
+"Specify the type of object for which to create the film.\n"
+"The object can be of type: Gerber or Geometry.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Film Object combobox."
+msgstr ""
+"Specify the type of object for which to create the film.\n"
+"The object can be of type: Gerber or Geometry.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Film Object combobox."
+
+#: flatcamTools/ToolFilm.py:92
+msgid "Film Object"
+msgstr "Film Object"
+
+#: flatcamTools/ToolFilm.py:94
+msgid "Object for which to create the film."
+msgstr "Object for which to create the film."
+
+#: flatcamTools/ToolFilm.py:115
+msgid ""
+"Specify the type of object to be used as an container for\n"
+"film creation. It can be: Gerber or Geometry type.The selection here decide "
+"the type of objects that will be\n"
+"in the Box Object combobox."
+msgstr ""
+"Specify the type of object to be used as an container for\n"
+"film creation. It can be: Gerber or Geometry type.The selection here decide "
+"the type of objects that will be\n"
+"in the Box Object combobox."
+
+#: flatcamTools/ToolFilm.py:129
+msgid "Box Object"
+msgstr "Box Object"
+
+#: flatcamTools/ToolFilm.py:131
+msgid ""
+"The actual object that is used a container for the\n"
+" selected object for which we create the film.\n"
+"Usually it is the PCB outline but it can be also the\n"
+"same object for which the film is created."
+msgstr ""
+"The actual object that is used a container for the\n"
+" selected object for which we create the film.\n"
+"Usually it is the PCB outline but it can be also the\n"
+"same object for which the film is created."
+
+#: flatcamTools/ToolFilm.py:273
+msgid "Film Parameters"
+msgstr "Film Parameters"
+
+#: flatcamTools/ToolFilm.py:334
+msgid "Punch drill holes"
+msgstr "Punch drill holes"
+
+#: flatcamTools/ToolFilm.py:335
+msgid ""
+"When checked the generated film will have holes in pads when\n"
+"the generated film is positive. This is done to help drilling,\n"
+"when done manually."
+msgstr ""
+"When checked the generated film will have holes in pads when\n"
+"the generated film is positive. This is done to help drilling,\n"
+"when done manually."
+
+#: flatcamTools/ToolFilm.py:353
+msgid "Source"
+msgstr "Source"
+
+#: flatcamTools/ToolFilm.py:355
+msgid ""
+"The punch hole source can be:\n"
+"- Excellon -> an Excellon holes center will serve as reference.\n"
+"- Pad Center -> will try to use the pads center as reference."
+msgstr ""
+"The punch hole source can be:\n"
+"- Excellon -> an Excellon holes center will serve as reference.\n"
+"- Pad Center -> will try to use the pads center as reference."
+
+#: flatcamTools/ToolFilm.py:360
+msgid "Pad center"
+msgstr "Pad center"
+
+#: flatcamTools/ToolFilm.py:365
+msgid "Excellon Obj"
+msgstr "Excellon Obj"
+
+#: flatcamTools/ToolFilm.py:367
+msgid ""
+"Remove the geometry of Excellon from the Film to create the holes in pads."
+msgstr ""
+"Remove the geometry of Excellon from the Film to create the holes in pads."
+
+#: flatcamTools/ToolFilm.py:381
+msgid "Punch Size"
+msgstr "Punch Size"
+
+#: flatcamTools/ToolFilm.py:382
+msgid "The value here will control how big is the punch hole in the pads."
+msgstr "The value here will control how big is the punch hole in the pads."
+
+#: flatcamTools/ToolFilm.py:502
+msgid "Save Film"
+msgstr "Save Film"
+
+#: flatcamTools/ToolFilm.py:504
+msgid ""
+"Create a Film for the selected object, within\n"
+"the specified box. Does not create a new \n"
+" FlatCAM object, but directly save it in the\n"
+"selected format."
+msgstr ""
+"Create a Film for the selected object, within\n"
+"the specified box. Does not create a new \n"
+" FlatCAM object, but directly save it in the\n"
+"selected format."
+
+#: flatcamTools/ToolFilm.py:664
+msgid ""
+"Using the Pad center does not work on Geometry objects. Only a Gerber object "
+"has pads."
+msgstr ""
+"Using the Pad center does not work on Geometry objects. Only a Gerber object "
+"has pads."
+
+#: flatcamTools/ToolFilm.py:674
+msgid "No FlatCAM object selected. Load an object for Film and retry."
+msgstr "No FlatCAM object selected. Load an object for Film and retry."
+
+#: flatcamTools/ToolFilm.py:681
+msgid "No FlatCAM object selected. Load an object for Box and retry."
+msgstr "No FlatCAM object selected. Load an object for Box and retry."
+
+#: flatcamTools/ToolFilm.py:685
+msgid "No FlatCAM object selected."
+msgstr "No FlatCAM object selected."
+
+#: flatcamTools/ToolFilm.py:696
+msgid "Generating Film ..."
+msgstr "Generating Film ..."
+
+#: flatcamTools/ToolFilm.py:745 flatcamTools/ToolFilm.py:749
+msgid "Export positive film"
+msgstr "Export positive film"
+
+#: flatcamTools/ToolFilm.py:782
+msgid ""
+"No Excellon object selected. Load an object for punching reference and retry."
+msgstr ""
+"No Excellon object selected. Load an object for punching reference and retry."
+
+#: flatcamTools/ToolFilm.py:806
+msgid ""
+" Could not generate punched hole film because the punch hole sizeis bigger "
+"than some of the apertures in the Gerber object."
+msgstr ""
+" Could not generate punched hole film because the punch hole sizeis bigger "
+"than some of the apertures in the Gerber object."
+
+#: flatcamTools/ToolFilm.py:818
+msgid ""
+"Could not generate punched hole film because the punch hole sizeis bigger "
+"than some of the apertures in the Gerber object."
+msgstr ""
+"Could not generate punched hole film because the punch hole sizeis bigger "
+"than some of the apertures in the Gerber object."
+
+#: flatcamTools/ToolFilm.py:836
+msgid ""
+"Could not generate punched hole film because the newly created object "
+"geometry is the same as the one in the source object geometry..."
+msgstr ""
+"Could not generate punched hole film because the newly created object "
+"geometry is the same as the one in the source object geometry..."
+
+#: flatcamTools/ToolFilm.py:891 flatcamTools/ToolFilm.py:895
+msgid "Export negative film"
+msgstr "Export negative film"
+
+#: flatcamTools/ToolFilm.py:956 flatcamTools/ToolFilm.py:1139
+#: flatcamTools/ToolPanelize.py:431
+msgid "No object Box. Using instead"
+msgstr "No object Box. Using instead"
+
+#: flatcamTools/ToolFilm.py:1072 flatcamTools/ToolFilm.py:1252
+msgid "Film file exported to"
+msgstr "Film file exported to"
+
+#: flatcamTools/ToolFilm.py:1075 flatcamTools/ToolFilm.py:1255
+msgid "Generating Film ... Please wait."
+msgstr "Generating Film ... Please wait."
+
+#: flatcamTools/ToolImage.py:24
+msgid "Image as Object"
+msgstr "Image as Object"
+
+#: flatcamTools/ToolImage.py:33
+msgid "Image to PCB"
+msgstr "Image to PCB"
+
+#: flatcamTools/ToolImage.py:56
+msgid ""
+"Specify the type of object to create from the image.\n"
+"It can be of type: Gerber or Geometry."
+msgstr ""
+"Specify the type of object to create from the image.\n"
+"It can be of type: Gerber or Geometry."
+
+#: flatcamTools/ToolImage.py:65
+msgid "DPI value"
+msgstr "DPI value"
+
+#: flatcamTools/ToolImage.py:66
+msgid "Specify a DPI value for the image."
+msgstr "Specify a DPI value for the image."
+
+#: flatcamTools/ToolImage.py:72
+msgid "Level of detail"
+msgstr "Level of detail"
+
+#: flatcamTools/ToolImage.py:81
+msgid "Image type"
+msgstr "Image type"
+
+#: flatcamTools/ToolImage.py:83
+msgid ""
+"Choose a method for the image interpretation.\n"
+"B/W means a black & white image. Color means a colored image."
+msgstr ""
+"Choose a method for the image interpretation.\n"
+"B/W means a black & white image. Color means a colored image."
+
+#: flatcamTools/ToolImage.py:92 flatcamTools/ToolImage.py:107
+#: flatcamTools/ToolImage.py:120 flatcamTools/ToolImage.py:133
+msgid "Mask value"
+msgstr "Mask value"
+
+#: flatcamTools/ToolImage.py:94
+msgid ""
+"Mask for monochrome image.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry.\n"
+"0 means no detail and 255 means everything \n"
+"(which is totally black)."
+msgstr ""
+"Mask for monochrome image.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry.\n"
+"0 means no detail and 255 means everything \n"
+"(which is totally black)."
+
+#: flatcamTools/ToolImage.py:109
+msgid ""
+"Mask for RED color.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry."
+msgstr ""
+"Mask for RED color.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry."
+
+#: flatcamTools/ToolImage.py:122
+msgid ""
+"Mask for GREEN color.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry."
+msgstr ""
+"Mask for GREEN color.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry."
+
+#: flatcamTools/ToolImage.py:135
+msgid ""
+"Mask for BLUE color.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry."
+msgstr ""
+"Mask for BLUE color.\n"
+"Takes values between [0 ... 255].\n"
+"Decides the level of details to include\n"
+"in the resulting geometry."
+
+#: flatcamTools/ToolImage.py:143
+msgid "Import image"
+msgstr "Import image"
+
+#: flatcamTools/ToolImage.py:145
+msgid "Open a image of raster type and then import it in FlatCAM."
+msgstr "Open a image of raster type and then import it in FlatCAM."
+
+#: flatcamTools/ToolImage.py:182
+msgid "Image Tool"
+msgstr "Image Tool"
+
+#: flatcamTools/ToolImage.py:234 flatcamTools/ToolImage.py:237
+msgid "Import IMAGE"
+msgstr "Import IMAGE"
+
+#: flatcamTools/ToolImage.py:285
+msgid "Importing Image"
+msgstr "Importing Image"
+
+#: flatcamTools/ToolInvertGerber.py:74
+msgid "Gerber object that will be inverted."
+msgstr "Gerber object that will be inverted."
+
+#: flatcamTools/ToolInvertGerber.py:83
+msgid "Parameters for this tool"
+msgstr "Parameters for this tool"
+
+#: flatcamTools/ToolInvertGerber.py:123
+msgid "Invert Gerber"
+msgstr "Invert Gerber"
+
+#: flatcamTools/ToolInvertGerber.py:125
+msgid ""
+"Will invert the Gerber object: areas that have copper\n"
+"will be empty of copper and previous empty area will be\n"
+"filled with copper."
+msgstr ""
+"Will invert the Gerber object: areas that have copper\n"
+"will be empty of copper and previous empty area will be\n"
+"filled with copper."
+
+#: flatcamTools/ToolInvertGerber.py:184
+msgid "Invert Tool"
+msgstr "Invert Tool"
+
+#: flatcamTools/ToolMove.py:102
+msgid "MOVE: Click on the Start point ..."
+msgstr "MOVE: Click on the Start point ..."
+
+#: flatcamTools/ToolMove.py:113
+msgid "Cancelled. No object(s) to move."
+msgstr "Cancelled. No object(s) to move."
+
+#: flatcamTools/ToolMove.py:140
+msgid "MOVE: Click on the Destination point ..."
+msgstr "MOVE: Click on the Destination point ..."
+
+#: flatcamTools/ToolMove.py:163
+msgid "Moving..."
+msgstr "Moving..."
+
+#: flatcamTools/ToolMove.py:166
+msgid "No object(s) selected."
+msgstr "No object(s) selected."
+
+#: flatcamTools/ToolMove.py:221
+msgid "Error when mouse left click."
+msgstr "Error when mouse left click."
+
+#: flatcamTools/ToolNCC.py:42
+msgid "Non-Copper Clearing"
+msgstr "Non-Copper Clearing"
+
+#: flatcamTools/ToolNCC.py:88
+msgid ""
+"Specify the type of object to be cleared of excess copper.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+msgstr ""
+"Specify the type of object to be cleared of excess copper.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+
+#: flatcamTools/ToolNCC.py:110
+msgid "Object to be cleared of excess copper."
+msgstr "Object to be cleared of excess copper."
+
+#: flatcamTools/ToolNCC.py:122
+msgid ""
+"Tools pool from which the algorithm\n"
+"will pick the ones used for copper clearing."
+msgstr ""
+"Tools pool from which the algorithm\n"
+"will pick the ones used for copper clearing."
+
+#: flatcamTools/ToolNCC.py:138
+msgid ""
+"This is the Tool Number.\n"
+"Non copper clearing will start with the tool with the biggest \n"
+"diameter, continuing until there are no more tools.\n"
+"Only tools that create NCC clearing geometry will still be present\n"
+"in the resulting geometry. This is because with some tools\n"
+"this function will not be able to create painting geometry."
+msgstr ""
+"This is the Tool Number.\n"
+"Non copper clearing will start with the tool with the biggest \n"
+"diameter, continuing until there are no more tools.\n"
+"Only tools that create NCC clearing geometry will still be present\n"
+"in the resulting geometry. This is because with some tools\n"
+"this function will not be able to create painting geometry."
+
+#: flatcamTools/ToolNCC.py:146
+msgid ""
+"Tool Diameter. It's value (in current FlatCAM units)\n"
+"is the cut width into the material."
+msgstr ""
+"Tool Diameter. It's value (in current FlatCAM units)\n"
+"is the cut width into the material."
+
+#: flatcamTools/ToolNCC.py:150
+msgid ""
+"The Tool Type (TT) can be:\n"
+"- Circular with 1 ... 4 teeth -> it is informative only. Being circular,\n"
+"the cut width in material is exactly the tool diameter.\n"
+"- Ball -> informative only and make reference to the Ball type endmill.\n"
+"- V-Shape -> it will disable de Z-Cut parameter in the resulting geometry UI "
+"form\n"
+"and enable two additional UI form fields in the resulting geometry: V-Tip "
+"Dia and\n"
+"V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter "
+"such\n"
+"as the cut width into material will be equal with the value in the Tool "
+"Diameter\n"
+"column of this table.\n"
+"Choosing the 'V-Shape' Tool Type automatically will select the Operation "
+"Type\n"
+"in the resulting geometry as Isolation."
+msgstr ""
+"The Tool Type (TT) can be:\n"
+"- Circular with 1 ... 4 teeth -> it is informative only. Being circular,\n"
+"the cut width in material is exactly the tool diameter.\n"
+"- Ball -> informative only and make reference to the Ball type endmill.\n"
+"- V-Shape -> it will disable de Z-Cut parameter in the resulting geometry UI "
+"form\n"
+"and enable two additional UI form fields in the resulting geometry: V-Tip "
+"Dia and\n"
+"V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter "
+"such\n"
+"as the cut width into material will be equal with the value in the Tool "
+"Diameter\n"
+"column of this table.\n"
+"Choosing the 'V-Shape' Tool Type automatically will select the Operation "
+"Type\n"
+"in the resulting geometry as Isolation."
+
+#: flatcamTools/ToolNCC.py:296 flatcamTools/ToolPaint.py:279
+msgid ""
+"Add a new tool to the Tool Table\n"
+"with the diameter specified above."
+msgstr ""
+"Add a new tool to the Tool Table\n"
+"with the diameter specified above."
+
+#: flatcamTools/ToolNCC.py:318 flatcamTools/ToolPaint.py:301
+#: flatcamTools/ToolSolderPaste.py:130
+msgid ""
+"Delete a selection of tools in the Tool Table\n"
+"by first selecting a row(s) in the Tool Table."
+msgstr ""
+"Delete a selection of tools in the Tool Table\n"
+"by first selecting a row(s) in the Tool Table."
+
+#: flatcamTools/ToolNCC.py:554
+msgid ""
+"The type of FlatCAM object to be used as non copper clearing reference.\n"
+"It can be Gerber, Excellon or Geometry."
+msgstr ""
+"The type of FlatCAM object to be used as non copper clearing reference.\n"
+"It can be Gerber, Excellon or Geometry."
+
+#: flatcamTools/ToolNCC.py:597 flatcamTools/ToolPaint.py:537
+msgid "Generate Geometry"
+msgstr "Generate Geometry"
+
+#: flatcamTools/ToolNCC.py:1420 flatcamTools/ToolPaint.py:1179
+#: flatcamTools/ToolSolderPaste.py:888
+msgid "Please enter a tool diameter to add, in Float format."
+msgstr "Please enter a tool diameter to add, in Float format."
+
+#: flatcamTools/ToolNCC.py:1451 flatcamTools/ToolNCC.py:4008
+#: flatcamTools/ToolPaint.py:1203 flatcamTools/ToolPaint.py:3598
+#: flatcamTools/ToolSolderPaste.py:917
+msgid "Cancelled. Tool already in Tool Table."
+msgstr "Cancelled. Tool already in Tool Table."
+
+#: flatcamTools/ToolNCC.py:1458 flatcamTools/ToolNCC.py:4025
+#: flatcamTools/ToolPaint.py:1208 flatcamTools/ToolPaint.py:3615
+msgid "New tool added to Tool Table."
+msgstr "New tool added to Tool Table."
+
+#: flatcamTools/ToolNCC.py:1502 flatcamTools/ToolPaint.py:1252
+msgid "Tool from Tool Table was edited."
+msgstr "Tool from Tool Table was edited."
+
+#: flatcamTools/ToolNCC.py:1514 flatcamTools/ToolPaint.py:1264
+#: flatcamTools/ToolSolderPaste.py:978
+msgid "Cancelled. New diameter value is already in the Tool Table."
+msgstr "Cancelled. New diameter value is already in the Tool Table."
+
+#: flatcamTools/ToolNCC.py:1566 flatcamTools/ToolPaint.py:1362
+msgid "Delete failed. Select a tool to delete."
+msgstr "Delete failed. Select a tool to delete."
+
+#: flatcamTools/ToolNCC.py:1572 flatcamTools/ToolPaint.py:1368
+msgid "Tool(s) deleted from Tool Table."
+msgstr "Tool(s) deleted from Tool Table."
+
+#: flatcamTools/ToolNCC.py:1614
+msgid "Wrong Tool Dia value format entered, use a number."
+msgstr "Wrong Tool Dia value format entered, use a number."
+
+#: flatcamTools/ToolNCC.py:1623 flatcamTools/ToolPaint.py:1419
+msgid "No selected tools in Tool Table."
+msgstr "No selected tools in Tool Table."
+
+#: flatcamTools/ToolNCC.py:1699 flatcamTools/ToolPaint.py:1595
+msgid "Click the end point of the paint area."
+msgstr "Click the end point of the paint area."
+
+#: flatcamTools/ToolNCC.py:1971 flatcamTools/ToolNCC.py:2959
+msgid "NCC Tool. Preparing non-copper polygons."
+msgstr "NCC Tool. Preparing non-copper polygons."
+
+#: flatcamTools/ToolNCC.py:2030 flatcamTools/ToolNCC.py:3087
+msgid "NCC Tool. Calculate 'empty' area."
+msgstr "NCC Tool. Calculate 'empty' area."
+
+#: flatcamTools/ToolNCC.py:2049 flatcamTools/ToolNCC.py:2155
+#: flatcamTools/ToolNCC.py:2169 flatcamTools/ToolNCC.py:3100
+#: flatcamTools/ToolNCC.py:3205 flatcamTools/ToolNCC.py:3220
+#: flatcamTools/ToolNCC.py:3486 flatcamTools/ToolNCC.py:3587
+#: flatcamTools/ToolNCC.py:3602
+msgid "Buffering finished"
+msgstr "Buffering finished"
+
+#: flatcamTools/ToolNCC.py:2057 flatcamTools/ToolNCC.py:2176
+#: flatcamTools/ToolNCC.py:3108 flatcamTools/ToolNCC.py:3227
+#: flatcamTools/ToolNCC.py:3493 flatcamTools/ToolNCC.py:3609
+msgid "Could not get the extent of the area to be non copper cleared."
+msgstr "Could not get the extent of the area to be non copper cleared."
+
+#: flatcamTools/ToolNCC.py:2084 flatcamTools/ToolNCC.py:2162
+#: flatcamTools/ToolNCC.py:3135 flatcamTools/ToolNCC.py:3212
+#: flatcamTools/ToolNCC.py:3513 flatcamTools/ToolNCC.py:3594
+msgid ""
+"Isolation geometry is broken. Margin is less than isolation tool diameter."
+msgstr ""
+"Isolation geometry is broken. Margin is less than isolation tool diameter."
+
+#: flatcamTools/ToolNCC.py:2179 flatcamTools/ToolNCC.py:3231
+#: flatcamTools/ToolNCC.py:3612
+msgid "The selected object is not suitable for copper clearing."
+msgstr "The selected object is not suitable for copper clearing."
+
+#: flatcamTools/ToolNCC.py:2186 flatcamTools/ToolNCC.py:3238
+msgid "NCC Tool. Finished calculation of 'empty' area."
+msgstr "NCC Tool. Finished calculation of 'empty' area."
+
+#: flatcamTools/ToolNCC.py:2217 flatcamTools/ToolNCC.py:2219
+#: flatcamTools/ToolNCC.py:2911 flatcamTools/ToolNCC.py:2913
+msgid "Non-Copper clearing ..."
+msgstr "Non-Copper clearing ..."
+
+#: flatcamTools/ToolNCC.py:2273 flatcamTools/ToolNCC.py:3055
+msgid ""
+"NCC Tool. Finished non-copper polygons. Normal copper clearing task started."
+msgstr ""
+"NCC Tool. Finished non-copper polygons. Normal copper clearing task started."
+
+#: flatcamTools/ToolNCC.py:2307 flatcamTools/ToolNCC.py:2587
+msgid "NCC Tool failed creating bounding box."
+msgstr "NCC Tool failed creating bounding box."
+
+#: flatcamTools/ToolNCC.py:2321 flatcamTools/ToolNCC.py:2604
+#: flatcamTools/ToolNCC.py:3251 flatcamTools/ToolNCC.py:3637
+msgid "NCC Tool clearing with tool diameter"
+msgstr "NCC Tool clearing with tool diameter"
+
+#: flatcamTools/ToolNCC.py:2321 flatcamTools/ToolNCC.py:2604
+#: flatcamTools/ToolNCC.py:3251 flatcamTools/ToolNCC.py:3637
+msgid "started."
+msgstr "started."
+
+#: flatcamTools/ToolNCC.py:2513 flatcamTools/ToolNCC.py:3412
+msgid ""
+"There is no NCC Geometry in the file.\n"
+"Usually it means that the tool diameter is too big for the painted "
+"geometry.\n"
+"Change the painting parameters and try again."
+msgstr ""
+"There is no NCC Geometry in the file.\n"
+"Usually it means that the tool diameter is too big for the painted "
+"geometry.\n"
+"Change the painting parameters and try again."
+
+#: flatcamTools/ToolNCC.py:2522 flatcamTools/ToolNCC.py:3421
+msgid "NCC Tool clear all done."
+msgstr "NCC Tool clear all done."
+
+#: flatcamTools/ToolNCC.py:2525 flatcamTools/ToolNCC.py:3424
+msgid "NCC Tool clear all done but the copper features isolation is broken for"
+msgstr ""
+"NCC Tool clear all done but the copper features isolation is broken for"
+
+#: flatcamTools/ToolNCC.py:2527 flatcamTools/ToolNCC.py:2812
+#: flatcamTools/ToolNCC.py:3426 flatcamTools/ToolNCC.py:3809
+msgid "tools"
+msgstr "tools"
+
+#: flatcamTools/ToolNCC.py:2808 flatcamTools/ToolNCC.py:3805
+msgid "NCC Tool Rest Machining clear all done."
+msgstr "NCC Tool Rest Machining clear all done."
+
+#: flatcamTools/ToolNCC.py:2811 flatcamTools/ToolNCC.py:3808
+msgid ""
+"NCC Tool Rest Machining clear all done but the copper features isolation is "
+"broken for"
+msgstr ""
+"NCC Tool Rest Machining clear all done but the copper features isolation is "
+"broken for"
+
+#: flatcamTools/ToolNCC.py:2923
+msgid "NCC Tool started. Reading parameters."
+msgstr "NCC Tool started. Reading parameters."
+
+#: flatcamTools/ToolNCC.py:3901
+msgid ""
+"Try to use the Buffering Type = Full in Preferences -> Gerber General. "
+"Reload the Gerber file after this change."
+msgstr ""
+"Try to use the Buffering Type = Full in Preferences -> Gerber General. "
+"Reload the Gerber file after this change."
+
+#: flatcamTools/ToolOptimal.py:79
+msgid "Number of decimals kept for found distances."
+msgstr "Number of decimals kept for found distances."
+
+#: flatcamTools/ToolOptimal.py:87
+msgid "Minimum distance"
+msgstr "Minimum distance"
+
+#: flatcamTools/ToolOptimal.py:88
+msgid "Display minimum distance between copper features."
+msgstr "Display minimum distance between copper features."
+
+#: flatcamTools/ToolOptimal.py:92
+msgid "Determined"
+msgstr "Determined"
+
+#: flatcamTools/ToolOptimal.py:106
+msgid "Occurring"
+msgstr "Occurring"
+
+#: flatcamTools/ToolOptimal.py:107
+msgid "How many times this minimum is found."
+msgstr "How many times this minimum is found."
+
+#: flatcamTools/ToolOptimal.py:113
+msgid "Minimum points coordinates"
+msgstr "Minimum points coordinates"
+
+#: flatcamTools/ToolOptimal.py:114 flatcamTools/ToolOptimal.py:120
+msgid "Coordinates for points where minimum distance was found."
+msgstr "Coordinates for points where minimum distance was found."
+
+#: flatcamTools/ToolOptimal.py:133 flatcamTools/ToolOptimal.py:209
+msgid "Jump to selected position"
+msgstr "Jump to selected position"
+
+#: flatcamTools/ToolOptimal.py:135 flatcamTools/ToolOptimal.py:211
+msgid ""
+"Select a position in the Locations text box and then\n"
+"click this button."
+msgstr ""
+"Select a position in the Locations text box and then\n"
+"click this button."
+
+#: flatcamTools/ToolOptimal.py:143
+msgid "Other distances"
+msgstr "Other distances"
+
+#: flatcamTools/ToolOptimal.py:144
+msgid ""
+"Will display other distances in the Gerber file ordered from\n"
+"the minimum to the maximum, not including the absolute minimum."
+msgstr ""
+"Will display other distances in the Gerber file ordered from\n"
+"the minimum to the maximum, not including the absolute minimum."
+
+#: flatcamTools/ToolOptimal.py:149
+msgid "Other distances points coordinates"
+msgstr "Other distances points coordinates"
+
+#: flatcamTools/ToolOptimal.py:150 flatcamTools/ToolOptimal.py:164
+#: flatcamTools/ToolOptimal.py:171 flatcamTools/ToolOptimal.py:188
+#: flatcamTools/ToolOptimal.py:195
+msgid ""
+"Other distances and the coordinates for points\n"
+"where the distance was found."
+msgstr ""
+"Other distances and the coordinates for points\n"
+"where the distance was found."
+
+#: flatcamTools/ToolOptimal.py:163
+msgid "Gerber distances"
+msgstr "Gerber distances"
+
+#: flatcamTools/ToolOptimal.py:187
+msgid "Points coordinates"
+msgstr "Points coordinates"
+
+#: flatcamTools/ToolOptimal.py:219
+msgid "Find Minimum"
+msgstr "Find Minimum"
+
+#: flatcamTools/ToolOptimal.py:221
+msgid ""
+"Calculate the minimum distance between copper features,\n"
+"this will allow the determination of the right tool to\n"
+"use for isolation or copper clearing."
+msgstr ""
+"Calculate the minimum distance between copper features,\n"
+"this will allow the determination of the right tool to\n"
+"use for isolation or copper clearing."
+
+#: flatcamTools/ToolOptimal.py:346
+msgid "Only Gerber objects can be evaluated."
+msgstr "Only Gerber objects can be evaluated."
+
+#: flatcamTools/ToolOptimal.py:352
+msgid ""
+"Optimal Tool. Started to search for the minimum distance between copper "
+"features."
+msgstr ""
+"Optimal Tool. Started to search for the minimum distance between copper "
+"features."
+
+#: flatcamTools/ToolOptimal.py:362
+msgid "Optimal Tool. Parsing geometry for aperture"
+msgstr "Optimal Tool. Parsing geometry for aperture"
+
+#: flatcamTools/ToolOptimal.py:373
+msgid "Optimal Tool. Creating a buffer for the object geometry."
+msgstr "Optimal Tool. Creating a buffer for the object geometry."
+
+#: flatcamTools/ToolOptimal.py:383
+msgid ""
+"The Gerber object has one Polygon as geometry.\n"
+"There are no distances between geometry elements to be found."
+msgstr ""
+"The Gerber object has one Polygon as geometry.\n"
+"There are no distances between geometry elements to be found."
+
+#: flatcamTools/ToolOptimal.py:388
+msgid ""
+"Optimal Tool. Finding the distances between each two elements. Iterations"
+msgstr ""
+"Optimal Tool. Finding the distances between each two elements. Iterations"
+
+#: flatcamTools/ToolOptimal.py:423
+msgid "Optimal Tool. Finding the minimum distance."
+msgstr "Optimal Tool. Finding the minimum distance."
+
+#: flatcamTools/ToolOptimal.py:439
+msgid "Optimal Tool. Finished successfully."
+msgstr "Optimal Tool. Finished successfully."
+
+#: flatcamTools/ToolPDF.py:157 flatcamTools/ToolPDF.py:161
+msgid "Open PDF"
+msgstr "Open PDF"
+
+#: flatcamTools/ToolPDF.py:164
+msgid "Open PDF cancelled"
+msgstr "Open PDF cancelled"
+
+#: flatcamTools/ToolPDF.py:195
+msgid "Parsing PDF file ..."
+msgstr "Parsing PDF file ..."
+
+#: flatcamTools/ToolPDF.py:278 flatcamTools/ToolPDF.py:353
+#, python-format
+msgid "Rendering PDF layer #%d ..."
+msgstr "Rendering PDF layer #%d ..."
+
+#: flatcamTools/ToolPDF.py:283 flatcamTools/ToolPDF.py:358
+msgid "Open PDF file failed."
+msgstr "Open PDF file failed."
+
+#: flatcamTools/ToolPDF.py:289 flatcamTools/ToolPDF.py:363
+msgid "Rendered"
+msgstr "Rendered"
+
+#: flatcamTools/ToolPaint.py:82
+msgid ""
+"Specify the type of object to be painted.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+msgstr ""
+"Specify the type of object to be painted.\n"
+"It can be of type: Gerber or Geometry.\n"
+"What is selected here will dictate the kind\n"
+"of objects that will populate the 'Object' combobox."
+
+#: flatcamTools/ToolPaint.py:104
+msgid "Object to be painted."
+msgstr "Object to be painted."
+
+#: flatcamTools/ToolPaint.py:117
+msgid ""
+"Tools pool from which the algorithm\n"
+"will pick the ones used for painting."
+msgstr ""
+"Tools pool from which the algorithm\n"
+"will pick the ones used for painting."
+
+#: flatcamTools/ToolPaint.py:134
+msgid ""
+"This is the Tool Number.\n"
+"Painting will start with the tool with the biggest diameter,\n"
+"continuing until there are no more tools.\n"
+"Only tools that create painting geometry will still be present\n"
+"in the resulting geometry. This is because with some tools\n"
+"this function will not be able to create painting geometry."
+msgstr ""
+"This is the Tool Number.\n"
+"Painting will start with the tool with the biggest diameter,\n"
+"continuing until there are no more tools.\n"
+"Only tools that create painting geometry will still be present\n"
+"in the resulting geometry. This is because with some tools\n"
+"this function will not be able to create painting geometry."
+
+#: flatcamTools/ToolPaint.py:146
+msgid ""
+"The Tool Type (TT) can be:<BR>- <B>Circular</B> with 1 ... 4 teeth -> it is "
+"informative only. Being circular, <BR>the cut width in material is exactly "
+"the tool diameter.<BR>- <B>Ball</B> -> informative only and make reference "
+"to the Ball type endmill.<BR>- <B>V-Shape</B> -> it will disable de Z-Cut "
+"parameter in the resulting geometry UI form and enable two additional UI "
+"form fields in the resulting geometry: V-Tip Dia and V-Tip Angle. Adjusting "
+"those two values will adjust the Z-Cut parameter such as the cut width into "
+"material will be equal with the value in the Tool Diameter column of this "
+"table.<BR>Choosing the <B>V-Shape</B> Tool Type automatically will select "
+"the Operation Type in the resulting geometry as Isolation."
+msgstr ""
+"The Tool Type (TT) can be:<BR>- <B>Circular</B> with 1 ... 4 teeth -> it is "
+"informative only. Being circular, <BR>the cut width in material is exactly "
+"the tool diameter.<BR>- <B>Ball</B> -> informative only and make reference "
+"to the Ball type endmill.<BR>- <B>V-Shape</B> -> it will disable de Z-Cut "
+"parameter in the resulting geometry UI form and enable two additional UI "
+"form fields in the resulting geometry: V-Tip Dia and V-Tip Angle. Adjusting "
+"those two values will adjust the Z-Cut parameter such as the cut width into "
+"material will be equal with the value in the Tool Diameter column of this "
+"table.<BR>Choosing the <B>V-Shape</B> Tool Type automatically will select "
+"the Operation Type in the resulting geometry as Isolation."
+
+#: flatcamTools/ToolPaint.py:498
+msgid ""
+"The type of FlatCAM object to be used as paint reference.\n"
+"It can be Gerber, Excellon or Geometry."
+msgstr ""
+"The type of FlatCAM object to be used as paint reference.\n"
+"It can be Gerber, Excellon or Geometry."
+
+#: flatcamTools/ToolPaint.py:539
+msgid ""
+"- '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."
+msgstr ""
+"- '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."
+
+#: flatcamTools/ToolPaint.py:1388
+#, python-format
+msgid "Could not retrieve object: %s"
+msgstr "Could not retrieve object: %s"
+
+#: flatcamTools/ToolPaint.py:1398
+msgid "Can't do Paint on MultiGeo geometries"
+msgstr "Can't do Paint on MultiGeo geometries"
+
+#: flatcamTools/ToolPaint.py:1428
+msgid "Click on a polygon to paint it."
+msgstr "Click on a polygon to paint it."
+
+#: flatcamTools/ToolPaint.py:1448
+msgid "Click the start point of the paint area."
+msgstr "Click the start point of the paint area."
+
+#: flatcamTools/ToolPaint.py:1513
+msgid "Click to add next polygon or right click to start painting."
+msgstr "Click to add next polygon or right click to start painting."
+
+#: flatcamTools/ToolPaint.py:1526
+msgid "Click to add/remove next polygon or right click to start painting."
+msgstr "Click to add/remove next polygon or right click to start painting."
+
+#: flatcamTools/ToolPaint.py:2024
+msgid "Painting polygon with method: lines."
+msgstr "Painting polygon with method: lines."
+
+#: flatcamTools/ToolPaint.py:2036
+msgid "Failed. Painting polygon with method: seed."
+msgstr "Failed. Painting polygon with method: seed."
+
+#: flatcamTools/ToolPaint.py:2047
+msgid "Failed. Painting polygon with method: standard."
+msgstr "Failed. Painting polygon with method: standard."
+
+#: flatcamTools/ToolPaint.py:2063
+msgid "Geometry could not be painted completely"
+msgstr "Geometry could not be painted completely"
+
+#: flatcamTools/ToolPaint.py:2092 flatcamTools/ToolPaint.py:2095
+#: flatcamTools/ToolPaint.py:2103 flatcamTools/ToolPaint.py:2406
+#: flatcamTools/ToolPaint.py:2409 flatcamTools/ToolPaint.py:2417
+#: flatcamTools/ToolPaint.py:2905 flatcamTools/ToolPaint.py:2908
+#: flatcamTools/ToolPaint.py:2914
+msgid "Paint Tool."
+msgstr "Paint Tool."
+
+#: flatcamTools/ToolPaint.py:2092 flatcamTools/ToolPaint.py:2095
+#: flatcamTools/ToolPaint.py:2103
+msgid "Normal painting polygon task started."
+msgstr "Normal painting polygon task started."
+
+#: flatcamTools/ToolPaint.py:2093 flatcamTools/ToolPaint.py:2407
+#: flatcamTools/ToolPaint.py:2906
+msgid "Buffering geometry..."
+msgstr "Buffering geometry..."
+
+#: flatcamTools/ToolPaint.py:2115 flatcamTools/ToolPaint.py:2424
+#: flatcamTools/ToolPaint.py:2922
+msgid "No polygon found."
+msgstr "No polygon found."
+
+#: flatcamTools/ToolPaint.py:2145
+msgid "Painting polygon..."
+msgstr "Painting polygon..."
+
+#: flatcamTools/ToolPaint.py:2155 flatcamTools/ToolPaint.py:2470
+#: flatcamTools/ToolPaint.py:2660 flatcamTools/ToolPaint.py:2968
+#: flatcamTools/ToolPaint.py:3147
+msgid "Painting with tool diameter = "
+msgstr "Painting with tool diameter = "
+
+#: flatcamTools/ToolPaint.py:2156 flatcamTools/ToolPaint.py:2471
+#: flatcamTools/ToolPaint.py:2661 flatcamTools/ToolPaint.py:2969
+#: flatcamTools/ToolPaint.py:3148
+msgid "started"
+msgstr "started"
+
+#: flatcamTools/ToolPaint.py:2181 flatcamTools/ToolPaint.py:2497
+#: flatcamTools/ToolPaint.py:2687 flatcamTools/ToolPaint.py:2995
+#: flatcamTools/ToolPaint.py:3174
+msgid "Margin parameter too big. Tool is not used"
+msgstr "Margin parameter too big. Tool is not used"
+
+#: flatcamTools/ToolPaint.py:2239 flatcamTools/ToolPaint.py:2566
+#: flatcamTools/ToolPaint.py:2744 flatcamTools/ToolPaint.py:3058
+#: flatcamTools/ToolPaint.py:3236
+msgid ""
+"Could not do Paint. Try a different combination of parameters. Or a "
+"different strategy of paint"
+msgstr ""
+"Could not do Paint. Try a different combination of parameters. Or a "
+"different strategy of paint"
+
+#: flatcamTools/ToolPaint.py:2296 flatcamTools/ToolPaint.py:2632
+#: flatcamTools/ToolPaint.py:2801 flatcamTools/ToolPaint.py:3119
+#: flatcamTools/ToolPaint.py:3298
+msgid ""
+"There is no Painting Geometry in the file.\n"
+"Usually it means that the tool diameter is too big for the painted "
+"geometry.\n"
+"Change the painting parameters and try again."
+msgstr ""
+"There is no Painting Geometry in the file.\n"
+"Usually it means that the tool diameter is too big for the painted "
+"geometry.\n"
+"Change the painting parameters and try again."
+
+#: flatcamTools/ToolPaint.py:2319
+msgid "Paint Single failed."
+msgstr "Paint Single failed."
+
+#: flatcamTools/ToolPaint.py:2325
+msgid "Paint Single Done."
+msgstr "Paint Single Done."
+
+#: flatcamTools/ToolPaint.py:2327 flatcamTools/ToolPaint.py:2837
+#: flatcamTools/ToolPaint.py:3334
+msgid "Polygon Paint started ..."
+msgstr "Polygon Paint started ..."
+
+#: flatcamTools/ToolPaint.py:2406 flatcamTools/ToolPaint.py:2409
+#: flatcamTools/ToolPaint.py:2417
+msgid "Paint all polygons task started."
+msgstr "Paint all polygons task started."
+
+#: flatcamTools/ToolPaint.py:2448 flatcamTools/ToolPaint.py:2946
+msgid "Painting polygons..."
+msgstr "Painting polygons..."
+
+#: flatcamTools/ToolPaint.py:2641
+msgid "Paint All Done."
+msgstr "Paint All Done."
+
+#: flatcamTools/ToolPaint.py:2810 flatcamTools/ToolPaint.py:3307
+msgid "Paint All with Rest-Machining done."
+msgstr "Paint All with Rest-Machining done."
+
+#: flatcamTools/ToolPaint.py:2829
+msgid "Paint All failed."
+msgstr "Paint All failed."
+
+#: flatcamTools/ToolPaint.py:2835
+msgid "Paint Poly All Done."
+msgstr "Paint Poly All Done."
+
+#: flatcamTools/ToolPaint.py:2905 flatcamTools/ToolPaint.py:2908
+#: flatcamTools/ToolPaint.py:2914
+msgid "Painting area task started."
+msgstr "Painting area task started."
+
+#: flatcamTools/ToolPaint.py:3128
+msgid "Paint Area Done."
+msgstr "Paint Area Done."
+
+#: flatcamTools/ToolPaint.py:3326
+msgid "Paint Area failed."
+msgstr "Paint Area failed."
+
+#: flatcamTools/ToolPaint.py:3332
+msgid "Paint Poly Area Done."
+msgstr "Paint Poly Area Done."
+
+#: flatcamTools/ToolPanelize.py:32
+msgid "Panelize PCB"
+msgstr "Panelize PCB"
+
+#: flatcamTools/ToolPanelize.py:54
+msgid ""
+"Specify the type of object to be panelized\n"
+"It can be of type: Gerber, Excellon or Geometry.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Object combobox."
+msgstr ""
+"Specify the type of object to be panelized\n"
+"It can be of type: Gerber, Excellon or Geometry.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Object combobox."
+
+#: flatcamTools/ToolPanelize.py:87
+msgid ""
+"Object to be panelized. This means that it will\n"
+"be duplicated in an array of rows and columns."
+msgstr ""
+"Object to be panelized. This means that it will\n"
+"be duplicated in an array of rows and columns."
+
+#: flatcamTools/ToolPanelize.py:100
+msgid "Penelization Reference"
+msgstr "Penelization Reference"
+
+#: flatcamTools/ToolPanelize.py:102
+msgid ""
+"Choose the reference for panelization:\n"
+"- Object = the bounding box of a different object\n"
+"- Bounding Box = the bounding box of the object to be panelized\n"
+"\n"
+"The reference is useful when doing panelization for more than one\n"
+"object. The spacings (really offsets) will be applied in reference\n"
+"to this reference object therefore maintaining the panelized\n"
+"objects in sync."
+msgstr ""
+"Choose the reference for panelization:\n"
+"- Object = the bounding box of a different object\n"
+"- Bounding Box = the bounding box of the object to be panelized\n"
+"\n"
+"The reference is useful when doing panelization for more than one\n"
+"object. The spacings (really offsets) will be applied in reference\n"
+"to this reference object therefore maintaining the panelized\n"
+"objects in sync."
+
+#: flatcamTools/ToolPanelize.py:123
+msgid "Box Type"
+msgstr "Box Type"
+
+#: flatcamTools/ToolPanelize.py:125
+msgid ""
+"Specify the type of object to be used as an container for\n"
+"panelization. It can be: Gerber or Geometry type.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Box Object combobox."
+msgstr ""
+"Specify the type of object to be used as an container for\n"
+"panelization. It can be: Gerber or Geometry type.\n"
+"The selection here decide the type of objects that will be\n"
+"in the Box Object combobox."
+
+#: flatcamTools/ToolPanelize.py:139
+msgid ""
+"The actual object that is used a container for the\n"
+" selected object that is to be panelized."
+msgstr ""
+"The actual object that is used a container for the\n"
+" selected object that is to be panelized."
+
+#: flatcamTools/ToolPanelize.py:145
+msgid "Panel Data"
+msgstr "Panel Data"
+
+#: flatcamTools/ToolPanelize.py:147
+msgid ""
+"This informations will shape the resulting panel.\n"
+"The number of rows and columns will set how many\n"
+"duplicates of the original geometry will be generated.\n"
+"\n"
+"The spacings will set the distance between any two\n"
+"elements of the panel array."
+msgstr ""
+"This informations will shape the resulting panel.\n"
+"The number of rows and columns will set how many\n"
+"duplicates of the original geometry will be generated.\n"
+"\n"
+"The spacings will set the distance between any two\n"
+"elements of the panel array."
+
+#: flatcamTools/ToolPanelize.py:206
+msgid ""
+"Choose the type of object for the panel object:\n"
+"- Geometry\n"
+"- Gerber"
+msgstr ""
+"Choose the type of object for the panel object:\n"
+"- Geometry\n"
+"- Gerber"
+
+#: flatcamTools/ToolPanelize.py:214
+msgid "Constrain panel within"
+msgstr "Constrain panel within"
+
+#: flatcamTools/ToolPanelize.py:250
+msgid "Panelize Object"
+msgstr "Panelize Object"
+
+#: flatcamTools/ToolPanelize.py:252 flatcamTools/ToolRulesCheck.py:501
+msgid ""
+"Panelize the specified object around the specified box.\n"
+"In other words it creates multiple copies of the source object,\n"
+"arranged in a 2D array of rows and columns."
+msgstr ""
+"Panelize the specified object around the specified box.\n"
+"In other words it creates multiple copies of the source object,\n"
+"arranged in a 2D array of rows and columns."
+
+#: flatcamTools/ToolPanelize.py:320
+msgid "Panel. Tool"
+msgstr "Panel. Tool"
+
+#: flatcamTools/ToolPanelize.py:458
+msgid "Columns or Rows are zero value. Change them to a positive integer."
+msgstr "Columns or Rows are zero value. Change them to a positive integer."
+
+#: flatcamTools/ToolPanelize.py:495
+msgid "Generating panel ... "
+msgstr "Generating panel ... "
+
+#: flatcamTools/ToolPanelize.py:775
+msgid "Generating panel ... Adding the Gerber code."
+msgstr "Generating panel ... Adding the Gerber code."
+
+#: flatcamTools/ToolPanelize.py:786
+msgid "Generating panel... Spawning copies"
+msgstr "Generating panel... Spawning copies"
+
+#: flatcamTools/ToolPanelize.py:793
+msgid "Panel done..."
+msgstr "Panel done..."
+
+#: flatcamTools/ToolPanelize.py:796
+#, python-brace-format
+msgid ""
+"{text} Too big for the constrain area. Final panel has {col} columns and "
+"{row} rows"
+msgstr ""
+"{text} Too big for the constrain area. Final panel has {col} columns and "
+"{row} rows"
+
+#: flatcamTools/ToolPanelize.py:805
+msgid "Panel created successfully."
+msgstr "Panel created successfully."
+
+#: flatcamTools/ToolPcbWizard.py:31
+msgid "PcbWizard Import Tool"
+msgstr "PcbWizard Import Tool"
+
+#: flatcamTools/ToolPcbWizard.py:40
+msgid "Import 2-file Excellon"
+msgstr "Import 2-file Excellon"
+
+#: flatcamTools/ToolPcbWizard.py:51
+msgid "Load files"
+msgstr "Load files"
+
+#: flatcamTools/ToolPcbWizard.py:57
+msgid "Excellon file"
+msgstr "Excellon file"
+
+#: flatcamTools/ToolPcbWizard.py:59
+msgid ""
+"Load the Excellon file.\n"
+"Usually it has a .DRL extension"
+msgstr ""
+"Load the Excellon file.\n"
+"Usually it has a .DRL extension"
+
+#: flatcamTools/ToolPcbWizard.py:65
+msgid "INF file"
+msgstr "INF file"
+
+#: flatcamTools/ToolPcbWizard.py:67
+msgid "Load the INF file."
+msgstr "Load the INF file."
+
+#: flatcamTools/ToolPcbWizard.py:79
+msgid "Tool Number"
+msgstr "Tool Number"
+
+#: flatcamTools/ToolPcbWizard.py:81
+msgid "Tool diameter in file units."
+msgstr "Tool diameter in file units."
+
+#: flatcamTools/ToolPcbWizard.py:87
+msgid "Excellon format"
+msgstr "Excellon format"
+
+#: flatcamTools/ToolPcbWizard.py:95
+msgid "Int. digits"
+msgstr "Int. digits"
+
+#: flatcamTools/ToolPcbWizard.py:97
+msgid "The number of digits for the integral part of the coordinates."
+msgstr "The number of digits for the integral part of the coordinates."
+
+#: flatcamTools/ToolPcbWizard.py:104
+msgid "Frac. digits"
+msgstr "Frac. digits"
+
+#: flatcamTools/ToolPcbWizard.py:106
+msgid "The number of digits for the fractional part of the coordinates."
+msgstr "The number of digits for the fractional part of the coordinates."
+
+#: flatcamTools/ToolPcbWizard.py:113
+msgid "No Suppression"
+msgstr "No Suppression"
+
+#: flatcamTools/ToolPcbWizard.py:114
+msgid "Zeros supp."
+msgstr "Zeros supp."
+
+#: flatcamTools/ToolPcbWizard.py:116
+msgid ""
+"The type of zeros suppression used.\n"
+"Can be of type:\n"
+"- LZ = leading zeros are kept\n"
+"- TZ = trailing zeros are kept\n"
+"- No Suppression = no zero suppression"
+msgstr ""
+"The type of zeros suppression used.\n"
+"Can be of type:\n"
+"- LZ = leading zeros are kept\n"
+"- TZ = trailing zeros are kept\n"
+"- No Suppression = no zero suppression"
+
+#: flatcamTools/ToolPcbWizard.py:129
+msgid ""
+"The type of units that the coordinates and tool\n"
+"diameters are using. Can be INCH or MM."
+msgstr ""
+"The type of units that the coordinates and tool\n"
+"diameters are using. Can be INCH or MM."
+
+#: flatcamTools/ToolPcbWizard.py:136
+msgid "Import Excellon"
+msgstr "Import Excellon"
+
+#: flatcamTools/ToolPcbWizard.py:138
+msgid ""
+"Import in FlatCAM 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."
+msgstr ""
+"Import in FlatCAM 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."
+
+#: flatcamTools/ToolPcbWizard.py:197
+msgid "PCBWizard Tool"
+msgstr "PCBWizard Tool"
+
+#: flatcamTools/ToolPcbWizard.py:291 flatcamTools/ToolPcbWizard.py:295
+msgid "Load PcbWizard Excellon file"
+msgstr "Load PcbWizard Excellon file"
+
+#: flatcamTools/ToolPcbWizard.py:314 flatcamTools/ToolPcbWizard.py:318
+msgid "Load PcbWizard INF file"
+msgstr "Load PcbWizard INF file"
+
+#: flatcamTools/ToolPcbWizard.py:366
+msgid ""
+"The INF file does not contain the tool table.\n"
+"Try to open the Excellon file from File -> Open -> Excellon\n"
+"and edit the drill diameters manually."
+msgstr ""
+"The INF file does not contain the tool table.\n"
+"Try to open the Excellon file from File -> Open -> Excellon\n"
+"and edit the drill diameters manually."
+
+#: flatcamTools/ToolPcbWizard.py:387
+msgid "PcbWizard .INF file loaded."
+msgstr "PcbWizard .INF file loaded."
+
+#: flatcamTools/ToolPcbWizard.py:392
+msgid "Main PcbWizard Excellon file loaded."
+msgstr "Main PcbWizard Excellon file loaded."
+
+#: flatcamTools/ToolPcbWizard.py:428
+msgid "Cannot parse file"
+msgstr "Cannot parse file"
+
+#: flatcamTools/ToolPcbWizard.py:452
+msgid "Importing Excellon."
+msgstr "Importing Excellon."
+
+#: flatcamTools/ToolPcbWizard.py:459
+msgid "Import Excellon file failed."
+msgstr "Import Excellon file failed."
+
+#: flatcamTools/ToolPcbWizard.py:467
+msgid "Imported"
+msgstr "Imported"
+
+#: flatcamTools/ToolPcbWizard.py:471
+msgid "Excellon merging is in progress. Please wait..."
+msgstr "Excellon merging is in progress. Please wait..."
+
+#: flatcamTools/ToolPcbWizard.py:474
+msgid "The imported Excellon file is None."
+msgstr "The imported Excellon file is None."
+
+#: flatcamTools/ToolProperties.py:131
+msgid "Object Properties are displayed."
+msgstr "Object Properties are displayed."
+
+#: flatcamTools/ToolProperties.py:136
+msgid "Properties Tool"
+msgstr "Properties Tool"
+
+#: flatcamTools/ToolProperties.py:150
+msgid "TYPE"
+msgstr "TYPE"
+
+#: flatcamTools/ToolProperties.py:151
+msgid "NAME"
+msgstr "NAME"
+
+#: flatcamTools/ToolProperties.py:153
+msgid "Dimensions"
+msgstr "Dimensions"
+
+#: flatcamTools/ToolProperties.py:181
+msgid "Geo Type"
+msgstr "Geo Type"
+
+#: flatcamTools/ToolProperties.py:184
+msgid "Single-Geo"
+msgstr "Single-Geo"
+
+#: flatcamTools/ToolProperties.py:185
+msgid "Multi-Geo"
+msgstr "Multi-Geo"
+
+#: flatcamTools/ToolProperties.py:196
+msgid "Calculating dimensions ... Please wait."
+msgstr "Calculating dimensions ... Please wait."
+
+#: flatcamTools/ToolProperties.py:339 flatcamTools/ToolProperties.py:343
+#: flatcamTools/ToolProperties.py:345
+msgid "Inch"
+msgstr "Inch"
+
+#: flatcamTools/ToolProperties.py:339 flatcamTools/ToolProperties.py:344
+#: flatcamTools/ToolProperties.py:346
+msgid "Metric"
+msgstr "Metric"
+
+#: flatcamTools/ToolProperties.py:421 flatcamTools/ToolProperties.py:486
+msgid "Drills number"
+msgstr "Drills number"
+
+#: flatcamTools/ToolProperties.py:422 flatcamTools/ToolProperties.py:488
+msgid "Slots number"
+msgstr "Slots number"
+
+#: flatcamTools/ToolProperties.py:424
+msgid "Drills total number:"
+msgstr "Drills total number:"
+
+#: flatcamTools/ToolProperties.py:425
+msgid "Slots total number:"
+msgstr "Slots total number:"
+
+#: flatcamTools/ToolProperties.py:452 flatcamTools/ToolProperties.py:455
+#: flatcamTools/ToolProperties.py:458 flatcamTools/ToolProperties.py:483
+msgid "Present"
+msgstr "Present"
+
+#: flatcamTools/ToolProperties.py:453 flatcamTools/ToolProperties.py:484
+msgid "Solid Geometry"
+msgstr "Solid Geometry"
+
+#: flatcamTools/ToolProperties.py:456
+msgid "GCode Text"
+msgstr "GCode Text"
+
+#: flatcamTools/ToolProperties.py:459
+msgid "GCode Geometry"
+msgstr "GCode Geometry"
+
+#: flatcamTools/ToolProperties.py:462
+msgid "Data"
+msgstr "Data"
+
+#: flatcamTools/ToolProperties.py:495
+msgid "Depth of Cut"
+msgstr "Depth of Cut"
+
+#: flatcamTools/ToolProperties.py:507
+msgid "Clearance Height"
+msgstr "Clearance Height"
+
+#: flatcamTools/ToolProperties.py:539
+msgid "Routing time"
+msgstr "Routing time"
+
+#: flatcamTools/ToolProperties.py:546
+msgid "Travelled distance"
+msgstr "Travelled distance"
+
+#: flatcamTools/ToolProperties.py:564
+msgid "Width"
+msgstr "Width"
+
+#: flatcamTools/ToolProperties.py:570 flatcamTools/ToolProperties.py:578
+msgid "Box Area"
+msgstr "Box Area"
+
+#: flatcamTools/ToolProperties.py:573 flatcamTools/ToolProperties.py:581
+msgid "Convex_Hull Area"
+msgstr "Convex_Hull Area"
+
+#: flatcamTools/ToolProperties.py:588 flatcamTools/ToolProperties.py:591
+msgid "Copper Area"
+msgstr "Copper Area"
+
+#: flatcamTools/ToolPunchGerber.py:30 flatcamTools/ToolPunchGerber.py:323
+msgid "Punch Gerber"
+msgstr "Punch Gerber"
+
+#: flatcamTools/ToolPunchGerber.py:65
+msgid "Gerber into which to punch holes"
+msgstr "Gerber into which to punch holes"
+
+#: flatcamTools/ToolPunchGerber.py:85
+msgid "ALL"
+msgstr "ALL"
+
+#: flatcamTools/ToolPunchGerber.py:166
+msgid ""
+"Remove the geometry of Excellon from the Gerber to create the holes in pads."
+msgstr ""
+"Remove the geometry of Excellon from the Gerber to create the holes in pads."
+
+#: flatcamTools/ToolPunchGerber.py:325
+msgid ""
+"Create a Gerber object from the selected object, within\n"
+"the specified box."
+msgstr ""
+"Create a Gerber object from the selected object, within\n"
+"the specified box."
+
+#: flatcamTools/ToolPunchGerber.py:425
+msgid "Punch Tool"
+msgstr "Punch Tool"
+
+#: flatcamTools/ToolPunchGerber.py:599
+msgid "The value of the fixed diameter is 0.0. Aborting."
+msgstr "The value of the fixed diameter is 0.0. Aborting."
+
+#: flatcamTools/ToolPunchGerber.py:607 flatcamTools/ToolPunchGerber.py:619
+#| msgid ""
+#| "Could not generate punched hole Gerber because the punch hole sizeis "
+#| "bigger than some of the apertures in the Gerber object."
+msgid ""
+"Could not generate punched hole Gerber because the punch hole size is bigger "
+"than some of the apertures in the Gerber object."
+msgstr ""
+"Could not generate punched hole Gerber because the punch hole size is bigger "
+"than some of the apertures in the Gerber object."
+
+#: flatcamTools/ToolPunchGerber.py:656
+msgid ""
+"Could not generate punched hole Gerber because the newly created object "
+"geometry is the same as the one in the source object geometry..."
+msgstr ""
+"Could not generate punched hole Gerber because the newly created object "
+"geometry is the same as the one in the source object geometry..."
+
+#: flatcamTools/ToolQRCode.py:80
+msgid "Gerber Object to which the QRCode will be added."
+msgstr "Gerber Object to which the QRCode will be added."
+
+#: flatcamTools/ToolQRCode.py:93
+msgid "QRCode Parameters"
+msgstr "QRCode Parameters"
+
+#: flatcamTools/ToolQRCode.py:95
+msgid "The parameters used to shape the QRCode."
+msgstr "The parameters used to shape the QRCode."
+
+#: flatcamTools/ToolQRCode.py:207
+msgid "Export QRCode"
+msgstr "Export QRCode"
+
+#: flatcamTools/ToolQRCode.py:209
+msgid ""
+"Show a set of controls allowing to export the QRCode\n"
+"to a SVG file or an PNG file."
+msgstr ""
+"Show a set of controls allowing to export the QRCode\n"
+"to a SVG file or an PNG file."
+
+#: flatcamTools/ToolQRCode.py:248
+msgid "Transparent back color"
+msgstr "Transparent back color"
+
+#: flatcamTools/ToolQRCode.py:273
+msgid "Export QRCode SVG"
+msgstr "Export QRCode SVG"
+
+#: flatcamTools/ToolQRCode.py:275
+msgid "Export a SVG file with the QRCode content."
+msgstr "Export a SVG file with the QRCode content."
+
+#: flatcamTools/ToolQRCode.py:286
+msgid "Export QRCode PNG"
+msgstr "Export QRCode PNG"
+
+#: flatcamTools/ToolQRCode.py:288
+msgid "Export a PNG image file with the QRCode content."
+msgstr "Export a PNG image file with the QRCode content."
+
+#: flatcamTools/ToolQRCode.py:299
+msgid "Insert QRCode"
+msgstr "Insert QRCode"
+
+#: flatcamTools/ToolQRCode.py:301
+msgid "Create the QRCode object."
+msgstr "Create the QRCode object."
+
+#: flatcamTools/ToolQRCode.py:415 flatcamTools/ToolQRCode.py:750
+#: flatcamTools/ToolQRCode.py:799
+msgid "Cancelled. There is no QRCode Data in the text box."
+msgstr "Cancelled. There is no QRCode Data in the text box."
+
+#: flatcamTools/ToolQRCode.py:434
+msgid "Generating QRCode geometry"
+msgstr "Generating QRCode geometry"
+
+#: flatcamTools/ToolQRCode.py:474
+msgid "Click on the Destination point ..."
+msgstr "Click on the Destination point ..."
+
+#: flatcamTools/ToolQRCode.py:589
+msgid "QRCode Tool done."
+msgstr "QRCode Tool done."
+
+#: flatcamTools/ToolQRCode.py:782 flatcamTools/ToolQRCode.py:786
+msgid "Export PNG"
+msgstr "Export PNG"
+
+#: flatcamTools/ToolRulesCheck.py:33
+msgid "Check Rules"
+msgstr "Check Rules"
+
+#: flatcamTools/ToolRulesCheck.py:61
+msgid "Gerber Files"
+msgstr "Gerber Files"
+
+#: flatcamTools/ToolRulesCheck.py:63
+msgid "Gerber objects for which to check rules."
+msgstr "Gerber objects for which to check rules."
+
+#: flatcamTools/ToolRulesCheck.py:78
+msgid "Top"
+msgstr "Top"
+
+#: flatcamTools/ToolRulesCheck.py:80
+msgid "The Top Gerber Copper object for which rules are checked."
+msgstr "The Top Gerber Copper object for which rules are checked."
+
+#: flatcamTools/ToolRulesCheck.py:96
+msgid "Bottom"
+msgstr "Bottom"
+
+#: flatcamTools/ToolRulesCheck.py:98
+msgid "The Bottom Gerber Copper object for which rules are checked."
+msgstr "The Bottom Gerber Copper object for which rules are checked."
+
+#: flatcamTools/ToolRulesCheck.py:114
+msgid "SM Top"
+msgstr "SM Top"
+
+#: flatcamTools/ToolRulesCheck.py:116
+msgid "The Top Gerber Solder Mask object for which rules are checked."
+msgstr "The Top Gerber Solder Mask object for which rules are checked."
+
+#: flatcamTools/ToolRulesCheck.py:132
+msgid "SM Bottom"
+msgstr "SM Bottom"
+
+#: flatcamTools/ToolRulesCheck.py:134
+msgid "The Bottom Gerber Solder Mask object for which rules are checked."
+msgstr "The Bottom Gerber Solder Mask object for which rules are checked."
+
+#: flatcamTools/ToolRulesCheck.py:150
+msgid "Silk Top"
+msgstr "Silk Top"
+
+#: flatcamTools/ToolRulesCheck.py:152
+msgid "The Top Gerber Silkscreen object for which rules are checked."
+msgstr "The Top Gerber Silkscreen object for which rules are checked."
+
+#: flatcamTools/ToolRulesCheck.py:168
+msgid "Silk Bottom"
+msgstr "Silk Bottom"
+
+#: flatcamTools/ToolRulesCheck.py:170
+msgid "The Bottom Gerber Silkscreen object for which rules are checked."
+msgstr "The Bottom Gerber Silkscreen object for which rules are checked."
+
+#: flatcamTools/ToolRulesCheck.py:188
+msgid "The Gerber Outline (Cutout) object for which rules are checked."
+msgstr "The Gerber Outline (Cutout) object for which rules are checked."
+
+#: flatcamTools/ToolRulesCheck.py:199
+msgid "Excellon Objects"
+msgstr "Excellon Objects"
+
+#: flatcamTools/ToolRulesCheck.py:201
+msgid "Excellon objects for which to check rules."
+msgstr "Excellon objects for which to check rules."
+
+#: flatcamTools/ToolRulesCheck.py:213
+msgid "Excellon 1"
+msgstr "Excellon 1"
+
+#: flatcamTools/ToolRulesCheck.py:215
+msgid ""
+"Excellon object for which to check rules.\n"
+"Holds the plated holes or a general Excellon file content."
+msgstr ""
+"Excellon object for which to check rules.\n"
+"Holds the plated holes or a general Excellon file content."
+
+#: flatcamTools/ToolRulesCheck.py:232
+msgid "Excellon 2"
+msgstr "Excellon 2"
+
+#: flatcamTools/ToolRulesCheck.py:234
+msgid ""
+"Excellon object for which to check rules.\n"
+"Holds the non-plated holes."
+msgstr ""
+"Excellon object for which to check rules.\n"
+"Holds the non-plated holes."
+
+#: flatcamTools/ToolRulesCheck.py:247
+msgid "All Rules"
+msgstr "All Rules"
+
+#: flatcamTools/ToolRulesCheck.py:249
+msgid "This check/uncheck all the rules below."
+msgstr "This check/uncheck all the rules below."
+
+#: flatcamTools/ToolRulesCheck.py:499
+msgid "Run Rules Check"
+msgstr "Run Rules Check"
+
+#: flatcamTools/ToolRulesCheck.py:1158 flatcamTools/ToolRulesCheck.py:1218
+#: flatcamTools/ToolRulesCheck.py:1255 flatcamTools/ToolRulesCheck.py:1327
+#: flatcamTools/ToolRulesCheck.py:1381 flatcamTools/ToolRulesCheck.py:1419
+#: flatcamTools/ToolRulesCheck.py:1484
+msgid "Value is not valid."
+msgstr "Value is not valid."
+
+#: flatcamTools/ToolRulesCheck.py:1172
+msgid "TOP -> Copper to Copper clearance"
+msgstr "TOP -> Copper to Copper clearance"
+
+#: flatcamTools/ToolRulesCheck.py:1183
+msgid "BOTTOM -> Copper to Copper clearance"
+msgstr "BOTTOM -> Copper to Copper clearance"
+
+#: flatcamTools/ToolRulesCheck.py:1188 flatcamTools/ToolRulesCheck.py:1282
+#: flatcamTools/ToolRulesCheck.py:1446
+msgid ""
+"At least one Gerber object has to be selected for this rule but none is "
+"selected."
+msgstr ""
+"At least one Gerber object has to be selected for this rule but none is "
+"selected."
+
+#: flatcamTools/ToolRulesCheck.py:1224
+msgid ""
+"One of the copper Gerber objects or the Outline Gerber object is not valid."
+msgstr ""
+"One of the copper Gerber objects or the Outline Gerber object is not valid."
+
+#: flatcamTools/ToolRulesCheck.py:1237 flatcamTools/ToolRulesCheck.py:1401
+msgid ""
+"Outline Gerber object presence is mandatory for this rule but it is not "
+"selected."
+msgstr ""
+"Outline Gerber object presence is mandatory for this rule but it is not "
+"selected."
+
+#: flatcamTools/ToolRulesCheck.py:1254 flatcamTools/ToolRulesCheck.py:1281
+msgid "Silk to Silk clearance"
+msgstr "Silk to Silk clearance"
+
+#: flatcamTools/ToolRulesCheck.py:1267
+msgid "TOP -> Silk to Silk clearance"
+msgstr "TOP -> Silk to Silk clearance"
+
+#: flatcamTools/ToolRulesCheck.py:1277
+msgid "BOTTOM -> Silk to Silk clearance"
+msgstr "BOTTOM -> Silk to Silk clearance"
+
+#: flatcamTools/ToolRulesCheck.py:1333
+msgid "One or more of the Gerber objects is not valid."
+msgstr "One or more of the Gerber objects is not valid."
+
+#: flatcamTools/ToolRulesCheck.py:1341
+msgid "TOP -> Silk to Solder Mask Clearance"
+msgstr "TOP -> Silk to Solder Mask Clearance"
+
+#: flatcamTools/ToolRulesCheck.py:1347
+msgid "BOTTOM -> Silk to Solder Mask Clearance"
+msgstr "BOTTOM -> Silk to Solder Mask Clearance"
+
+#: flatcamTools/ToolRulesCheck.py:1351
+msgid ""
+"Both Silk and Solder Mask Gerber objects has to be either both Top or both "
+"Bottom."
+msgstr ""
+"Both Silk and Solder Mask Gerber objects has to be either both Top or both "
+"Bottom."
+
+#: flatcamTools/ToolRulesCheck.py:1387
+msgid ""
+"One of the Silk Gerber objects or the Outline Gerber object is not valid."
+msgstr ""
+"One of the Silk Gerber objects or the Outline Gerber object is not valid."
+
+#: flatcamTools/ToolRulesCheck.py:1431
+msgid "TOP -> Minimum Solder Mask Sliver"
+msgstr "TOP -> Minimum Solder Mask Sliver"
+
+#: flatcamTools/ToolRulesCheck.py:1441
+msgid "BOTTOM -> Minimum Solder Mask Sliver"
+msgstr "BOTTOM -> Minimum Solder Mask Sliver"
+
+#: flatcamTools/ToolRulesCheck.py:1490
+msgid "One of the Copper Gerber objects or the Excellon objects is not valid."
+msgstr "One of the Copper Gerber objects or the Excellon objects is not valid."
+
+#: flatcamTools/ToolRulesCheck.py:1506
+msgid ""
+"Excellon object presence is mandatory for this rule but none is selected."
+msgstr ""
+"Excellon object presence is mandatory for this rule but none is selected."
+
+#: flatcamTools/ToolRulesCheck.py:1579 flatcamTools/ToolRulesCheck.py:1592
+#: flatcamTools/ToolRulesCheck.py:1603 flatcamTools/ToolRulesCheck.py:1616
+msgid "STATUS"
+msgstr "STATUS"
+
+#: flatcamTools/ToolRulesCheck.py:1582 flatcamTools/ToolRulesCheck.py:1606
+msgid "FAILED"
+msgstr "FAILED"
+
+#: flatcamTools/ToolRulesCheck.py:1595 flatcamTools/ToolRulesCheck.py:1619
+msgid "PASSED"
+msgstr "PASSED"
+
+#: flatcamTools/ToolRulesCheck.py:1596 flatcamTools/ToolRulesCheck.py:1620
+msgid "Violations: There are no violations for the current rule."
+msgstr "Violations: There are no violations for the current rule."
+
+#: flatcamTools/ToolShell.py:74 flatcamTools/ToolShell.py:76
+msgid "...processing..."
+msgstr "...processing..."
+
+#: flatcamTools/ToolSolderPaste.py:37
+msgid "Solder Paste Tool"
+msgstr "Solder Paste Tool"
+
+#: flatcamTools/ToolSolderPaste.py:69
+msgid "Gerber Solder paste object.                        "
+msgstr "Gerber Solder paste object.                        "
+
+#: flatcamTools/ToolSolderPaste.py:76
+msgid ""
+"Tools pool from which the algorithm\n"
+"will pick the ones used for dispensing solder paste."
+msgstr ""
+"Tools pool from which the algorithm\n"
+"will pick the ones used for dispensing solder paste."
+
+#: flatcamTools/ToolSolderPaste.py:91
+msgid ""
+"This is the Tool Number.\n"
+"The solder dispensing will start with the tool with the biggest \n"
+"diameter, continuing until there are no more Nozzle tools.\n"
+"If there are no longer tools but there are still pads not covered\n"
+" with solder paste, the app will issue a warning message box."
+msgstr ""
+"This is the Tool Number.\n"
+"The solder dispensing will start with the tool with the biggest \n"
+"diameter, continuing until there are no more Nozzle tools.\n"
+"If there are no longer tools but there are still pads not covered\n"
+" with solder paste, the app will issue a warning message box."
+
+#: flatcamTools/ToolSolderPaste.py:98
+msgid ""
+"Nozzle tool Diameter. It's value (in current FlatCAM units)\n"
+"is the width of the solder paste dispensed."
+msgstr ""
+"Nozzle tool Diameter. It's value (in current FlatCAM units)\n"
+"is the width of the solder paste dispensed."
+
+#: flatcamTools/ToolSolderPaste.py:105
+msgid "New Nozzle Tool"
+msgstr "New Nozzle Tool"
+
+#: flatcamTools/ToolSolderPaste.py:124
+msgid ""
+"Add a new nozzle tool to the Tool Table\n"
+"with the diameter specified above."
+msgstr ""
+"Add a new nozzle tool to the Tool Table\n"
+"with the diameter specified above."
+
+#: flatcamTools/ToolSolderPaste.py:136
+msgid "Generate solder paste dispensing geometry."
+msgstr "Generate solder paste dispensing geometry."
+
+#: flatcamTools/ToolSolderPaste.py:155
+msgid "STEP 1"
+msgstr "STEP 1"
+
+#: flatcamTools/ToolSolderPaste.py:157
+msgid ""
+"First step is to select a number of nozzle tools for usage\n"
+"and then optionally modify the GCode parameters bellow."
+msgstr ""
+"First step is to select a number of nozzle tools for usage\n"
+"and then optionally modify the GCode parameters bellow."
+
+#: flatcamTools/ToolSolderPaste.py:160
+msgid ""
+"Select tools.\n"
+"Modify parameters."
+msgstr ""
+"Select tools.\n"
+"Modify parameters."
+
+#: flatcamTools/ToolSolderPaste.py:280
+msgid ""
+"Feedrate (speed) while moving up vertically\n"
+" to Dispense position (on Z plane)."
+msgstr ""
+"Feedrate (speed) while moving up vertically\n"
+" to Dispense position (on Z plane)."
+
+#: flatcamTools/ToolSolderPaste.py:350
+msgid ""
+"Generate GCode for Solder Paste dispensing\n"
+"on PCB pads."
+msgstr ""
+"Generate GCode for Solder Paste dispensing\n"
+"on PCB pads."
+
+#: flatcamTools/ToolSolderPaste.py:371
+msgid "STEP 2"
+msgstr "STEP 2"
+
+#: flatcamTools/ToolSolderPaste.py:373
+msgid ""
+"Second step is to create a solder paste dispensing\n"
+"geometry out of an Solder Paste Mask Gerber file."
+msgstr ""
+"Second step is to create a solder paste dispensing\n"
+"geometry out of an Solder Paste Mask Gerber file."
+
+#: flatcamTools/ToolSolderPaste.py:390
+msgid "Geo Result"
+msgstr "Geo Result"
+
+#: flatcamTools/ToolSolderPaste.py:392
+msgid ""
+"Geometry Solder Paste object.\n"
+"The name of the object has to end in:\n"
+"'_solderpaste' as a protection."
+msgstr ""
+"Geometry Solder Paste object.\n"
+"The name of the object has to end in:\n"
+"'_solderpaste' as a protection."
+
+#: flatcamTools/ToolSolderPaste.py:401
+msgid "STEP 3"
+msgstr "STEP 3"
+
+#: flatcamTools/ToolSolderPaste.py:403
+msgid ""
+"Third step is to select a solder paste dispensing geometry,\n"
+"and then generate a CNCJob object.\n"
+"\n"
+"REMEMBER: if you want to create a CNCJob with new parameters,\n"
+"first you need to generate a geometry with those new params,\n"
+"and only after that you can generate an updated CNCJob."
+msgstr ""
+"Third step is to select a solder paste dispensing geometry,\n"
+"and then generate a CNCJob object.\n"
+"\n"
+"REMEMBER: if you want to create a CNCJob with new parameters,\n"
+"first you need to generate a geometry with those new params,\n"
+"and only after that you can generate an updated CNCJob."
+
+#: flatcamTools/ToolSolderPaste.py:424
+msgid "CNC Result"
+msgstr "CNC Result"
+
+#: flatcamTools/ToolSolderPaste.py:426
+msgid ""
+"CNCJob Solder paste object.\n"
+"In order to enable the GCode save section,\n"
+"the name of the object has to end in:\n"
+"'_solderpaste' as a protection."
+msgstr ""
+"CNCJob Solder paste object.\n"
+"In order to enable the GCode save section,\n"
+"the name of the object has to end in:\n"
+"'_solderpaste' as a protection."
+
+#: flatcamTools/ToolSolderPaste.py:436
+msgid "View GCode"
+msgstr "View GCode"
+
+#: flatcamTools/ToolSolderPaste.py:438
+msgid ""
+"View the generated GCode for Solder Paste dispensing\n"
+"on PCB pads."
+msgstr ""
+"View the generated GCode for Solder Paste dispensing\n"
+"on PCB pads."
+
+#: flatcamTools/ToolSolderPaste.py:448
+msgid "Save GCode"
+msgstr "Save GCode"
+
+#: flatcamTools/ToolSolderPaste.py:450
+msgid ""
+"Save the generated GCode for Solder Paste dispensing\n"
+"on PCB pads, to a file."
+msgstr ""
+"Save the generated GCode for Solder Paste dispensing\n"
+"on PCB pads, to a file."
+
+#: flatcamTools/ToolSolderPaste.py:460
+msgid "STEP 4"
+msgstr "STEP 4"
+
+#: flatcamTools/ToolSolderPaste.py:462
+msgid ""
+"Fourth step (and last) is to select a CNCJob made from \n"
+"a solder paste dispensing geometry, and then view/save it's GCode."
+msgstr ""
+"Fourth step (and last) is to select a CNCJob made from \n"
+"a solder paste dispensing geometry, and then view/save it's GCode."
+
+#: flatcamTools/ToolSolderPaste.py:922
+msgid "New Nozzle tool added to Tool Table."
+msgstr "New Nozzle tool added to Tool Table."
+
+#: flatcamTools/ToolSolderPaste.py:965
+msgid "Nozzle tool from Tool Table was edited."
+msgstr "Nozzle tool from Tool Table was edited."
+
+#: flatcamTools/ToolSolderPaste.py:1024
+msgid "Delete failed. Select a Nozzle tool to delete."
+msgstr "Delete failed. Select a Nozzle tool to delete."
+
+#: flatcamTools/ToolSolderPaste.py:1030
+msgid "Nozzle tool(s) deleted from Tool Table."
+msgstr "Nozzle tool(s) deleted from Tool Table."
+
+#: flatcamTools/ToolSolderPaste.py:1086
+msgid "No SolderPaste mask Gerber object loaded."
+msgstr "No SolderPaste mask Gerber object loaded."
+
+#: flatcamTools/ToolSolderPaste.py:1104
+msgid "Creating Solder Paste dispensing geometry."
+msgstr "Creating Solder Paste dispensing geometry."
+
+#: flatcamTools/ToolSolderPaste.py:1117
+msgid "No Nozzle tools in the tool table."
+msgstr "No Nozzle tools in the tool table."
+
+#: flatcamTools/ToolSolderPaste.py:1243
+msgid "Cancelled. Empty file, it has no geometry..."
+msgstr "Cancelled. Empty file, it has no geometry..."
+
+#: flatcamTools/ToolSolderPaste.py:1246
+msgid "Solder Paste geometry generated successfully"
+msgstr "Solder Paste geometry generated successfully"
+
+#: flatcamTools/ToolSolderPaste.py:1253
+msgid "Some or all pads have no solder due of inadequate nozzle diameters..."
+msgstr "Some or all pads have no solder due of inadequate nozzle diameters..."
+
+#: flatcamTools/ToolSolderPaste.py:1267
+msgid "Generating Solder Paste dispensing geometry..."
+msgstr "Generating Solder Paste dispensing geometry..."
+
+#: flatcamTools/ToolSolderPaste.py:1287
+msgid "There is no Geometry object available."
+msgstr "There is no Geometry object available."
+
+#: flatcamTools/ToolSolderPaste.py:1292
+msgid "This Geometry can't be processed. NOT a solder_paste_tool geometry."
+msgstr "This Geometry can't be processed. NOT a solder_paste_tool geometry."
+
+#: flatcamTools/ToolSolderPaste.py:1328
+msgid "An internal error has ocurred. See shell.\n"
+msgstr "An internal error has ocurred. See shell.\n"
+
+#: flatcamTools/ToolSolderPaste.py:1393
+msgid "ToolSolderPaste CNCjob created"
+msgstr "ToolSolderPaste CNCjob created"
+
+#: flatcamTools/ToolSolderPaste.py:1412
+msgid "SP GCode Editor"
+msgstr "SP GCode Editor"
+
+#: flatcamTools/ToolSolderPaste.py:1424 flatcamTools/ToolSolderPaste.py:1429
+#: flatcamTools/ToolSolderPaste.py:1484
+msgid ""
+"This CNCJob object can't be processed. NOT a solder_paste_tool CNCJob object."
+msgstr ""
+"This CNCJob object can't be processed. NOT a solder_paste_tool CNCJob object."
+
+#: flatcamTools/ToolSolderPaste.py:1454
+msgid "No Gcode in the object"
+msgstr "No Gcode in the object"
+
+#: flatcamTools/ToolSolderPaste.py:1494
+msgid "Export GCode ..."
+msgstr "Export GCode ..."
+
+#: flatcamTools/ToolSolderPaste.py:1542
+msgid "Solder paste dispenser GCode file saved to"
+msgstr "Solder paste dispenser GCode file saved to"
+
+#: flatcamTools/ToolSub.py:65
+msgid "Gerber Objects"
+msgstr "Gerber Objects"
+
+#: flatcamTools/ToolSub.py:78
+msgid ""
+"Gerber object from which to subtract\n"
+"the subtractor Gerber object."
+msgstr ""
+"Gerber object from which to subtract\n"
+"the subtractor Gerber object."
+
+#: flatcamTools/ToolSub.py:91 flatcamTools/ToolSub.py:146
+msgid "Subtractor"
+msgstr "Subtractor"
+
+#: flatcamTools/ToolSub.py:93
+msgid ""
+"Gerber object that will be subtracted\n"
+"from the target Gerber object."
+msgstr ""
+"Gerber object that will be subtracted\n"
+"from the target Gerber object."
+
+#: flatcamTools/ToolSub.py:100
+msgid "Substract Gerber"
+msgstr "Substract Gerber"
+
+#: flatcamTools/ToolSub.py:102
+msgid ""
+"Will remove the area occupied by the subtractor\n"
+"Gerber from the Target Gerber.\n"
+"Can be used to remove the overlapping silkscreen\n"
+"over the soldermask."
+msgstr ""
+"Will remove the area occupied by the subtractor\n"
+"Gerber from the Target Gerber.\n"
+"Can be used to remove the overlapping silkscreen\n"
+"over the soldermask."
+
+#: flatcamTools/ToolSub.py:120
+msgid "Geometry Objects"
+msgstr "Geometry Objects"
+
+#: flatcamTools/ToolSub.py:133
+msgid ""
+"Geometry object from which to subtract\n"
+"the subtractor Geometry object."
+msgstr ""
+"Geometry object from which to subtract\n"
+"the subtractor Geometry object."
+
+#: flatcamTools/ToolSub.py:148
+msgid ""
+"Geometry object that will be subtracted\n"
+"from the target Geometry object."
+msgstr ""
+"Geometry object that will be subtracted\n"
+"from the target Geometry object."
+
+#: flatcamTools/ToolSub.py:156
+msgid ""
+"Checking this will close the paths cut by the Geometry subtractor object."
+msgstr ""
+"Checking this will close the paths cut by the Geometry subtractor object."
+
+#: flatcamTools/ToolSub.py:159
+msgid "Subtract Geometry"
+msgstr "Subtract Geometry"
+
+#: flatcamTools/ToolSub.py:161
+msgid ""
+"Will remove the area occupied by the subtractor\n"
+"Geometry from the Target Geometry."
+msgstr ""
+"Will remove the area occupied by the subtractor\n"
+"Geometry from the Target Geometry."
+
+#: flatcamTools/ToolSub.py:263
+msgid "Sub Tool"
+msgstr "Sub Tool"
+
+#: flatcamTools/ToolSub.py:284 flatcamTools/ToolSub.py:489
+msgid "No Target object loaded."
+msgstr "No Target object loaded."
+
+#: flatcamTools/ToolSub.py:287
+msgid "Loading geometry from Gerber objects."
+msgstr "Loading geometry from Gerber objects."
+
+#: flatcamTools/ToolSub.py:299 flatcamTools/ToolSub.py:504
+msgid "No Subtractor object loaded."
+msgstr "No Subtractor object loaded."
+
+#: flatcamTools/ToolSub.py:331
+msgid "Processing geometry from Subtractor Gerber object."
+msgstr "Processing geometry from Subtractor Gerber object."
+
+#: flatcamTools/ToolSub.py:352
+msgid "Parsing geometry for aperture"
+msgstr "Parsing geometry for aperture"
+
+#: flatcamTools/ToolSub.py:413
+msgid "Finished parsing geometry for aperture"
+msgstr "Finished parsing geometry for aperture"
+
+#: flatcamTools/ToolSub.py:458 flatcamTools/ToolSub.py:661
+msgid "Generating new object ..."
+msgstr "Generating new object ..."
+
+#: flatcamTools/ToolSub.py:462 flatcamTools/ToolSub.py:665
+#: flatcamTools/ToolSub.py:746
+msgid "Generating new object failed."
+msgstr "Generating new object failed."
+
+#: flatcamTools/ToolSub.py:467 flatcamTools/ToolSub.py:671
+msgid "Created"
+msgstr "Created"
+
+#: flatcamTools/ToolSub.py:518
+msgid "Currently, the Subtractor geometry cannot be of type Multigeo."
+msgstr "Currently, the Subtractor geometry cannot be of type Multigeo."
+
+#: flatcamTools/ToolSub.py:563
+msgid "Parsing solid_geometry ..."
+msgstr "Parsing solid_geometry ..."
+
+#: flatcamTools/ToolSub.py:565
+msgid "Parsing solid_geometry for tool"
+msgstr "Parsing solid_geometry for tool"
+
+#: flatcamTools/ToolTransform.py:23
+msgid "Object Transform"
+msgstr "Object Transform"
+
+#: flatcamTools/ToolTransform.py:78
+msgid ""
+"Rotate the selected object(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected objects."
+msgstr ""
+"Rotate the selected object(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected objects."
+
+#: flatcamTools/ToolTransform.py:99 flatcamTools/ToolTransform.py:120
+msgid ""
+"Angle for Skew action, in degrees.\n"
+"Float number between -360 and 360."
+msgstr ""
+"Angle for Skew action, in degrees.\n"
+"Float number between -360 and 360."
+
+#: flatcamTools/ToolTransform.py:109 flatcamTools/ToolTransform.py:130
+msgid ""
+"Skew/shear the selected object(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected objects."
+msgstr ""
+"Skew/shear the selected object(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected objects."
+
+#: flatcamTools/ToolTransform.py:159 flatcamTools/ToolTransform.py:179
+msgid ""
+"Scale the selected object(s).\n"
+"The point of reference depends on \n"
+"the Scale reference checkbox state."
+msgstr ""
+"Scale the selected object(s).\n"
+"The point of reference depends on \n"
+"the Scale reference checkbox state."
+
+#: flatcamTools/ToolTransform.py:228 flatcamTools/ToolTransform.py:248
+msgid ""
+"Offset the selected object(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected objects.\n"
+msgstr ""
+"Offset the selected object(s).\n"
+"The point of reference is the middle of\n"
+"the bounding box for all selected objects.\n"
+
+#: flatcamTools/ToolTransform.py:268 flatcamTools/ToolTransform.py:273
+msgid "Flip the selected object(s) over the X axis."
+msgstr "Flip the selected object(s) over the X axis."
+
+#: flatcamTools/ToolTransform.py:297
+msgid "Ref. Point"
+msgstr "Ref. Point"
+
+#: flatcamTools/ToolTransform.py:348
+msgid ""
+"Create the buffer effect on each geometry,\n"
+"element from the selected object, using the distance."
+msgstr ""
+"Create the buffer effect on each geometry,\n"
+"element from the selected object, using the distance."
+
+#: flatcamTools/ToolTransform.py:374
+msgid ""
+"Create the buffer effect on each geometry,\n"
+"element from the selected object, using the factor."
+msgstr ""
+"Create the buffer effect on each geometry,\n"
+"element from the selected object, using the factor."
+
+#: flatcamTools/ToolTransform.py:479
+msgid "Buffer D"
+msgstr "Buffer D"
+
+#: flatcamTools/ToolTransform.py:480
+msgid "Buffer F"
+msgstr "Buffer F"
+
+#: flatcamTools/ToolTransform.py:557
+msgid "Rotate transformation can not be done for a value of 0."
+msgstr "Rotate transformation can not be done for a value of 0."
+
+#: flatcamTools/ToolTransform.py:596 flatcamTools/ToolTransform.py:619
+msgid "Scale transformation can not be done for a factor of 0 or 1."
+msgstr "Scale transformation can not be done for a factor of 0 or 1."
+
+#: flatcamTools/ToolTransform.py:634 flatcamTools/ToolTransform.py:644
+msgid "Offset transformation can not be done for a value of 0."
+msgstr "Offset transformation can not be done for a value of 0."
+
+#: flatcamTools/ToolTransform.py:676
+msgid "No object selected. Please Select an object to rotate!"
+msgstr "No object selected. Please Select an object to rotate!"
+
+#: flatcamTools/ToolTransform.py:702
+msgid "CNCJob objects can't be rotated."
+msgstr "CNCJob objects can't be rotated."
+
+#: flatcamTools/ToolTransform.py:710
+msgid "Rotate done"
+msgstr "Rotate done"
+
+#: flatcamTools/ToolTransform.py:713 flatcamTools/ToolTransform.py:783
+#: flatcamTools/ToolTransform.py:833 flatcamTools/ToolTransform.py:887
+#: flatcamTools/ToolTransform.py:917 flatcamTools/ToolTransform.py:953
+msgid "Due of"
+msgstr "Due of"
+
+#: flatcamTools/ToolTransform.py:713 flatcamTools/ToolTransform.py:783
+#: flatcamTools/ToolTransform.py:833 flatcamTools/ToolTransform.py:887
+#: flatcamTools/ToolTransform.py:917 flatcamTools/ToolTransform.py:953
+msgid "action was not executed."
+msgstr "action was not executed."
+
+#: flatcamTools/ToolTransform.py:725
+msgid "No object selected. Please Select an object to flip"
+msgstr "No object selected. Please Select an object to flip"
+
+#: flatcamTools/ToolTransform.py:758
+msgid "CNCJob objects can't be mirrored/flipped."
+msgstr "CNCJob objects can't be mirrored/flipped."
+
+#: flatcamTools/ToolTransform.py:793
+msgid "Skew transformation can not be done for 0, 90 and 180 degrees."
+msgstr "Skew transformation can not be done for 0, 90 and 180 degrees."
+
+#: flatcamTools/ToolTransform.py:798
+msgid "No object selected. Please Select an object to shear/skew!"
+msgstr "No object selected. Please Select an object to shear/skew!"
+
+#: flatcamTools/ToolTransform.py:818
+msgid "CNCJob objects can't be skewed."
+msgstr "CNCJob objects can't be skewed."
+
+#: flatcamTools/ToolTransform.py:830
+msgid "Skew on the"
+msgstr "Skew on the"
+
+#: flatcamTools/ToolTransform.py:830 flatcamTools/ToolTransform.py:884
+#: flatcamTools/ToolTransform.py:914
+msgid "axis done"
+msgstr "axis done"
+
+#: flatcamTools/ToolTransform.py:844
+msgid "No object selected. Please Select an object to scale!"
+msgstr "No object selected. Please Select an object to scale!"
+
+#: flatcamTools/ToolTransform.py:875
+msgid "CNCJob objects can't be scaled."
+msgstr "CNCJob objects can't be scaled."
+
+#: flatcamTools/ToolTransform.py:884
+msgid "Scale on the"
+msgstr "Scale on the"
+
+#: flatcamTools/ToolTransform.py:894
+msgid "No object selected. Please Select an object to offset!"
+msgstr "No object selected. Please Select an object to offset!"
+
+#: flatcamTools/ToolTransform.py:901
+msgid "CNCJob objects can't be offset."
+msgstr "CNCJob objects can't be offset."
+
+#: flatcamTools/ToolTransform.py:914
+msgid "Offset on the"
+msgstr "Offset on the"
+
+#: flatcamTools/ToolTransform.py:924
+msgid "No object selected. Please Select an object to buffer!"
+msgstr "No object selected. Please Select an object to buffer!"
+
+#: flatcamTools/ToolTransform.py:927
+msgid "Applying Buffer"
+msgstr "Applying Buffer"
+
+#: flatcamTools/ToolTransform.py:931
+msgid "CNCJob objects can't be buffered."
+msgstr "CNCJob objects can't be buffered."
+
+#: flatcamTools/ToolTransform.py:948
+msgid "Buffer done"
+msgstr "Buffer done"
+
+#: tclCommands/TclCommandBbox.py:75 tclCommands/TclCommandNregions.py:74
+msgid "Expected GerberObject or GeometryObject, got"
+msgstr "Expected GerberObject or GeometryObject, got"
+
+#: tclCommands/TclCommandBounds.py:67 tclCommands/TclCommandBounds.py:71
+msgid "Expected a list of objects names separated by comma. Got"
+msgstr "Expected a list of objects names separated by comma. Got"
+
+#: tclCommands/TclCommandBounds.py:82
+msgid "TclCommand Bounds done."
+msgstr "TclCommand Bounds done."
+
+#: tclCommands/TclCommandCopperClear.py:276 tclCommands/TclCommandPaint.py:272
+#: tclCommands/TclCommandScale.py:81
+msgid "Could not retrieve box object"
+msgstr "Could not retrieve box object"
+
+#: tclCommands/TclCommandCopperClear.py:299
+msgid "Expected either -box <value> or -all."
+msgstr "Expected either -box <value> or -all."
+
+#: tclCommands/TclCommandGeoCutout.py:147
+msgid ""
+"The name of the object for which cutout is done is missing. Add it and retry."
+msgstr ""
+"The name of the object for which cutout is done is missing. Add it and retry."
+
+#: tclCommands/TclCommandGeoCutout.py:189
+msgid "Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8."
+msgstr "Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8."
+
+#: tclCommands/TclCommandGeoCutout.py:301
+#: tclCommands/TclCommandGeoCutout.py:359
+msgid "Any-form Cutout operation finished."
+msgstr "Any-form Cutout operation finished."
+
+#: tclCommands/TclCommandGeoCutout.py:365
+msgid "Cancelled. Object type is not supported."
+msgstr "Cancelled. Object type is not supported."
+
+#: tclCommands/TclCommandHelp.py:75
+msgid "Available commands:"
+msgstr "Available commands:"
+
+#: tclCommands/TclCommandHelp.py:115
+msgid "Type help <command_name> for usage."
+msgstr "Type help <command_name> for usage."
+
+#: tclCommands/TclCommandHelp.py:115
+msgid "Example: help open_gerber"
+msgstr "Example: help open_gerber"
+
+#: tclCommands/TclCommandPaint.py:244
+msgid "Expected -x <value> and -y <value>."
+msgstr "Expected -x <value> and -y <value>."
+
+#: tclCommands/TclCommandPaint.py:265
+msgid "Expected -box <value>."
+msgstr "Expected -box <value>."
+
+#: tclCommands/TclCommandPaint.py:286
+msgid ""
+"None of the following args: 'box', 'single', 'all' were used.\n"
+"Paint failed."
+msgstr ""
+"None of the following args: 'box', 'single', 'all' were used.\n"
+"Paint failed."
+
+#: tclCommands/TclCommandScale.py:106
+msgid ""
+"Expected -origin <origin> or -origin <min_bounds> or -origin <center> or - "
+"origin 3.0,4.2."
+msgstr ""
+"Expected -origin <origin> or -origin <min_bounds> or -origin <center> or - "
+"origin 3.0,4.2."
+
+#: tclCommands/TclCommandScale.py:119
+msgid "Expected -x <value> -y <value>."
+msgstr "Expected -x <value> -y <value>."
+
+#: tclCommands/TclCommandSetOrigin.py:95
+msgid "Expected a pair of (x, y) coordinates. Got"
+msgstr "Expected a pair of (x, y) coordinates. Got"
+
+#: tclCommands/TclCommandSetOrigin.py:102
+msgid "Origin set by offsetting all loaded objects with "
+msgstr "Origin set by offsetting all loaded objects with "
+
+#: tclCommands/TclCommandSubtractRectangle.py:62
+msgid "No Geometry name in args. Provide a name and try again."
+msgstr "No Geometry name in args. Provide a name and try again."
+
+#~ msgid "e_fr_probe"
+#~ msgstr "e_fr_probe"
+
+#~ msgid ""
+#~ " Could not generate punched hole Gerber because the punch hole sizeis "
+#~ "bigger than some of the apertures in the Gerber object."
+#~ msgstr ""
+#~ " Could not generate punched hole Gerber because the punch hole sizeis "
+#~ "bigger than some of the apertures in the Gerber object."
+
+#~ msgid "Paint Tool. Normal painting all task started."
+#~ msgstr "Paint Tool. Normal painting all task started."
+
+#~ msgid "Rest machining painting all task started."
+#~ msgstr "Rest machining painting all task started."
+
+#~| msgid "Painting polygon at location"
+#~ msgid "Painting polygons with method: lines."
+#~ msgstr "Painting polygons with method: lines."
+
+#~| msgid "Normal painting polygon task started."
+#~ msgid "Failed. Painting polygons with method: seed."
+#~ msgstr "Failed. Painting polygons with method: seed."
+
+#~| msgid "Normal painting polygon task started."
+#~ msgid "Failed. Painting polygons with method: standard."
+#~ msgstr "Failed. Painting polygons with method: standard."
+
+#~ msgid ""
+#~ "Could not do Paint All. Try a different combination of parameters. Or a "
+#~ "different Method of paint"
+#~ msgstr ""
+#~ "Could not do Paint All. Try a different combination of parameters. Or a "
+#~ "different Method of paint"
+
+#~| msgid "Paint Tool. Normal painting all task started."
+#~ msgid "Paint Tool. Normal painting area task started."
+#~ msgstr "Paint Tool. Normal painting area task started."
+
+#~ msgid "Rest machining painting area task started."
+#~ msgstr "Rest machining painting area task started."
+
+#~ msgid "Executing Tcl Script ..."
+#~ msgstr "Executing Tcl Script ..."
+
+#~ msgid "Open cancelled."
+#~ msgstr "Open cancelled."
+
+#~ msgid "Preferences default restore was cancelled."
+#~ msgstr "Preferences default restore was cancelled."
+
+#~ msgid "FlatCAM preferences import cancelled."
+#~ msgstr "FlatCAM preferences import cancelled."
+
+#~ msgid "FlatCAM preferences export cancelled."
+#~ msgstr "FlatCAM preferences export cancelled."
+
+#~ msgid "Multigeo. Geometry merging finished"
+#~ msgstr "Multigeo. Geometry merging finished"
+
+#~ msgid "Units conversion cancelled."
+#~ msgstr "Units conversion cancelled."
+
+#~ msgid "Open Gerber cancelled."
+#~ msgstr "Open Gerber cancelled."
+
+#~ msgid " Open Excellon cancelled."
+#~ msgstr " Open Excellon cancelled."
+
+#~ msgid "Open G-Code cancelled."
+#~ msgstr "Open G-Code cancelled."
+
+#~ msgid "Open Project cancelled."
+#~ msgstr "Open Project cancelled."
+
+#~ msgid "Open HPGL2 file cancelled."
+#~ msgstr "Open HPGL2 file cancelled."
+
+#~ msgid "Open Config cancelled."
+#~ msgstr "Open Config cancelled."
+
+#~ msgid " Export SVG cancelled."
+#~ msgstr " Export SVG cancelled."
+
+#~ msgid "Export PNG cancelled."
+#~ msgstr "Export PNG cancelled."
+
+#~ msgid "No object selected. Please select an Gerber object to export."
+#~ msgstr "No object selected. Please select an Gerber object to export."
+
+#~ msgid "Save Gerber source file cancelled."
+#~ msgstr "Save Gerber source file cancelled."
+
+#~ msgid "No object selected. Please select an Script object to export."
+#~ msgstr "No object selected. Please select an Script object to export."
+
+#~ msgid "Save Script source file cancelled."
+#~ msgstr "Save Script source file cancelled."
+
+#~ msgid "No object selected. Please select an Document object to export."
+#~ msgstr "No object selected. Please select an Document object to export."
+
+#~ msgid "Save Document source file cancelled."
+#~ msgstr "Save Document source file cancelled."
+
+#~ msgid "No object selected. Please select an Excellon object to export."
+#~ msgstr "No object selected. Please select an Excellon object to export."
+
+#~ msgid "Saving Excellon source file cancelled."
+#~ msgstr "Saving Excellon source file cancelled."
+
+#~ msgid "No object selected. Please Select an Excellon object to export."
+#~ msgstr "No object selected. Please Select an Excellon object to export."
+
+#~ msgid "Export Excellon cancelled."
+#~ msgstr "Export Excellon cancelled."
+
+#~ msgid "No object selected. Please Select an Gerber object to export."
+#~ msgstr "No object selected. Please Select an Gerber object to export."
+
+#~ msgid "Export Gerber cancelled."
+#~ msgstr "Export Gerber cancelled."
+
+#~ msgid "Export DXF cancelled."
+#~ msgstr "Export DXF cancelled."
+
+#~ msgid "Open SVG cancelled."
+#~ msgstr "Open SVG cancelled."
+
+#~ msgid "Open DXF cancelled."
+#~ msgstr "Open DXF cancelled."
+
+#~ msgid "Open TCL script cancelled."
+#~ msgstr "Open TCL script cancelled."
+
+#~ msgid "Run TCL script cancelled."
+#~ msgstr "Run TCL script cancelled."
+
+#~ msgid "Save Project cancelled."
+#~ msgstr "Save Project cancelled."
+
+#~ msgid "Save Object PDF cancelled."
+#~ msgstr "Save Object PDF cancelled."
+
+#~ msgid "Shows list of commands."
+#~ msgstr "Shows list of commands."
+
+#~ msgid "FlatCAM bookmarks export cancelled."
+#~ msgstr "FlatCAM bookmarks export cancelled."
+
+#~ msgid "FlatCAM bookmarks import cancelled."
+#~ msgstr "FlatCAM bookmarks import cancelled."
+
+#~ msgid "FlatCAM Tools DB export cancelled."
+#~ msgstr "FlatCAM Tools DB export cancelled."
+
+#~ msgid "FlatCAM Tools DB import cancelled."
+#~ msgstr "FlatCAM Tools DB import cancelled."
+
+#~ msgid ""
+#~ "Wrong value format for self.defaults[\"z_pdepth\"] or self."
+#~ "options[\"z_pdepth\"]"
+#~ msgstr ""
+#~ "Wrong value format for self.defaults[\"z_pdepth\"] or self."
+#~ "options[\"z_pdepth\"]"
+
+#~ msgid ""
+#~ "Wrong value format for self.defaults[\"feedrate_probe\"] or self."
+#~ "options[\"feedrate_probe\"]"
+#~ msgstr ""
+#~ "Wrong value format for self.defaults[\"feedrate_probe\"] or self."
+#~ "options[\"feedrate_probe\"]"
+
+#~ msgid "Starting G-Code..."
+#~ msgstr "Starting G-Code..."
+
+#~ msgid ""
+#~ "Algorithm to paint the polygon:<BR><B>Standard</B>: Fixed step inwards."
+#~ "<BR><B>Seed-based</B>: Outwards from seed."
+#~ msgstr ""
+#~ "Algorithm to paint the polygon:<BR><B>Standard</B>: Fixed step inwards."
+#~ "<BR><B>Seed-based</B>: Outwards from seed."
+
+#~ msgid "Seed-based"
+#~ msgstr "Seed-based"
+
+#~ msgid "Straight lines"
+#~ msgstr "Straight lines"
+
+#~ msgid "Paint cancelled. No shape selected."
+#~ msgstr "Paint cancelled. No shape selected."
+
+#~ msgid "Transformation cancelled. No shape selected."
+#~ msgstr "Transformation cancelled. No shape selected."
+
+#~ msgid "Buffer cancelled. No shape selected."
+#~ msgstr "Buffer cancelled. No shape selected."
+
+#~ msgid "Export Code cancelled."
+#~ msgstr "Export Code cancelled."
+
+#~ msgid "&Save Project ..."
+#~ msgstr "&Save Project ..."
+
+#~ msgid "Save Project C&opy ..."
+#~ msgstr "Save Project C&opy ..."
+
+#~ msgid "Change the size of the object."
+#~ msgstr "Change the size of the object."
+
+#~ msgid "Change the position of this object."
+#~ msgstr "Change the position of this object."
+
+#~ msgid "Vector"
+#~ msgstr "Vector"
+
+#~ msgid ""
+#~ "Create a CNC Job object\n"
+#~ "for this drill object."
+#~ msgstr ""
+#~ "Create a CNC Job object\n"
+#~ "for this drill object."
+
+#~ msgid ""
+#~ "Choose what to use for GCode generation:\n"
+#~ "'Drills', 'Slots' or 'Both'.\n"
+#~ "When choosing 'Slots' or 'Both', slots will be\n"
+#~ "converted to a series of drills."
+#~ msgstr ""
+#~ "Choose what to use for GCode generation:\n"
+#~ "'Drills', 'Slots' or 'Both'.\n"
+#~ "When choosing 'Slots' or 'Both', slots will be\n"
+#~ "converted to a series of drills."
+
+#~ msgid "Generate the CNC Job."
+#~ msgstr "Generate the CNC Job."
+
+#~ msgid "Add Tool from DataBase"
+#~ msgstr "Add Tool from DataBase"
+
+#~ msgid "Select a theme for FlatCAM."
+#~ msgstr "Select a theme for FlatCAM."
+
+#~ msgid "Conv."
+#~ msgstr "Conv."
+
+#~ msgid "Diameters of the cutting tools, separated by ','"
+#~ msgstr "Diameters of the cutting tools, separated by ','"
+
+#~ msgid "Tools dia"
+#~ msgstr "Tools dia"
+
+#~ msgid "The new tool diameter (cut width) to add in the tool table."
+#~ msgstr "The new tool diameter (cut width) to add in the tool table."
+
+#~ msgid ""
+#~ "Algorithm for non-copper clearing:<BR><B>Standard</B>: Fixed step inwards."
+#~ "<BR><B>Seed-based</B>: Outwards from seed.<BR><B>Line-based</B>: Parallel "
+#~ "lines."
+#~ msgstr ""
+#~ "Algorithm for non-copper clearing:<BR><B>Standard</B>: Fixed step inwards."
+#~ "<BR><B>Seed-based</B>: Outwards from seed.<BR><B>Line-based</B>: Parallel "
+#~ "lines."
+
+#~ msgid "Area"
+#~ msgstr "Area"
+
+#~ msgid "Ref"
+#~ msgstr "Ref"
+
+#~ msgid ""
+#~ "- 'Itself' -  the non copper clearing extent\n"
+#~ "is based on the object that is copper cleared.\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"
+#~ "- 'Reference Object' -  will do non copper clearing within the area\n"
+#~ "specified by another object."
+#~ msgstr ""
+#~ "- 'Itself' -  the non copper clearing extent\n"
+#~ "is based on the object that is copper cleared.\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"
+#~ "- 'Reference Object' -  will do non copper clearing within the area\n"
+#~ "specified by another object."
+
+#~ msgid "Sel"
+#~ msgstr "Sel"
+
+#~ msgid "Diameters of nozzle tools, separated by ','"
+#~ msgstr "Diameters of nozzle tools, separated by ','"
+
+#~ msgid "Reference Gerber"
+#~ msgstr "Reference Gerber"
+
+#~ msgid "Reference Excellon"
+#~ msgstr "Reference Excellon"
+
+#~ msgid "Reference Geometry"
+#~ msgstr "Reference Geometry"
+
+#~ msgid "Point/Box Reference"
+#~ msgstr "Point/Box Reference"
+
+#~ msgid ""
+#~ "If 'Point' is selected above it store the coordinates (x, y) through "
+#~ "which\n"
+#~ "the mirroring axis passes.\n"
+#~ "If 'Box' is selected above, select here a FlatCAM object (Gerber, Exc or "
+#~ "Geo).\n"
+#~ "Through the center of this object pass the mirroring axis selected above."
+#~ msgstr ""
+#~ "If 'Point' is selected above it store the coordinates (x, y) through "
+#~ "which\n"
+#~ "the mirroring axis passes.\n"
+#~ "If 'Box' is selected above, select here a FlatCAM object (Gerber, Exc or "
+#~ "Geo).\n"
+#~ "Through the center of this object pass the mirroring axis selected above."
+
+#~ msgid "Alignment Drill Diameter"
+#~ msgstr "Alignment Drill Diameter"
+
+#~ msgid ""
+#~ "'Point' coordinates missing. Using Origin (0, 0) as mirroring reference."
+#~ msgstr ""
+#~ "'Point' coordinates missing. Using Origin (0, 0) as mirroring reference."
+
+#~ msgid "Export positive film cancelled."
+#~ msgstr "Export positive film cancelled."
+
+#~ msgid "Export negative film cancelled."
+#~ msgstr "Export negative film cancelled."
+
+#~ msgid "Move action cancelled."
+#~ msgstr "Move action cancelled."
+
+#~ msgid "Diameter for the new tool."
+#~ msgstr "Diameter for the new tool."
+
+#~ msgid "Create Paint Geometry"
+#~ msgstr "Create Paint Geometry"
+
+#~ msgid "Paint Tool. Reading parameters."
+#~ msgstr "Paint Tool. Reading parameters."
+
+#~ msgid "Paint Tool. Rest machining painting area task started."
+#~ msgstr "Paint Tool. Rest machining painting area task started."
+
+#~ msgid "Properties Tool was not displayed. No object selected."
+#~ msgstr "Properties Tool was not displayed. No object selected."
+
+#~ msgid " Export PNG cancelled."
+#~ msgstr " Export PNG cancelled."
+
+#~ msgid "Adding Nozzle tool cancelled. Tool already in Tool Table."
+#~ msgstr "Adding Nozzle tool cancelled. Tool already in Tool Table."
+
+#~ msgid ""
+#~ "None of the following args: 'ref', 'all' were found or none was set to "
+#~ "1.\n"
+#~ "Copper clearing failed."
+#~ msgstr ""
+#~ "None of the following args: 'ref', 'all' were found or none was set to "
+#~ "1.\n"
+#~ "Copper clearing failed."
+
+#~ msgid "PostProcessor"
+#~ msgstr "PostProcessor"
+
+#~ msgid "Default <b>Zeros</b>"
+#~ msgstr "Default <b>Zeros</b>"
+
+#~ msgid ""
+#~ "This sets the default type of Excellon zeros.\n"
+#~ "If it is not detected in the parsed file the value here\n"
+#~ "will be used.If LZ then Leading Zeros are kept and\n"
+#~ "Trailing Zeros are removed.\n"
+#~ "If TZ is checked then Trailing Zeros are kept\n"
+#~ "and Leading Zeros are removed."
+#~ msgstr ""
+#~ "This sets the default type of Excellon zeros.\n"
+#~ "If it is not detected in the parsed file the value here\n"
+#~ "will be used.If LZ then Leading Zeros are kept and\n"
+#~ "Trailing Zeros are removed.\n"
+#~ "If TZ is checked then Trailing Zeros are kept\n"
+#~ "and Leading Zeros are removed."
+
+#~ msgid "Default <b>Units</b>"
+#~ msgstr "Default <b>Units</b>"
+
+#~ msgid "Optimization Time"
+#~ msgstr "Optimization Time"
+
+#~ msgid "Coordinates decimals"
+#~ msgstr "Coordinates decimals"
+
+#~ msgid "Feedrate decimals"
+#~ msgstr "Feedrate decimals"
+
+#~ msgid "Rest M."
+#~ msgstr "Rest M."
+
+#~ msgid "Convex Sh."
+#~ msgstr "Convex Sh."
+
+#~ msgid "Add Tool to Tools DB"
+#~ msgstr "Add Tool to Tools DB"
+
+#~ msgid "Remove Tool from Tools DB"
+#~ msgstr "Remove Tool from Tools DB"
+
+#~ msgid "Export Tool DB"
+#~ msgstr "Export Tool DB"
+
+#~ msgid "Import Tool DB"
+#~ msgstr "Import Tool DB"
+
+#~ msgid "Please enter the desired tool diameter in Float format."
+#~ msgstr "Please enter the desired tool diameter in Float format."
+
+#~ msgid "Default Tool added. Wrong value format entered."
+#~ msgstr "Default Tool added. Wrong value format entered."
+
+#~ msgid "Import Preferences"
+#~ msgstr "Import Preferences"
+
+#~ msgid ""
+#~ "Import a full set of FlatCAM settings from a file\n"
+#~ "previously saved on HDD.\n"
+#~ "\n"
+#~ "FlatCAM automatically save a 'factory_defaults' file\n"
+#~ "on the first start. Do not delete that file."
+#~ msgstr ""
+#~ "Import a full set of FlatCAM settings from a file\n"
+#~ "previously saved on HDD.\n"
+#~ "\n"
+#~ "FlatCAM automatically save a 'factory_defaults' file\n"
+#~ "on the first start. Do not delete that file."
+
+#~ msgid "Export Preferences"
+#~ msgstr "Export Preferences"
+
+#~ msgid ""
+#~ "Export a full set of FlatCAM settings in a file\n"
+#~ "that is saved on HDD."
+#~ msgstr ""
+#~ "Export a full set of FlatCAM settings in a file\n"
+#~ "that is saved on HDD."
+
+#~ msgid "Start move Z"
+#~ msgstr "Start move Z"
+
+#~ msgid "Grid X value"
+#~ msgstr "Grid X value"
+
+#~ msgid "Grid Y value"
+#~ msgstr "Grid Y value"
+
+#~ msgid "Wk. size"
+#~ msgstr "Wk. size"
+
+#~ msgid "Sel. Fill"
+#~ msgstr "Sel. Fill"
+
+#~ msgid "Sel. Line"
+#~ msgstr "Sel. Line"
+
+#~ msgid "Sel2. Fill"
+#~ msgstr "Sel2. Fill"
+
+#~ msgid "Sel2. Line"
+#~ msgstr "Sel2. Line"
+
+#~ msgid "Editor Draw Sel."
+#~ msgstr "Editor Draw Sel."
+
+#~ msgid "Proj. Dis. Items"
+#~ msgstr "Proj. Dis. Items"
+
+#~ msgid "Sel. Shape"
+#~ msgstr "Sel. Shape"
+
+#~ msgid "NB Font Size"
+#~ msgstr "NB Font Size"
+
+#~ msgid "Axis Font Size"
+#~ msgstr "Axis Font Size"
+
+#~ msgid "Textbox Font Size"
+#~ msgstr "Textbox Font Size"
+
+#~ msgid "Shell at StartUp"
+#~ msgstr "Shell at StartUp"
+
+#~ msgid "Project at StartUp"
+#~ msgstr "Project at StartUp"
+
+#~ msgid "Mouse Cursor"
+#~ msgstr "Mouse Cursor"
+
+#~ msgid ""
+#~ "Set the language used throughout FlatCAM.\n"
+#~ "The app will restart after click.Windows: When FlatCAM is installed in "
+#~ "Program Files\n"
+#~ "directory, it is possible that the app will not\n"
+#~ "restart after the button is clicked due of Windows\n"
+#~ "security features. In this case the language will be\n"
+#~ "applied at the next app start."
+#~ msgstr ""
+#~ "Set the language used throughout FlatCAM.\n"
+#~ "The app will restart after click.Windows: When FlatCAM is installed in "
+#~ "Program Files\n"
+#~ "directory, it is possible that the app will not\n"
+#~ "restart after the button is clicked due of Windows\n"
+#~ "security features. In this case the language will be\n"
+#~ "applied at the next app start."
+
+#~ msgid "G-code does not have a units code: either G20 or G21"
+#~ msgstr "G-code does not have a units code: either G20 or G21"
+
+#, python-brace-format
+#~ msgid ""
+#~ "[selected] {kind} created/selected: <span style=\"color:{color};\">{name}"
+#~ "</span>"
+#~ msgstr ""
+#~ "[selected] {kind} created/selected: <span style=\"color:{color};\">{name}"
+#~ "</span>"
+
+#, python-brace-format
+#~ msgid "[selected]<span style=\"color:{color};\">{name}</span> selected"
+#~ msgstr "[selected]<span style=\"color:{color};\">{name}</span> selected"
+
+#, python-brace-format
+#~ msgid "{l_save}/Project_{date}"
+#~ msgstr "{l_save}/Project_{date}"
+
+#, python-brace-format
+#~| msgid "{l_save}/Project_{date}"
+#~ msgid "{l_save}/{obj_name}_{date}"
+#~ msgstr "{l_save}/{obj_name}_{date}"
+
+#, python-format
+#~ msgid ""
+#~ "How much (fraction) of the tool width to overlap each tool pass.\n"
+#~ "Example:\n"
+#~ "A value here of 0.25 means 25%% from the tool diameter found above.\n"
+#~ "\n"
+#~ "Adjust the value starting with lower values\n"
+#~ "and increasing it if areas that should be painted are still \n"
+#~ "not painted.\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."
+#~ msgstr ""
+#~ "How much (fraction) of the tool width to overlap each tool pass.\n"
+#~ "Example:\n"
+#~ "A value here of 0.25 means 25%% from the tool diameter found above.\n"
+#~ "\n"
+#~ "Adjust the value starting with lower values\n"
+#~ "and increasing it if areas that should be painted are still \n"
+#~ "not painted.\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."
+
+#~ msgid ""
+#~ "Type here any G-Code commands you would like to append to the generated "
+#~ "file. I.e.: M2 (End of program)"
+#~ msgstr ""
+#~ "Type here any G-Code commands you would like to append to the generated "
+#~ "file. I.e.: M2 (End of program)"
+
+#~ msgid ""
+#~ "Can be:\n"
+#~ "- Portrait\n"
+#~ "- Lanscape"
+#~ msgstr ""
+#~ "Can be:\n"
+#~ "- Portrait\n"
+#~ "- Lanscape"
+
+#~ msgid ""
+#~ "- 'Rectangular' - the bounding box will be of rectangular shape.\n"
+#~ " - 'Minimal' - the bounding box will be the convex hull shape."
+#~ msgstr ""
+#~ "- 'Rectangular' - the bounding box will be of rectangular shape.\n"
+#~ " - 'Minimal' - the bounding box will be the convex hull shape."
+
+#~ msgid ""
+#~ "- '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."
+#~ msgstr ""
+#~ "- '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."
+
+#~ msgid "Calibrate Tool"
+#~ msgstr "Calibrate Tool"
+
+#, python-brace-format
+#~ msgid "MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}"
+#~ msgstr "MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}"
+
+#~ msgid "Paint Area"
+#~ msgstr "Paint Area"
+
+#~ msgid ""
+#~ "Generate GCode file to locate and align the PCB by using\n"
+#~ "the four points acquired above."
+#~ msgstr ""
+#~ "Generate GCode file to locate and align the PCB by using\n"
+#~ "the four points acquired above."
+
+#~ msgid "Axis Ref:"
+#~ msgstr "Axis Ref:"
+
+#~ msgid "Change project units ..."
+#~ msgstr "Change project units ..."
+
+#~ msgid "Tool diameter value is missing or wrong format. Add it and retry."
+#~ msgstr "Tool diameter value is missing or wrong format. Add it and retry."
+
+#~ msgid "Overlap value is missing or wrong format. Add it and retry."
+#~ msgstr "Overlap value is missing or wrong format. Add it and retry."
+
+#~ msgid "Margin distance value is missing or wrong format. Add it and retry."
+#~ msgstr "Margin distance value is missing or wrong format. Add it and retry."
+
+#, python-format
+#~ msgid ""
+#~ "How much (fraction) of the tool width to overlap each tool pass.\n"
+#~ "Example:\n"
+#~ "A value here of 0.25 means an overlap of 25%% from the tool diameter "
+#~ "found above."
+#~ msgstr ""
+#~ "How much (fraction) of the tool width to overlap each tool pass.\n"
+#~ "Example:\n"
+#~ "A value here of 0.25 means an overlap of 25%% from the tool diameter "
+#~ "found above."
+
+#~ msgid "Feed Rate X-Y"
+#~ msgstr "Feed Rate X-Y"
+
+#~ msgid "Feed Rate Z"
+#~ msgstr "Feed Rate Z"
+
+#~ msgid "Feed Rate Rapids"
+#~ msgstr "Feed Rate Rapids"
+
+#~ msgid "Generate"
+#~ msgstr "Generate"
+
+#~| msgid "STEP 1"
+#~ msgid "STEP 5"
+#~ msgstr "STEP 5"
+
+#~| msgid "Calc. Tool"
+#~ msgid "Cal Exc Tool"
+#~ msgstr "Cal Exc Tool"
+
+#~ msgid "Object to be cutout.                        "
+#~ msgstr "Object to be cutout.                        "
+
+#~ msgid "Margin:"
+#~ msgstr "Margin:"
+
+#~ msgid "Gap size:"
+#~ msgstr "Gap size:"
+
+#~ msgid ""
+#~ "The cutout shape can be of ny shape.\n"
+#~ "Useful when the PCB has a non-rectangular shape."
+#~ msgstr ""
+#~ "The cutout shape can be of ny shape.\n"
+#~ "Useful when the PCB has a non-rectangular shape."
+
+#~ msgid ""
+#~ "The resulting cutout shape is\n"
+#~ "always a rectangle shape and it will be\n"
+#~ "the bounding box of the Object."
+#~ msgstr ""
+#~ "The resulting cutout shape is\n"
+#~ "always a rectangle shape and it will be\n"
+#~ "the bounding box of the Object."
+
+#~ msgid "Geo Obj"
+#~ msgstr "Geo Obj"
+
+#~ msgid "Manual Geo"
+#~ msgstr "Manual Geo"
+
+#~ msgid ""
+#~ "Use the left mouse button (LMB) click\n"
+#~ "to create a bridge gap to separate the PCB from\n"
+#~ "the surrounding material."
+#~ msgstr ""
+#~ "Use the left mouse button (LMB) click\n"
+#~ "to create a bridge gap to separate the PCB from\n"
+#~ "the surrounding material."
+
+#~ msgid "Generate Gap"
+#~ msgstr "Generate Gap"
+
+#~ msgid "Reset"
+#~ msgstr "Reset"
+
+#~ msgid "Resets all the fields."
+#~ msgstr "Resets all the fields."
+
+#~ msgid "Overlap value must be between 0 (inclusive) and 1 (exclusive), "
+#~ msgstr "Overlap value must be between 0 (inclusive) and 1 (exclusive), "
+
+#~ msgid "Overlap value must be between 0 (inclusive) and 1 (exclusive)"
+#~ msgstr "Overlap value must be between 0 (inclusive) and 1 (exclusive)"
+
+#~ msgid "Click inside the desired polygon."
+#~ msgstr "Click inside the desired polygon."
+
+#~ msgid ""
+#~ "#\n"
+#~ "# CREATE A NEW FLATCAM TCL SCRIPT\n"
+#~ "# TCL Tutorial here: https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial."
+#~ "html\n"
+#~ "#\n"
+#~ "\n"
+#~ "# FlatCAM commands list:\n"
+#~ "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, "
+#~ "AlignDrillGrid, ClearShell, ClearCopper,\n"
+#~ "# Cncjob, Cutout, Delete, Drillcncjob, ExportGcode, ExportSVG, Exteriors, "
+#~ "GeoCutout, GeoUnion, GetNames,\n"
+#~ "# GetSys, ImportSvg, Interiors, Isolate, Follow, JoinExcellon, "
+#~ "JoinGeometry, ListSys, MillDrills,\n"
+#~ "# MillSlots, Mirror, New, NewGeometry, Offset, OpenExcellon, OpenGCode, "
+#~ "OpenGerber, OpenProject,\n"
+#~ "# Options, Paint, Panelize, Plot, SaveProject, SaveSys, Scale, SetActive, "
+#~ "SetSys, Skew, SubtractPoly,\n"
+#~ "# SubtractRectangle, Version, WriteGCode\n"
+#~ "#\n"
+#~ "\n"
+#~ msgstr ""
+#~ "#\n"
+#~ "# CREATE A NEW FLATCAM TCL SCRIPT\n"
+#~ "# TCL Tutorial here: https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial."
+#~ "html\n"
+#~ "#\n"
+#~ "\n"
+#~ "# FlatCAM commands list:\n"
+#~ "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, "
+#~ "AlignDrillGrid, ClearShell, ClearCopper,\n"
+#~ "# Cncjob, Cutout, Delete, Drillcncjob, ExportGcode, ExportSVG, Exteriors, "
+#~ "GeoCutout, GeoUnion, GetNames,\n"
+#~ "# GetSys, ImportSvg, Interiors, Isolate, Follow, JoinExcellon, "
+#~ "JoinGeometry, ListSys, MillDrills,\n"
+#~ "# MillSlots, Mirror, New, NewGeometry, Offset, OpenExcellon, OpenGCode, "
+#~ "OpenGerber, OpenProject,\n"
+#~ "# Options, Paint, Panelize, Plot, SaveProject, SaveSys, Scale, SetActive, "
+#~ "SetSys, Skew, SubtractPoly,\n"
+#~ "# SubtractRectangle, Version, WriteGCode\n"
+#~ "#\n"
+#~ "\n"
+
+#~ msgid "Program Author"
+#~ msgstr "Program Author"
+
+#~ msgid "Export G-Code ..."
+#~ msgstr "Export G-Code ..."
+
+#~ msgid "&View"
+#~ msgstr "&View"
+
+#~ msgid "&Tool"
+#~ msgstr "&Tool"
+
+#~ msgid "APP.  DEFAULTS"
+#~ msgstr "APP.  DEFAULTS"
+
+#~ msgid "PROJ. OPTIONS "
+#~ msgstr "PROJ. OPTIONS "
+
+#~ msgid "FULL Geo"
+#~ msgstr "FULL Geo"
+
+#~ msgid ""
+#~ "Create the Geometry Object\n"
+#~ "for isolation routing. It contains both\n"
+#~ "the interiors and exteriors geometry."
+#~ msgstr ""
+#~ "Create the Geometry Object\n"
+#~ "for isolation routing. It contains both\n"
+#~ "the interiors and exteriors geometry."
+
+#~ msgid "Ext Geo"
+#~ msgstr "Ext Geo"
+
+#~ msgid ""
+#~ "Create the Geometry Object\n"
+#~ "for isolation routing containing\n"
+#~ "only the exteriors geometry."
+#~ msgstr ""
+#~ "Create the Geometry Object\n"
+#~ "for isolation routing containing\n"
+#~ "only the exteriors geometry."
+
+#~ msgid "Int Geo"
+#~ msgstr "Int Geo"
+
+#~ msgid ""
+#~ "Create the Geometry Object\n"
+#~ "for isolation routing containing\n"
+#~ "only the interiors geometry."
+#~ msgstr ""
+#~ "Create the Geometry Object\n"
+#~ "for isolation routing containing\n"
+#~ "only the interiors geometry."
+
+#~ msgid ""
+#~ "Select from the Tools Table above\n"
+#~ "the hole dias that are to be drilled.\n"
+#~ "Use the # column to make the selection."
+#~ msgstr ""
+#~ "Select from the Tools Table above\n"
+#~ "the hole dias that are to be drilled.\n"
+#~ "Use the # column to make the selection."
+
+#~ msgid "Wk. format"
+#~ msgstr "Wk. format"
+
+#~ msgid "y_toolchange = Y coord for Toolchange"
+#~ msgstr "y_toolchange = Y coord for Toolchange"
+
+#~ msgid "Ref."
+#~ msgstr "Ref."
+
+#~ msgid "Gerber   Reference Box Object"
+#~ msgstr "Gerber   Reference Box Object"
+
+#~ msgid "Excellon Reference Box Object"
+#~ msgstr "Excellon Reference Box Object"
+
+#~ msgid "Geometry Reference Box Object"
+#~ msgstr "Geometry Reference Box Object"
+
+#~ msgid "{l_save}/FlatCAM_Bookmarks_{date}"
+#~ msgstr "{l_save}/FlatCAM_Bookmarks_{date}"
+
+#~ msgid "Could not load bookamrks file."
+#~ msgstr "Could not load bookamrks file."
+
+#~ msgid "&Help"
+#~ msgstr "&Help"
+
+#~ msgid "FlatCAM.org"
+#~ msgstr "FlatCAM.org"
+
+#~ msgid "tool = tool number"
+#~ msgstr "tool = tool number"
+
+#~ msgid "tooldia = tool diameter"
+#~ msgstr "tooldia = tool diameter"
+
+#~ msgid "t_drills = for Excellon, total number of drills"
+#~ msgstr "t_drills = for Excellon, total number of drills"
+
+#~ msgid "x_toolchange = X coord for Toolchange"
+#~ msgstr "x_toolchange = X coord for Toolchange"
+
+#~ msgid "z_toolchange = Z coord for Toolchange"
+#~ msgstr "z_toolchange = Z coord for Toolchange"
+
+#~ msgid "z_depthpercut = the step value for multidepth cut"
+#~ msgstr "z_depthpercut = the step value for multidepth cut"
+
+#~ msgid "spindlesspeed = the value for the spindle speed"
+#~ msgstr "spindlesspeed = the value for the spindle speed"
+
+#~ msgid "Rotate Angle"
+#~ msgstr "Rotate Angle"
+
+#~ msgid "Skew_X angle"
+#~ msgstr "Skew_X angle"
+
+#~ msgid "Skew_Y angle"
+#~ msgstr "Skew_Y angle"
+
+#~ msgid "Scale_X factor"
+#~ msgstr "Scale_X factor"
+
+#~ msgid "Scale_Y factor"
+#~ msgstr "Scale_Y factor"
+
+#~ msgid "Offset_X val"
+#~ msgstr "Offset_X val"
+
+#~ msgid "Offset_Y val"
+#~ msgstr "Offset_Y val"
+
+#~ msgid " Mirror Ref. Point"
+#~ msgstr " Mirror Ref. Point"
+
+#~ msgid "The Gerber Copper Bottom file for which rules are checked."
+#~ msgstr "The Gerber Copper Bottom file for which rules are checked."
+
+#~ msgid "The Gerber Silkscreen Bottom file for which rules are checked."
+#~ msgstr "The Gerber Silkscreen Bottom file for which rules are checked."
+
+#~| msgid "Excellon file"
+#~ msgid "Excellon Files"
+#~ msgstr "Excellon Files"
+
+#~ msgid "Go"
+#~ msgstr "Go"
+
+#~ msgid "There are no polygons to mark area."
+#~ msgstr "There are no polygons to mark area."
+
+#~ msgid "&Edit"
+#~ msgstr "&Edit"
+
+#~ msgid "&Options"
+#~ msgstr "&Options"
+
+#~ msgid "Measurement Tool"
+#~ msgstr "Measurement Tool"
+
+#~ msgid "Margin value is missing or wrong format. Add it and retry."
+#~ msgstr "Margin value is missing or wrong format. Add it and retry."
+
+#~ msgid "Gap size value is missing or wrong format. Add it and retry."
+#~ msgstr "Gap size value is missing or wrong format. Add it and retry."
+
+#~ msgid "Measurement"
+#~ msgstr "Measurement"
+
+#~ msgid "Meas. Tool"
+#~ msgstr "Meas. Tool"
+
+#~ msgid "Not available with the current Graphic Engine Legacy(2D)."
+#~ msgstr "Not available with the current Graphic Engine Legacy(2D)."
+
+#~ msgid "ToolMove.on_left_click()"
+#~ msgstr "ToolMove.on_left_click()"
+
+#~ msgid "on_paint_button_click"
+#~ msgstr "on_paint_button_click"
+
+#~ msgid "PaintTool.paint_poly()"
+#~ msgstr "PaintTool.paint_poly()"
+
+#~ msgid "ToolSolderPaste.on_view_gcode()"
+#~ msgstr "ToolSolderPaste.on_view_gcode()"
+
+#~ msgid "App.on_fileopenscript() -->"
+#~ msgstr "App.on_fileopenscript() -->"
+
+#~ msgid "<span style=\"color:green;\"><b>%s</b></span>"
+#~ msgstr "<span style=\"color:green;\"><b>%s</b></span>"
+
+#~ msgid "<span style=\"color:red;\"><b>%s</b></span>"
+#~ msgstr "<span style=\"color:red;\"><b>%s</b></span>"
+
+#~ msgid "FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->"
+#~ msgstr "FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->"
+
+#~ msgid "FlatCAMCNNJob.on_edit_code_click() -->"
+#~ msgstr "FlatCAMCNNJob.on_edit_code_click() -->"
+
+#~ msgid ""
+#~ "toolbars, key shortcuts or even dragging and dropping the files on the GUI"
+#~ msgstr ""
+#~ "toolbars, key shortcuts or even dragging and dropping the files on the GUI"
+
+#~ msgid ""
+#~ "You can also load a FlatCAM project by double clicking on the project "
+#~ "file, drag"
+#~ msgstr ""
+#~ "You can also load a FlatCAM project by double clicking on the project "
+#~ "file, drag"
+
+#~ msgid ""
+#~ "Once an object is available in the Project Tab, by selecting it and then "
+#~ "focusing on"
+#~ msgstr ""
+#~ "Once an object is available in the Project Tab, by selecting it and then "
+#~ "focusing on"
+
+#~ msgid "SELECTED TAB"
+#~ msgstr "SELECTED TAB"
+
+#~ msgid "more simpler is to double click the object name in the Project Tab"
+#~ msgstr "more simpler is to double click the object name in the Project Tab"
+
+#~ msgid "will be updated with the object properties according to"
+#~ msgstr "will be updated with the object properties according to"
+
+#~ msgid "kind: Gerber, Excellon, Geometry or CNCJob object"
+#~ msgstr "kind: Gerber, Excellon, Geometry or CNCJob object"
+
+#~ msgid ""
+#~ "If the selection of the object is done on the canvas by single click "
+#~ "instead, and the"
+#~ msgstr ""
+#~ "If the selection of the object is done on the canvas by single click "
+#~ "instead, and the"
+
+#~ msgid "and populate it even if it was out of focus"
+#~ msgstr "and populate it even if it was out of focus"
+
+#~ msgid "Gerber/Excellon Object"
+#~ msgstr "Gerber/Excellon Object"
+
+#~ msgid "Add tools (change param in Selected Tab)"
+#~ msgstr "Add tools (change param in Selected Tab)"
+
+#~ msgid "Generate CNCJob"
+#~ msgstr "Generate CNCJob"
+
+#~ msgid ""
+#~ "Verify GCode (through Edit CNC Code) and/or append/prepend to GCode "
+#~ "(again, done in"
+#~ msgstr ""
+#~ "Verify GCode (through Edit CNC Code) and/or append/prepend to GCode "
+#~ "(again, done in"
+
+#~ msgid "Shortcuts List"
+#~ msgstr "Shortcuts List"
+
+#~ msgid "or through"
+#~ msgstr "or through"
+
+#~ msgid "own key shortcut"
+#~ msgstr "own key shortcut"
+
+#~ msgid "polygons"
+#~ msgstr "polygons"
+
+#~ msgid "geo"
+#~ msgstr "geo"
+
+#~ msgid "Stop"
+#~ msgstr "Stop"
+
+#~ msgid "Generating panel ..."
+#~ msgstr "Generating panel ..."
+
+#~ msgid "Spawning copies"
+#~ msgstr "Spawning copies"
+
+#~ msgid "Parsing tool"
+#~ msgstr "Parsing tool"
+
+#~ msgid ""
+#~ " Wrong value format for self.defaults[\"feedrate_probe\"] or self."
+#~ "options[\"feedrate_probe\"]"
+#~ msgstr ""
+#~ " Wrong value format for self.defaults[\"feedrate_probe\"] or self."
+#~ "options[\"feedrate_probe\"]"
+
+#~ msgid "Wrong optimization type selected."
+#~ msgstr "Wrong optimization type selected."
+
+#~ msgid "FILE ASSOCIATIONS"
+#~ msgstr "FILE ASSOCIATIONS"
+
+#~ msgid "MH"
+#~ msgstr "MH"
+
+#~ msgid "Feedrate (Plunge)"
+#~ msgstr "Feedrate (Plunge)"
+
+#~ msgid ""
+#~ "Parameters used to create a CNC Job object\n"
+#~ "for this drill object that are shown when App Level is Advanced."
+#~ msgstr ""
+#~ "Parameters used to create a CNC Job object\n"
+#~ "for this drill object that are shown when App Level is Advanced."
+
+#~ msgid ""
+#~ "Parameters to create a CNC Job object\n"
+#~ "tracing the contours of a Geometry object."
+#~ msgstr ""
+#~ "Parameters to create a CNC Job object\n"
+#~ "tracing the contours of a Geometry object."
+
+#~ msgid "Manufacturing"
+#~ msgstr "Manufacturing"
+
+#~ msgid "Function"
+#~ msgstr "Function"
+
+#~ msgid ""
+#~ "Juan Pablo Caram <BR><BR>Denis Hayrullin<BR>Kamil Sopko<BR>Marius "
+#~ "Stanciu<BR>Matthieu Berthomé<BR><Br>and many others found <a href = "
+#~ "\"https://bitbucket.org/jpcgt/flatcam/pull-requests/?state=MERGED\">here."
+#~ "</a><BR><BR>"
+#~ msgstr ""
+#~ "Juan Pablo Caram <BR><BR>Denis Hayrullin<BR>Kamil Sopko<BR>Marius "
+#~ "Stanciu<BR>Matthieu Berthomé<BR><Br>and many others found <a href = "
+#~ "\"https://bitbucket.org/jpcgt/flatcam/pull-requests/?state=MERGED\">here."
+#~ "</a><BR><BR>"
+
+#~ msgid ""
+#~ "\n"
+#~ "<p><span style=\"font-size:{tsize}px\"><strong>Selected Tab - Choose an "
+#~ "Item from Project Tab</strong></span></p>\n"
+#~ "\n"
+#~ "<p><span style=\"font-size:{fsize}px\"><strong>Details</strong>:<br />\n"
+#~ "The normal flow when working in FlatCAM is the following:</span></p>\n"
+#~ "\n"
+#~ "<ol>\n"
+#~ "\t<li><span style=\"font-size:{fsize}px\">Loat/Import a Gerber, Excellon, "
+#~ "Gcode, DXF, Raster Image or SVG file into FlatCAM using either the "
+#~ "menu&#39;s, toolbars, key shortcuts or even dragging and dropping the "
+#~ "files on the GUI.<br />\n"
+#~ "\t<br />\n"
+#~ "\tYou can also load a <strong>FlatCAM project</strong> by double clicking "
+#~ "on the project file, drag &amp; drop of the file into the FLATCAM GUI or "
+#~ "through the menu/toolbar links offered within the app.</span><br />\n"
+#~ "\t&nbsp;</li>\n"
+#~ "\t<li><span style=\"font-size:{fsize}px\">Once an object is available in "
+#~ "the Project Tab, by selecting it and then focusing on <strong>SELECTED "
+#~ "TAB </strong>(more simpler is to double click the object name in the "
+#~ "Project Tab), <strong>SELECTED TAB </strong>will be updated with the "
+#~ "object properties according to it&#39;s kind: Gerber, Excellon, Geometry "
+#~ "or CNCJob object.<br />\n"
+#~ "\t<br />\n"
+#~ "\tIf the selection of the object is done on the canvas by single click "
+#~ "instead, and the <strong>SELECTED TAB</strong> is in focus, again the "
+#~ "object properties will be displayed into the Selected Tab. Alternatively, "
+#~ "double clicking on the object on the canvas will bring the "
+#~ "<strong>SELECTED TAB</strong> and populate it even if it was out of focus."
+#~ "<br />\n"
+#~ "\t<br />\n"
+#~ "\tYou can change the parameters in this screen and the flow direction is "
+#~ "like this:<br />\n"
+#~ "\t<br />\n"
+#~ "\t<strong>Gerber/Excellon Object</strong> -&gt; Change Param -&gt; "
+#~ "Generate Geometry -&gt;<strong> Geometry Object </strong>-&gt; Add tools "
+#~ "(change param in Selected Tab) -&gt; Generate CNCJob -&gt;<strong> CNCJob "
+#~ "Object </strong>-&gt; Verify GCode (through Edit CNC Code) and/or append/"
+#~ "prepend to GCode (again, done in <strong>SELECTED TAB)&nbsp;</strong>-"
+#~ "&gt; Save GCode</span></li>\n"
+#~ "</ol>\n"
+#~ "\n"
+#~ "<p><span style=\"font-size:{fsize}px\">A list of key shortcuts is "
+#~ "available through an menu entry in <strong>Help -&gt; Shortcuts List</"
+#~ "strong>&nbsp;or through it&#39;s own key shortcut: <strong>F3</strong>.</"
+#~ "span></p>\n"
+#~ "\n"
+#~ "        "
+#~ msgstr ""
+#~ "\n"
+#~ "<p><span style=\"font-size:{tsize}px\"><strong>Selected Tab - Choose an "
+#~ "Item from Project Tab</strong></span></p>\n"
+#~ "\n"
+#~ "<p><span style=\"font-size:{fsize}px\"><strong>Details</strong>:<br />\n"
+#~ "The normal flow when working in FlatCAM is the following:</span></p>\n"
+#~ "\n"
+#~ "<ol>\n"
+#~ "\t<li><span style=\"font-size:{fsize}px\">Loat/Import a Gerber, Excellon, "
+#~ "Gcode, DXF, Raster Image or SVG file into FlatCAM using either the "
+#~ "menu&#39;s, toolbars, key shortcuts or even dragging and dropping the "
+#~ "files on the GUI.<br />\n"
+#~ "\t<br />\n"
+#~ "\tYou can also load a <strong>FlatCAM project</strong> by double clicking "
+#~ "on the project file, drag &amp; drop of the file into the FLATCAM GUI or "
+#~ "through the menu/toolbar links offered within the app.</span><br />\n"
+#~ "\t&nbsp;</li>\n"
+#~ "\t<li><span style=\"font-size:{fsize}px\">Once an object is available in "
+#~ "the Project Tab, by selecting it and then focusing on <strong>SELECTED "
+#~ "TAB </strong>(more simpler is to double click the object name in the "
+#~ "Project Tab), <strong>SELECTED TAB </strong>will be updated with the "
+#~ "object properties according to it&#39;s kind: Gerber, Excellon, Geometry "
+#~ "or CNCJob object.<br />\n"
+#~ "\t<br />\n"
+#~ "\tIf the selection of the object is done on the canvas by single click "
+#~ "instead, and the <strong>SELECTED TAB</strong> is in focus, again the "
+#~ "object properties will be displayed into the Selected Tab. Alternatively, "
+#~ "double clicking on the object on the canvas will bring the "
+#~ "<strong>SELECTED TAB</strong> and populate it even if it was out of focus."
+#~ "<br />\n"
+#~ "\t<br />\n"
+#~ "\tYou can change the parameters in this screen and the flow direction is "
+#~ "like this:<br />\n"
+#~ "\t<br />\n"
+#~ "\t<strong>Gerber/Excellon Object</strong> -&gt; Change Param -&gt; "
+#~ "Generate Geometry -&gt;<strong> Geometry Object </strong>-&gt; Add tools "
+#~ "(change param in Selected Tab) -&gt; Generate CNCJob -&gt;<strong> CNCJob "
+#~ "Object </strong>-&gt; Verify GCode (through Edit CNC Code) and/or append/"
+#~ "prepend to GCode (again, done in <strong>SELECTED TAB)&nbsp;</strong>-"
+#~ "&gt; Save GCode</span></li>\n"
+#~ "</ol>\n"
+#~ "\n"
+#~ "<p><span style=\"font-size:{fsize}px\">A list of key shortcuts is "
+#~ "available through an menu entry in <strong>Help -&gt; Shortcuts List</"
+#~ "strong>&nbsp;or through it&#39;s own key shortcut: <strong>F3</strong>.</"
+#~ "span></p>\n"
+#~ "\n"
+#~ "        "
+
+#~ msgid "Run Script ...\tShift+S"
+#~ msgstr "Run Script ...\tShift+S"
+
+#~| msgid ""
+#~| "<font size=8><B>FlatCAM</B></font><BR>Version {version} {beta} ({date}) "
+#~| "- {arch} <BR><BR>2D Computer-Aided Printed Circuit "
+#~| "Board<BR>Manufacturing.<BR><BR>(c) 2014-2019 <B>Juan Pablo Caram</"
+#~| "B><BR><BR><B> Main Contributors:</B><BR>Denis Hayrullin<BR>Kamil "
+#~| "Sopko<BR>Marius Stanciu<BR>Matthieu Berthomé<BR>and many others found <a "
+#~| "href = \"https://bitbucket.org/jpcgt/flatcam/pull-requests/?state=MERGED"
+#~| "\">here.</a><BR><BR>Development is done <a href = \"https://bitbucket."
+#~| "org/jpcgt/flatcam/src/Beta/\">here.</a><BR>DOWNLOAD area <a href = "
+#~| "\"https://bitbucket.org/jpcgt/flatcam/downloads/\">here.</a><BR>"
+#~ msgid ""
+#~ "<font size=8><B>FlatCAM</B></font><BR>Version {version} {beta} ({date}) - "
+#~ "{arch} <BR><BR>2D Computer-Aided Printed Circuit Board<BR>Manufacturing."
+#~ "<BR><BR><B> License: </B><BR>Licensed under MIT license (2014 - "
+#~ "2019)<BR>by (c)Juan Pablo Caram <BR><BR><B> Programmers:</B><BR>Denis "
+#~ "Hayrullin<BR>Kamil Sopko<BR>Marius Stanciu<BR>Matthieu Berthomé<BR>and "
+#~ "many others found <a href = \"https://bitbucket.org/jpcgt/flatcam/pull-"
+#~ "requests/?state=MERGED\">here.</a><BR><BR><B>Development</B> is done <a "
+#~ "href = \"https://bitbucket.org/jpcgt/flatcam/src/Beta/\">here.</"
+#~ "a><BR><b>DOWNLOAD</B> area <a href = \"https://bitbucket.org/jpcgt/"
+#~ "flatcam/downloads/\">here.</a><BR>"
+#~ msgstr ""
+#~ "<font size=8><B>FlatCAM</B></font><BR>Version {version} {beta} ({date}) - "
+#~ "{arch} <BR><BR>2D Computer-Aided Printed Circuit Board<BR>Manufacturing."
+#~ "<BR><BR><B> License: </B><BR>Licensed under MIT license (2014 - "
+#~ "2019)<BR>by (c)Juan Pablo Caram <BR><BR><B> Programmers:</B><BR>Denis "
+#~ "Hayrullin<BR>Kamil Sopko<BR>Marius Stanciu<BR>Matthieu Berthomé<BR>and "
+#~ "many others found <a href = \"https://bitbucket.org/jpcgt/flatcam/pull-"
+#~ "requests/?state=MERGED\">here.</a><BR><BR><B>Development</B> is done <a "
+#~ "href = \"https://bitbucket.org/jpcgt/flatcam/src/Beta/\">here.</"
+#~ "a><BR><b>DOWNLOAD</B> area <a href = \"https://bitbucket.org/jpcgt/"
+#~ "flatcam/downloads/\">here.</a><BR>"
+
+#~| msgid "[ERROR_NOTCL] Expected a GeometryObject, got %s"
+#~ msgid "Expected a GeometryObject, got %s"
+#~ msgstr "Expected a GeometryObject, got %s"
+
+#~ msgid "Saved to: %s"
+#~ msgstr "Saved to: %s"
+
+#~ msgid "[WARNING_NOTCL] Adding Tool cancelled ..."
+#~ msgstr "[WARNING_NOTCL] Adding Tool cancelled ..."
+
+#~ msgid "%s"
+#~ msgstr "%s"
+
+#~| msgid "[ERROR]App.on_view_source() -->%s"
+#~ msgid "App.on_view_source() -->"
+#~ msgstr "App.on_view_source() -->"
+
+#~ msgid "[success] Name changed from {old} to {new}"
+#~ msgstr "[success] Name changed from {old} to {new}"
+
+#~| msgid ""
+#~| "[ERROR_NOTCL] Failed.\n"
+#~| "%s"
+#~ msgid "[ERROR_NOTCL] %s"
+#~ msgstr "[ERROR_NOTCL] %s"
+
+#~ msgid "Editor %s"
+#~ msgstr "Editor %s"
+
+#~ msgid "[success] Done. Path completed."
+#~ msgstr "[success] Done. Path completed."
+
+#~ msgid "[success] Paint done."
+#~ msgstr "[success] Paint done."
+
+#~ msgid "About"
+#~ msgstr "About"
+
+#~| msgid ""
+#~| "<b>General Shortcut list</b><br>\n"
+#~| "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" "
+#~| "style=\"width:283px\">\n"
+#~| "                <tbody>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\" width=\"89\"><strong>F3</"
+#~| "strong></td>\n"
+#~| "                        <td width=\"194\"><span style=\"color:"
+#~| "#006400\"><strong>&nbsp;SHOW SHORTCUT LIST</strong></span></td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\">&nbsp;</td>\n"
+#~| "                        <td>&nbsp;</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>1</strong></td>\n"
+#~| "                        <td>&nbsp;Switch to Project Tab</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>2</strong></td>\n"
+#~| "                        <td>&nbsp;Switch to Selected Tab</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>3</strong></td>\n"
+#~| "                        <td>&nbsp;Switch to Tool Tab</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\">&nbsp;</td>\n"
+#~| "                        <td>&nbsp;</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~| "                        <td>&nbsp;New Gerber</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~| "                        <td>&nbsp;Edit Object (if selected)</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>G</strong></td>\n"
+#~| "                        <td>&nbsp;Grid On/Off</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~| "                        <td>&nbsp;Jump to Coordinates</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>L</strong></td>\n"
+#~| "                        <td>&nbsp;New Excellon</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~| "                        <td>&nbsp;Move Obj</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~| "                        <td>&nbsp;New Geometry</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>O</strong></td>\n"
+#~| "                        <td>&nbsp;Set Origin</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Q</strong></td>\n"
+#~| "                        <td>&nbsp;Change Units</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~| "                        <td>&nbsp;Open Properties Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~| "                        <td>&nbsp;Rotate by 90 degree CW</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~| "                        <td>&nbsp;Shell Toggle</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~| "                        <td>&nbsp;Add a Tool (when in Geometry Selected "
+#~| "Tab or in Tools NCC or Tools Paint)</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>V</strong></td>\n"
+#~| "                        <td>&nbsp;Zoom Fit</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>X</strong></td>\n"
+#~| "                        <td>&nbsp;Flip on X_axis</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Y</strong></td>\n"
+#~| "                        <td>&nbsp;Flip on Y_axis</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>&#39;-&#39;</strong></"
+#~| "td>\n"
+#~| "                        <td>&nbsp;Zoom Out</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>&#39;=&#39;</strong></"
+#~| "td>\n"
+#~| "                        <td>&nbsp;Zoom In</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\">&nbsp;</td>\n"
+#~| "                        <td>&nbsp;</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+A</strong></td>\n"
+#~| "                        <td>&nbsp;Select All</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+C</strong></td>\n"
+#~| "                        <td>&nbsp;Copy Obj</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
+#~| "                        <td>&nbsp;Open Excellon File</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+G</strong></td>\n"
+#~| "                        <td>&nbsp;Open Gerber File</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+N</strong></td>\n"
+#~| "                        <td>&nbsp;New Project</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
+#~| "                        <td>&nbsp;Measurement Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+O</strong></td>\n"
+#~| "                        <td>&nbsp;Open Project</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~| "                        <td>&nbsp;Save Project As</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Ctrl+F10</strong></"
+#~| "td>\n"
+#~| "                        <td>&nbsp;Toggle Plot Area</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\">&nbsp;</td>\n"
+#~| "                        <td>&nbsp;</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+C</strong></td>\n"
+#~| "                        <td>&nbsp;Copy Obj_Name</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+E</strong></td>\n"
+#~| "                        <td>&nbsp;Toggle Code Editor</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+G</strong></td>\n"
+#~| "                        <td>&nbsp;Toggle the axis</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+P</strong></td>\n"
+#~| "                        <td>&nbsp;Open Preferences Window</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+R</strong></td>\n"
+#~| "                        <td>&nbsp;Rotate by 90 degree CCW</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+S</strong></td>\n"
+#~| "                        <td>&nbsp;Run a Script</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+W</strong></td>\n"
+#~| "                        <td>&nbsp;Toggle the workspace</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
+#~| "                        <td>&nbsp;Skew on X axis</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
+#~| "                        <td>&nbsp;Skew on Y axis</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\">&nbsp;</td>\n"
+#~| "                        <td>&nbsp;</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+C</strong></td>\n"
+#~| "                        <td>&nbsp;Calculators Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+D</strong></td>\n"
+#~| "                        <td>&nbsp;2-Sided PCB Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+K</strong></td>\n"
+#~| "                        <td>&nbsp;Solder Paste Dispensing Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+L</strong></td>\n"
+#~| "                        <td>&nbsp;Film PCB Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
+#~| "                        <td>&nbsp;Non-Copper Clearing Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+P</strong></td>\n"
+#~| "                        <td>&nbsp;Paint Area Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+Q</strong></td>\n"
+#~| "                        <td>&nbsp;PDF Import Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~| "                        <td>&nbsp;Transformations Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+S</strong></td>\n"
+#~| "                        <td>&nbsp;View File Source</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+U</strong></td>\n"
+#~| "                        <td>&nbsp;Cutout PCB Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+1</strong></td>\n"
+#~| "                        <td>&nbsp;Enable all Plots</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+2</strong></td>\n"
+#~| "                        <td>&nbsp;Disable all Plots</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+3</strong></td>\n"
+#~| "                        <td>&nbsp;Disable Non-selected Plots</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Alt+F10</strong></td>\n"
+#~| "                        <td>&nbsp;Toggle Full Screen</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\">&nbsp;</td>\n"
+#~| "                        <td>&nbsp;</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>F1</strong></td>\n"
+#~| "                        <td>&nbsp;Open Online Manual</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>F4</strong></td>\n"
+#~| "                        <td>&nbsp;Open Online Tutorials</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~| "                        <td>&nbsp;Delete Object</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~| "                        <td>&nbsp;Alternate: Delete Tool</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>'`'</strong></td>\n"
+#~| "                        <td>&nbsp;(left to Key_1)Toogle Notebook Area "
+#~| "(Left Side)</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>SPACE</strong></td>\n"
+#~| "                        <td>&nbsp;En(Dis)able Obj Plot</td>\n"
+#~| "                    </tr>\n"
+#~| "                    <tr height=\"20\">\n"
+#~| "                        <td height=\"20\"><strong>Escape</strong></td>\n"
+#~| "                        <td>&nbsp;Deselects all objects</td>\n"
+#~| "                    </tr>\n"
+#~| "                </tbody>\n"
+#~| "            </table>\n"
+#~| "    \n"
+#~| "            "
+#~ msgid ""
+#~ "<b>General Shortcut list</b><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>F3</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\"><span style=\"color:"
+#~ "#006400\"><strong>&nbsp;SHOW SHORTCUT LIST</strong></span></td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>1</strong></td>\n"
+#~ "                        <td>&nbsp;Switch to Project Tab</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>2</strong></td>\n"
+#~ "                        <td>&nbsp;Switch to Selected Tab</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>3</strong></td>\n"
+#~ "                        <td>&nbsp;Switch to Tool Tab</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;New Gerber</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Edit Object (if selected)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>G</strong></td>\n"
+#~ "                        <td>&nbsp;Grid On/Off</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Coordinates</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>L</strong></td>\n"
+#~ "                        <td>&nbsp;New Excellon</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Obj</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;New Geometry</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>O</strong></td>\n"
+#~ "                        <td>&nbsp;Set Origin</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Q</strong></td>\n"
+#~ "                        <td>&nbsp;Change Units</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Open Properties Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate by 90 degree CW</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Shell Toggle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add a Tool (when in Geometry Selected "
+#~ "Tab or in Tools NCC or Tools Paint)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>V</strong></td>\n"
+#~ "                        <td>&nbsp;Zoom Fit</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>X</strong></td>\n"
+#~ "                        <td>&nbsp;Flip on X_axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Y</strong></td>\n"
+#~ "                        <td>&nbsp;Flip on Y_axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>&#39;-&#39;</strong></"
+#~ "td>\n"
+#~ "                        <td>&nbsp;Zoom Out</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>&#39;=&#39;</strong></"
+#~ "td>\n"
+#~ "                        <td>&nbsp;Zoom In</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+A</strong></td>\n"
+#~ "                        <td>&nbsp;Select All</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Obj</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
+#~ "                        <td>&nbsp;Open Excellon File</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+G</strong></td>\n"
+#~ "                        <td>&nbsp;Open Gerber File</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+N</strong></td>\n"
+#~ "                        <td>&nbsp;New Project</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
+#~ "                        <td>&nbsp;Measurement Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+O</strong></td>\n"
+#~ "                        <td>&nbsp;Open Project</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Project As</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+F10</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Plot Area</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Obj_Name</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+E</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Code Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+G</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle the axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+P</strong></td>\n"
+#~ "                        <td>&nbsp;Open Preferences Window</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+R</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate by 90 degree CCW</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+S</strong></td>\n"
+#~ "                        <td>&nbsp;Run a Script</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+W</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle the workspace</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
+#~ "                        <td>&nbsp;Skew on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Skew on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+C</strong></td>\n"
+#~ "                        <td>&nbsp;Calculators Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+D</strong></td>\n"
+#~ "                        <td>&nbsp;2-Sided PCB Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+K</strong></td>\n"
+#~ "                        <td>&nbsp;Solder Paste Dispensing Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+L</strong></td>\n"
+#~ "                        <td>&nbsp;Film PCB Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
+#~ "                        <td>&nbsp;Non-Copper Clearing Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+P</strong></td>\n"
+#~ "                        <td>&nbsp;Paint Area Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+Q</strong></td>\n"
+#~ "                        <td>&nbsp;PDF Import Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Transformations Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+S</strong></td>\n"
+#~ "                        <td>&nbsp;View File Source</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+U</strong></td>\n"
+#~ "                        <td>&nbsp;Cutout PCB Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+1</strong></td>\n"
+#~ "                        <td>&nbsp;Enable all Plots</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+2</strong></td>\n"
+#~ "                        <td>&nbsp;Disable all Plots</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+3</strong></td>\n"
+#~ "                        <td>&nbsp;Disable Non-selected Plots</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+F10</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Full Screen</td>\n"
+#~ "                    </tr>                 \n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+Alt+X</strong></"
+#~ "td>\n"
+#~ "                        <td>&nbsp;Abort current task (gracefully)</td>\n"
+#~ "                    </tr>                    \n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>F1</strong></td>\n"
+#~ "                        <td>&nbsp;Open Online Manual</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>F4</strong></td>\n"
+#~ "                        <td>&nbsp;Open Online Tutorials</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Object</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>'`'</strong></td>\n"
+#~ "                        <td>&nbsp;(left to Key_1)Toogle Notebook Area "
+#~ "(Left Side)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>SPACE</strong></td>\n"
+#~ "                        <td>&nbsp;En(Dis)able Obj Plot</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Escape</strong></td>\n"
+#~ "                        <td>&nbsp;Deselects all objects</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "    \n"
+#~ "            "
+#~ msgstr ""
+#~ "<b>General Shortcut list</b><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>F3</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\"><span style=\"color:"
+#~ "#006400\"><strong>&nbsp;SHOW SHORTCUT LIST</strong></span></td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>1</strong></td>\n"
+#~ "                        <td>&nbsp;Switch to Project Tab</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>2</strong></td>\n"
+#~ "                        <td>&nbsp;Switch to Selected Tab</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>3</strong></td>\n"
+#~ "                        <td>&nbsp;Switch to Tool Tab</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;New Gerber</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Edit Object (if selected)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>G</strong></td>\n"
+#~ "                        <td>&nbsp;Grid On/Off</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Coordinates</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>L</strong></td>\n"
+#~ "                        <td>&nbsp;New Excellon</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Obj</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;New Geometry</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>O</strong></td>\n"
+#~ "                        <td>&nbsp;Set Origin</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Q</strong></td>\n"
+#~ "                        <td>&nbsp;Change Units</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Open Properties Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate by 90 degree CW</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Shell Toggle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add a Tool (when in Geometry Selected "
+#~ "Tab or in Tools NCC or Tools Paint)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>V</strong></td>\n"
+#~ "                        <td>&nbsp;Zoom Fit</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>X</strong></td>\n"
+#~ "                        <td>&nbsp;Flip on X_axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Y</strong></td>\n"
+#~ "                        <td>&nbsp;Flip on Y_axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>&#39;-&#39;</strong></"
+#~ "td>\n"
+#~ "                        <td>&nbsp;Zoom Out</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>&#39;=&#39;</strong></"
+#~ "td>\n"
+#~ "                        <td>&nbsp;Zoom In</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+A</strong></td>\n"
+#~ "                        <td>&nbsp;Select All</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Obj</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
+#~ "                        <td>&nbsp;Open Excellon File</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+G</strong></td>\n"
+#~ "                        <td>&nbsp;Open Gerber File</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+N</strong></td>\n"
+#~ "                        <td>&nbsp;New Project</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
+#~ "                        <td>&nbsp;Measurement Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+O</strong></td>\n"
+#~ "                        <td>&nbsp;Open Project</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Project As</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+F10</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Plot Area</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Obj_Name</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+E</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Code Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+G</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle the axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+P</strong></td>\n"
+#~ "                        <td>&nbsp;Open Preferences Window</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+R</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate by 90 degree CCW</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+S</strong></td>\n"
+#~ "                        <td>&nbsp;Run a Script</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+W</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle the workspace</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
+#~ "                        <td>&nbsp;Skew on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Skew on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+C</strong></td>\n"
+#~ "                        <td>&nbsp;Calculators Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+D</strong></td>\n"
+#~ "                        <td>&nbsp;2-Sided PCB Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+K</strong></td>\n"
+#~ "                        <td>&nbsp;Solder Paste Dispensing Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+L</strong></td>\n"
+#~ "                        <td>&nbsp;Film PCB Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
+#~ "                        <td>&nbsp;Non-Copper Clearing Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+P</strong></td>\n"
+#~ "                        <td>&nbsp;Paint Area Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+Q</strong></td>\n"
+#~ "                        <td>&nbsp;PDF Import Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Transformations Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+S</strong></td>\n"
+#~ "                        <td>&nbsp;View File Source</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+U</strong></td>\n"
+#~ "                        <td>&nbsp;Cutout PCB Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+1</strong></td>\n"
+#~ "                        <td>&nbsp;Enable all Plots</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+2</strong></td>\n"
+#~ "                        <td>&nbsp;Disable all Plots</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+3</strong></td>\n"
+#~ "                        <td>&nbsp;Disable Non-selected Plots</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+F10</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Full Screen</td>\n"
+#~ "                    </tr>                 \n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+Alt+X</strong></"
+#~ "td>\n"
+#~ "                        <td>&nbsp;Abort current task (gracefully)</td>\n"
+#~ "                    </tr>                    \n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>F1</strong></td>\n"
+#~ "                        <td>&nbsp;Open Online Manual</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>F4</strong></td>\n"
+#~ "                        <td>&nbsp;Open Online Tutorials</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Object</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>'`'</strong></td>\n"
+#~ "                        <td>&nbsp;(left to Key_1)Toogle Notebook Area "
+#~ "(Left Side)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>SPACE</strong></td>\n"
+#~ "                        <td>&nbsp;En(Dis)able Obj Plot</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Escape</strong></td>\n"
+#~ "                        <td>&nbsp;Deselects all objects</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "    \n"
+#~ "            "
+
+#~ msgid ""
+#~ "<b>Editor Shortcut list</b><br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#0000ff\">GEOMETRY EDITOR</span></"
+#~ "strong><br>\n"
+#~ "    \n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Draw an Arc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;Buffer Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Within Add Arc will toogle the ARC "
+#~ "direction: CW or CCW</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Intersection Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>I</strong></td>\n"
+#~ "                        <td>&nbsp;Paint Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>K</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Corner Snap</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Within Add Arc will cycle through the "
+#~ "ARC modes</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Polygon</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>O</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Circle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Path</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Draw Rectangle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Substraction Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add Text Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>U</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Union Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>X</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Y</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Editor Transformation Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+X</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
+#~ "                        <td>&nbsp;Measurement Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+X</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Space</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate Geometry</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ENTER</strong></td>\n"
+#~ "                        <td>&nbsp;Finish drawing for certain tools</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Shape</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "            <br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#ff0000\">EXCELLON EDITOR</span></"
+#~ "strong><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Drill Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Add Drill</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>Q</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Slot Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Resize Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add a new Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>W</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Slot</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Tool(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "            <br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#00ff00\">GERBER EDITOR</span></"
+#~ "strong><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Pad Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;Buffer</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Add Disc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Add SemiDisc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;Add Region</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Add Pad</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Within Track & Region Tools will cycle "
+#~ "in REVERSE the bend modes</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Scale</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add Track</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Within Track & Region Tools will cycle "
+#~ "FORWARD the bend modes</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Apertures</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
+#~ "                        <td>&nbsp;Eraser Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                     <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+A</strong></td>\n"
+#~ "                        <td>&nbsp;Mark Area Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
+#~ "                        <td>&nbsp;Poligonize Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Transformation Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "                    "
+#~ msgstr ""
+#~ "<b>Editor Shortcut list</b><br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#0000ff\">GEOMETRY EDITOR</span></"
+#~ "strong><br>\n"
+#~ "    \n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Draw an Arc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;Buffer Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Within Add Arc will toogle the ARC "
+#~ "direction: CW or CCW</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Intersection Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>I</strong></td>\n"
+#~ "                        <td>&nbsp;Paint Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>K</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Corner Snap</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Within Add Arc will cycle through the "
+#~ "ARC modes</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Polygon</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>O</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Circle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Path</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Draw Rectangle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Substraction Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add Text Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>U</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Union Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>X</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Y</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Editor Transformation Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+X</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
+#~ "                        <td>&nbsp;Measurement Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+X</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Space</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate Geometry</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ENTER</strong></td>\n"
+#~ "                        <td>&nbsp;Finish drawing for certain tools</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Shape</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "            <br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#ff0000\">EXCELLON EDITOR</span></"
+#~ "strong><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Drill Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Add Drill</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>Q</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Slot Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Resize Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add a new Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>W</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Slot</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Tool(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "            <br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#00ff00\">GERBER EDITOR</span></"
+#~ "strong><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Pad Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;Buffer</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Add Disc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Add SemiDisc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;Add Region</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Add Pad</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Within Track & Region Tools will cycle "
+#~ "in REVERSE the bend modes</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Scale</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add Track</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Within Track & Region Tools will cycle "
+#~ "FORWARD the bend modes</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Apertures</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
+#~ "                        <td>&nbsp;Eraser Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                     <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+A</strong></td>\n"
+#~ "                        <td>&nbsp;Mark Area Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
+#~ "                        <td>&nbsp;Poligonize Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Transformation Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "                    "
+
+#~ msgid "[success] Done."
+#~ msgstr "[success] Done."
+
+#~ msgid "[WARNING_NOTCL] Cancelled."
+#~ msgstr "[WARNING_NOTCL] Cancelled."
+
+#~ msgid "[success] Added new tool with dia: {dia} {units}"
+#~ msgstr "[success] Added new tool with dia: {dia} {units}"
+
+#~ msgid "[WARNING_NOTCL] Application is saving the project. Please wait ..."
+#~ msgstr "[WARNING_NOTCL] Application is saving the project. Please wait ..."
+
+#~ msgid "<b>%s:</b>"
+#~ msgstr "<b>%s:</b>"
+
+#~ msgid "%s:"
+#~ msgstr "%s:"
+
+#~| msgid "[ERROR_NOTCL] Object not found: %s"
+#~ msgid "Object not found: %s"
+#~ msgstr "Object not found: %s"
+
+#~ msgid "[success] Opened: %s"
+#~ msgstr "[success] Opened: %s"
+
+#~ msgid "[success] Paint All Done."
+#~ msgstr "[success] Paint All Done."
+
+#~| msgid ""
+#~| "[ERROR] Could not do Paint All. Try a different combination of "
+#~| "parameters. Or a different Method of paint\n"
+#~| "%s"
+#~ msgid ""
+#~ "Could not do Paint All. Try a different combination of parameters. Or a "
+#~ "different Method of paint\n"
+#~ "%s"
+#~ msgstr ""
+#~ "Could not do Paint All. Try a different combination of parameters. Or a "
+#~ "different Method of paint\n"
+#~ "%s"
+
+#~| msgid "[success] Paint All Done."
+#~ msgid "[success] Paint Area Done."
+#~ msgstr "[success] Paint Area Done."
+
+#~ msgid "Generating panel ... Please wait."
+#~ msgstr "Generating panel ... Please wait."
+
+#~ msgid "...proccessing... [%s]"
+#~ msgstr "...proccessing... [%s]"
+
+#~ msgid "Parsing aperture %s geometry ..."
+#~ msgstr "Parsing aperture %s geometry ..."
+
+#~ msgid "[success] Skew on the %s axis done ..."
+#~ msgstr "[success] Skew on the %s axis done ..."
+
+#~ msgid "[ERROR_NOTCL] Could not load defaults file."
+#~ msgstr "[ERROR_NOTCL] Could not load defaults file."
+
+#~ msgid "[ERROR_NOTCL] Failed to parse defaults file."
+#~ msgstr "[ERROR_NOTCL] Failed to parse defaults file."
+
+#~ msgid "[ERROR_NOTCL] An internal error has ocurred. See shell.\n"
+#~ msgstr "[ERROR_NOTCL] An internal error has ocurred. See shell.\n"
+
+#~ msgid "[success] Defaults saved."
+#~ msgstr "[success] Defaults saved."
+
+#~ msgid "[success] Converted units to %s"
+#~ msgstr "[success] Converted units to %s"
+
+#~ msgid "[WARNING_NOTCL] Export Code cancelled."
+#~ msgstr "[WARNING_NOTCL] Export Code cancelled."
+
+#~ msgid "[success] Origin set ..."
+#~ msgstr "[success] Origin set ..."
+
+#~ msgid "[success] Skew on X axis done."
+#~ msgstr "[success] Skew on X axis done."
+
+#~ msgid "[success] Skew on Y axis done."
+#~ msgstr "[success] Skew on Y axis done."
+
+#~ msgid "[success] New Grid added ..."
+#~ msgstr "[success] New Grid added ..."
+
+#~ msgid "[WARNING_NOTCL] Open Gerber cancelled."
+#~ msgstr "[WARNING_NOTCL] Open Gerber cancelled."
+
+#~ msgid "[WARNING_NOTCL] Open G-Code cancelled."
+#~ msgstr "[WARNING_NOTCL] Open G-Code cancelled."
+
+#~ msgid "[WARNING_NOTCL] Open Project cancelled."
+#~ msgstr "[WARNING_NOTCL] Open Project cancelled."
+
+#~ msgid "[WARNING_NOTCL] Open Config cancelled."
+#~ msgstr "[WARNING_NOTCL] Open Config cancelled."
+
+#~ msgid "[WARNING_NOTCL] No object selected."
+#~ msgstr "[WARNING_NOTCL] No object selected."
+
+#~ msgid "[WARNING_NOTCL] Export SVG cancelled."
+#~ msgstr "[WARNING_NOTCL] Export SVG cancelled."
+
+#~ msgid "[WARNING_NOTCL] Export Excellon cancelled."
+#~ msgstr "[WARNING_NOTCL] Export Excellon cancelled."
+
+#~ msgid "[WARNING_NOTCL] Export Gerber cancelled."
+#~ msgstr "[WARNING_NOTCL] Export Gerber cancelled."
+
+#~ msgid "[WARNING_NOTCL] Export DXF cancelled."
+#~ msgstr "[WARNING_NOTCL] Export DXF cancelled."
+
+#~ msgid "[WARNING_NOTCL] Open SVG cancelled."
+#~ msgstr "[WARNING_NOTCL] Open SVG cancelled."
+
+#~ msgid "[WARNING_NOTCL] Open DXF cancelled."
+#~ msgstr "[WARNING_NOTCL] Open DXF cancelled."
+
+#~ msgid "[WARNING_NOTCL] No object Box. Using instead %s"
+#~ msgstr "[WARNING_NOTCL] No object Box. Using instead %s"
+
+#~ msgid "[ERROR_NOTCL] Failed to parse file: {name}. {error}"
+#~ msgstr "[ERROR_NOTCL] Failed to parse file: {name}. {error}"
+
+#~ msgid "[ERROR_NOTCL] An internal error has occurred. See shell.\n"
+#~ msgstr "[ERROR_NOTCL] An internal error has occurred. See shell.\n"
+
+#~ msgid "[ERROR_NOTCL] Failed to verify project file: %s. Retry to save it."
+#~ msgstr "[ERROR_NOTCL] Failed to verify project file: %s. Retry to save it."
+
+#~ msgid ""
+#~ "[ERROR_NOTCL] Failed to parse saved project file: %s. Retry to save it."
+#~ msgstr ""
+#~ "[ERROR_NOTCL] Failed to parse saved project file: %s. Retry to save it."
+
+#~ msgid "[ERROR_NOTCL] Failed to save project file: %s. Retry to save it."
+#~ msgstr "[ERROR_NOTCL] Failed to save project file: %s. Retry to save it."
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered, use a number."
+#~ msgstr "[ERROR_NOTCL] Wrong value format entered, use a number."
+
+#~ msgid "[ERROR_NOTCL] Cancelled. Empty file, it has no geometry..."
+#~ msgstr "[ERROR_NOTCL] Cancelled. Empty file, it has no geometry..."
+
+#~ msgid "[WARNING_NOTCL] Export Machine Code cancelled ..."
+#~ msgstr "[WARNING_NOTCL] Export Machine Code cancelled ..."
+
+#~ msgid "[WARNING_NOTCL] No such file or directory"
+#~ msgstr "[WARNING_NOTCL] No such file or directory"
+
+#~ msgid "[ERROR_NOTCL] The value is mistyped. Check the value. %s"
+#~ msgstr "[ERROR_NOTCL] The value is mistyped. Check the value. %s"
+
+#~ msgid "[ERROR_NOTCL] Cancelled."
+#~ msgstr "[ERROR_NOTCL] Cancelled."
+
+#~ msgid "Tool Dia:"
+#~ msgstr "Tool Dia:"
+
+#~ msgid "Nr of drills:"
+#~ msgstr "Nr of drills:"
+
+#~ msgid "Direction:"
+#~ msgstr "Direction:"
+
+#~ msgid "Pitch:"
+#~ msgstr "Pitch:"
+
+#~ msgid "Length:"
+#~ msgstr "Length:"
+
+#~ msgid "Nr of slots:"
+#~ msgstr "Nr of slots:"
+
+#~ msgid "[success] Deleted tool with dia: {del_dia} {units}"
+#~ msgstr "[success] Deleted tool with dia: {del_dia} {units}"
+
+#~ msgid "Tool dia:"
+#~ msgstr "Tool dia:"
+
+#~ msgid "Overlap Rate:"
+#~ msgstr "Overlap Rate:"
+
+#~ msgid "Method:"
+#~ msgstr "Method:"
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered for Rotate, use a number."
+#~ msgstr "[ERROR_NOTCL] Wrong value format entered for Rotate, use a number."
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered for Skew X, use a number."
+#~ msgstr "[ERROR_NOTCL] Wrong value format entered for Skew X, use a number."
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered for Skew Y, use a number."
+#~ msgstr "[ERROR_NOTCL] Wrong value format entered for Skew Y, use a number."
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered for Scale X, use a number."
+#~ msgstr "[ERROR_NOTCL] Wrong value format entered for Scale X, use a number."
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered for Scale Y, use a number."
+#~ msgstr "[ERROR_NOTCL] Wrong value format entered for Scale Y, use a number."
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered for Offset X, use a number."
+#~ msgstr ""
+#~ "[ERROR_NOTCL] Wrong value format entered for Offset X, use a number."
+
+#~ msgid "[ERROR_NOTCL] Wrong value format entered for Offset Y, use a number."
+#~ msgstr ""
+#~ "[ERROR_NOTCL] Wrong value format entered for Offset Y, use a number."
+
+#~ msgid "[success] Flip on the Y axis done ..."
+#~ msgstr "[success] Flip on the Y axis done ..."
+
+#~ msgid "[success] Flip on the X axis done ..."
+#~ msgstr "[success] Flip on the X axis done ..."
+
+#~ msgid "[success] Offset on the %s axis done ..."
+#~ msgstr "[success] Offset on the %s axis done ..."
+
+#~ msgid ""
+#~ "[WARNING_NOTCL] Editing MultiGeo Geometry, tool: {tool} with diameter: "
+#~ "{dia}"
+#~ msgstr ""
+#~ "[WARNING_NOTCL] Editing MultiGeo Geometry, tool: {tool} with diameter: "
+#~ "{dia}"
+
+#~ msgid "Clear GUI Settings:"
+#~ msgstr "Clear GUI Settings:"
+
+#~ msgid "Duration:"
+#~ msgstr "Duration:"
+
+#~ msgid "Fast Plunge:"
+#~ msgstr "Fast Plunge:"
+
+#~ msgid "Linear Dir.:"
+#~ msgstr "Linear Dir.:"
+
+#~ msgid "Plot kind:"
+#~ msgstr "Plot kind:"
+
+#~ msgid ""
+#~ "Select from the Tools Table above\n"
+#~ "the tools you want to include."
+#~ msgstr ""
+#~ "Select from the Tools Table above\n"
+#~ "the tools you want to include."
+
+#~ msgid ""
+#~ "[WARNING_NOTCL] Tool Diameter is zero value. Change it to a positive real "
+#~ "number."
+#~ msgstr ""
+#~ "[WARNING_NOTCL] Tool Diameter is zero value. Change it to a positive real "
+#~ "number."
+
+#~ msgid "[success] Gerber %s was mirrored..."
+#~ msgstr "[success] Gerber %s was mirrored..."
+
+#~ msgid "[success] Excellon %s was mirrored..."
+#~ msgstr "[success] Excellon %s was mirrored..."
+
+#~ msgid "[success] Geometry %s was mirrored..."
+#~ msgstr "[success] Geometry %s was mirrored..."
+
+#~ msgid "[WARNING_NOTCL] No object(s) selected."
+#~ msgstr "[WARNING_NOTCL] No object(s) selected."
+
+#~ msgid "[success] %s object was moved ..."
+#~ msgstr "[success] %s object was moved ..."
+
+#~ msgid "[WARNING_NOTCL] Object(s) not selected"
+#~ msgstr "[WARNING_NOTCL] Object(s) not selected"
+
+#~ msgid "[WARNING_NOTCL] Buffering ..."
+#~ msgstr "[WARNING_NOTCL] Buffering ..."
+
+#~ msgid "[success] Non-Copper Clearing with ToolDia = %s started."
+#~ msgstr "[success] Non-Copper Clearing with ToolDia = %s started."
+
+#~ msgid "[ERROR_NOTCL] NCCTool.clear_non_copper() --> %s"
+#~ msgstr "[ERROR_NOTCL] NCCTool.clear_non_copper() --> %s"
+
+#~ msgid "[success] NCC Tool finished."
+#~ msgstr "[success] NCC Tool finished."
+
+#~ msgid ""
+#~ "[WARNING_NOTCL] NCC Tool finished but some PCB features could not be "
+#~ "cleared. Check the result."
+#~ msgstr ""
+#~ "[WARNING_NOTCL] NCC Tool finished but some PCB features could not be "
+#~ "cleared. Check the result."
+
+#~ msgid "[success] Non-Copper Rest Clearing with ToolDia = %s started."
+#~ msgstr "[success] Non-Copper Rest Clearing with ToolDia = %s started."
+
+#~ msgid "[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s"
+#~ msgstr "[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s"
+
+#~ msgid ""
+#~ "[ERROR_NOTCL] NCC Tool finished but could not clear the object with "
+#~ "current settings."
+#~ msgstr ""
+#~ "[ERROR_NOTCL] NCC Tool finished but could not clear the object with "
+#~ "current settings."
+
+#~ msgid "[WARNING_NOTCL] Open PDF cancelled."
+#~ msgstr "[WARNING_NOTCL] Open PDF cancelled."
+
+#~ msgid "[ERROR_NOTCL] Open PDF file failed."
+#~ msgstr "[ERROR_NOTCL] Open PDF file failed."
+
+#~ msgid "[success] Rendered: %s"
+#~ msgstr "[success] Rendered: %s"
+
+#~ msgid ""
+#~ "How to select the polygons to paint.<BR>Options:<BR>- <B>Single Polygons</"
+#~ "B>: left mouse click on the polygon to be painted.<BR>- <B>Area "
+#~ "Selection</B>: left mouse click to start selection of the area to be "
+#~ "painted.<BR>- <B>All Polygons</B>: paint all polygons.<BR>- <B>Reference "
+#~ "Object</B>: paint an area described by an external reference object."
+#~ msgstr ""
+#~ "How to select the polygons to paint.<BR>Options:<BR>- <B>Single Polygons</"
+#~ "B>: left mouse click on the polygon to be painted.<BR>- <B>Area "
+#~ "Selection</B>: left mouse click to start selection of the area to be "
+#~ "painted.<BR>- <B>All Polygons</B>: paint all polygons.<BR>- <B>Reference "
+#~ "Object</B>: paint an area described by an external reference object."
+
+#~ msgid ""
+#~ "[ERROR_NOTCL] There is no Painting Geometry in the file.\n"
+#~ "Usually it means that the tool diameter is too big for the painted "
+#~ "geometry.\n"
+#~ "Change the painting parameters and try again."
+#~ msgstr ""
+#~ "[ERROR_NOTCL] There is no Painting Geometry in the file.\n"
+#~ "Usually it means that the tool diameter is too big for the painted "
+#~ "geometry.\n"
+#~ "Change the painting parameters and try again."
+
+#~ msgid "[WARNING_NOTCL]No object Box. Using instead %s"
+#~ msgstr "[WARNING_NOTCL]No object Box. Using instead %s"
+
+#~ msgid "[success] Imported: %s"
+#~ msgstr "[success] Imported: %s"
+
+#~ msgid "[ERROR_NOTCL] Generating new object failed."
+#~ msgstr "[ERROR_NOTCL] Generating new object failed."
+
+#~ msgid "[success] Created: %s"
+#~ msgstr "[success] Created: %s"
+
+#~ msgid "[success] Rotate done ..."
+#~ msgstr "[success] Rotate done ..."
+
+#~ msgid ""
+#~ "When choosing the 'Itself' option the non copper clearing extent\n"
+#~ "is based on the object that is copper cleared.\n"
+#~ " Choosing the 'Box' option will do non copper clearing within the box\n"
+#~ "specified by another object different than the one that is copper cleared."
+#~ msgstr ""
+#~ "When choosing the 'Itself' option the non copper clearing extent\n"
+#~ "is based on the object that is copper cleared.\n"
+#~ " Choosing the 'Box' option will do non copper clearing within the box\n"
+#~ "specified by another object different than the one that is copper cleared."
+
+#~ msgid ""
+#~ "How to select the polygons to paint.<BR>Options:<BR>- <B>Single</B>: left "
+#~ "mouse click on the polygon to be painted.<BR>- <B>Area</B>: left mouse "
+#~ "click to start selection of the area to be painted.<BR>- <B>All</B>: "
+#~ "paint all polygons.<BR>- <B>Ref</B>: paint an area described by an "
+#~ "external reference object."
+#~ msgstr ""
+#~ "How to select the polygons to paint.<BR>Options:<BR>- <B>Single</B>: left "
+#~ "mouse click on the polygon to be painted.<BR>- <B>Area</B>: left mouse "
+#~ "click to start selection of the area to be painted.<BR>- <B>All</B>: "
+#~ "paint all polygons.<BR>- <B>Ref</B>: paint an area described by an "
+#~ "external reference object."
+
+#~ msgid "Geometry object to be painted.                        "
+#~ msgstr "Geometry object to be painted.                        "
+
+#~ msgid ""
+#~ "After clicking here, click inside<BR>the polygon you wish to be painted "
+#~ "if <B>Single</B> is selected.<BR>If <B>Area</B> is selected, then the "
+#~ "selection of the area to be painted<BR>will be initiated by a first click "
+#~ "and finished by the second mouse click.<BR>If <B>All</B>  is selected "
+#~ "then the Paint will start after click.<BR>If <B>Ref</B>  is selected then "
+#~ "the Paint will start after click,<BR>and the painted area will be "
+#~ "described by a selected object.<BR>A new Geometry object with the tool "
+#~ "paths will be created."
+#~ msgstr ""
+#~ "After clicking here, click inside<BR>the polygon you wish to be painted "
+#~ "if <B>Single</B> is selected.<BR>If <B>Area</B> is selected, then the "
+#~ "selection of the area to be painted<BR>will be initiated by a first click "
+#~ "and finished by the second mouse click.<BR>If <B>All</B>  is selected "
+#~ "then the Paint will start after click.<BR>If <B>Ref</B>  is selected then "
+#~ "the Paint will start after click,<BR>and the painted area will be "
+#~ "described by a selected object.<BR>A new Geometry object with the tool "
+#~ "paths will be created."
+
+#~ msgid "<b>Apertures:</b>"
+#~ msgstr "<b>Apertures:</b>"
+
+#~ msgid "Aperture Code:"
+#~ msgstr "Aperture Code:"
+
+#~ msgid "<b>Languages:</b>"
+#~ msgstr "<b>Languages:</b>"
+
+#~ msgid "Width (# passes):"
+#~ msgstr "Width (# passes):"
+
+#~| msgid "<b>Clear non-copper:</b>"
+#~ msgid "Clear non-copper"
+#~ msgstr "Clear non-copper"
+
+#~ msgid "Rounded corners"
+#~ msgstr "Rounded corners"
+
+#~ msgid ""
+#~ "Creates a Geometry objects with polygons\n"
+#~ "covering the copper-free areas of the PCB."
+#~ msgstr ""
+#~ "Creates a Geometry objects with polygons\n"
+#~ "covering the copper-free areas of the PCB."
+
+#~ msgid "<b>Bounding Box:</b>"
+#~ msgstr "<b>Bounding Box:</b>"
+
+#~ msgid "<b>Units</b>:"
+#~ msgstr "<b>Units</b>:"
+
+#~ msgid "<b>Zeros</b>:"
+#~ msgstr "<b>Zeros</b>:"
+
+#~ msgid "INCH:"
+#~ msgstr "INCH:"
+
+#~ msgid "Tool change:"
+#~ msgstr "Tool change:"
+
+#~ msgid "Toolchange Z position."
+#~ msgstr "Toolchange Z position."
+
+#~ msgid ""
+#~ "Tool speed while drilling\n"
+#~ "(in units per minute)."
+#~ msgstr ""
+#~ "Tool speed while drilling\n"
+#~ "(in units per minute)."
+
+#~ msgid "<b>Gcode:    </b>"
+#~ msgstr "<b>Gcode:    </b>"
+
+#~ msgid "Offset Z:"
+#~ msgstr "Offset Z:"
+
+#~ msgid "<b>Slots:</b>"
+#~ msgstr "<b>Slots:</b>"
+
+#~ msgid "<b>Create CNC Job:</b>"
+#~ msgstr "<b>Create CNC Job:</b>"
+
+#~ msgid "Multidepth"
+#~ msgstr "Multidepth"
+
+#~ msgid "Multidepth usage: True or False."
+#~ msgstr "Multidepth usage: True or False."
+
+#~ msgid ""
+#~ "The preprocessor file that dictates\n"
+#~ "Machine Code output."
+#~ msgstr ""
+#~ "The preprocessor file that dictates\n"
+#~ "Machine Code output."
+
+#~ msgid "Display Annotation:"
+#~ msgstr "Display Annotation:"
+
+#~ msgid ""
+#~ "Type here any G-Code commands you would\n"
+#~ "like to be executed when Toolchange event is encountered.\n"
+#~ "This will constitute a Custom Toolchange GCode,\n"
+#~ "or a Toolchange Macro."
+#~ msgstr ""
+#~ "Type here any G-Code commands you would\n"
+#~ "like to be executed when Toolchange event is encountered.\n"
+#~ "This will constitute a Custom Toolchange GCode,\n"
+#~ "or a Toolchange Macro."
+
+#~ msgid ""
+#~ "If checked, use 'rest machining'.\n"
+#~ "Basically it will clear 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.\n"
+#~ "If not checked, use the standard algorithm."
+#~ msgstr ""
+#~ "If checked, use 'rest machining'.\n"
+#~ "Basically it will clear 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.\n"
+#~ "If not checked, use the standard algorithm."
+
+#~ msgid "Offset:"
+#~ msgstr "Offset:"
+
+#~ msgid ""
+#~ "Distance from objects at which\n"
+#~ "to draw the cutout."
+#~ msgstr ""
+#~ "Distance from objects at which\n"
+#~ "to draw the cutout."
+
+#~ msgid ""
+#~ "Size of the gaps in the toolpath\n"
+#~ "that will remain to hold the\n"
+#~ "board in place."
+#~ msgstr ""
+#~ "Size of the gaps in the toolpath\n"
+#~ "that will remain to hold the\n"
+#~ "board in place."
+
+#~ msgid "Create a convex shape surrounding the entire PCB."
+#~ msgstr "Create a convex shape surrounding the entire PCB."
+
+#~ msgid ""
+#~ "The axis should pass through a <b>point</b> or cut\n"
+#~ " a specified <b>box</b> (in a Geometry object) in \n"
+#~ "the middle."
+#~ msgstr ""
+#~ "The axis should pass through a <b>point</b> or cut\n"
+#~ " a specified <b>box</b> (in a Geometry object) in \n"
+#~ "the middle."
+
+#~ msgid "Panel Type:"
+#~ msgstr "Panel Type:"
+
+#~ msgid "Tip angle:"
+#~ msgstr "Tip angle:"
+
+#~ msgid "Angle for rotation. In degrees."
+#~ msgstr "Angle for rotation. In degrees."
+
+#~ msgid "Angle for Skew/Shear on X axis. In degrees."
+#~ msgstr "Angle for Skew/Shear on X axis. In degrees."
+
+#~ msgid "Angle for Skew/Shear on Y axis. In degrees."
+#~ msgstr "Angle for Skew/Shear on Y axis. In degrees."
+
+#~ msgid "XY Toolchange:"
+#~ msgstr "XY Toolchange:"
+
+#~ msgid "PostProcessors:"
+#~ msgstr "PostProcessors:"
+
+#~ msgid "<b>Scale:</b>"
+#~ msgstr "<b>Scale:</b>"
+
+#~ msgid "<b>Offset:</b>"
+#~ msgstr "<b>Offset:</b>"
+
+#~ msgid "<b>Tools Table</b>"
+#~ msgstr "<b>Tools Table</b>"
+
+#~ msgid ""
+#~ "Tool height just before starting the work.\n"
+#~ "Delete the value if you don't need this feature."
+#~ msgstr ""
+#~ "Tool height just before starting the work.\n"
+#~ "Delete the value if you don't need this feature."
+
+#~ msgid ""
+#~ "Z-axis position (height) for\n"
+#~ "the last move."
+#~ msgstr ""
+#~ "Z-axis position (height) for\n"
+#~ "the last move."
+
+#~ msgid ""
+#~ "The json file that dictates\n"
+#~ "gcode output."
+#~ msgstr ""
+#~ "The json file that dictates\n"
+#~ "gcode output."
+
+#~ msgid "<b>Type:    </b>"
+#~ msgstr "<b>Type:    </b>"
+
+#~ msgid "Drills Tool dia:"
+#~ msgstr "Drills Tool dia:"
+
+#~ msgid "Slots Tool dia:"
+#~ msgstr "Slots Tool dia:"
+
+#~ msgid "<b>Tool Dia:</b>"
+#~ msgstr "<b>Tool Dia:</b>"
+
+#~ msgid "<b>Tool Data</b>"
+#~ msgstr "<b>Tool Data</b>"
+
+#~ msgid ""
+#~ "This is the height (Z) at which the CNC\n"
+#~ "will go as the last move."
+#~ msgstr ""
+#~ "This is the height (Z) at which the CNC\n"
+#~ "will go as the last move."
+
+#~ msgid "Feed Rate Z (Plunge):"
+#~ msgstr "Feed Rate Z (Plunge):"
+
+#~ msgid ""
+#~ "Cutting speed in the Z\n"
+#~ "plane in units per minute"
+#~ msgstr ""
+#~ "Cutting speed in the Z\n"
+#~ "plane in units per minute"
+
+#~ msgid ""
+#~ "Cutting speed in the XY\n"
+#~ "plane in units per minute\n"
+#~ "(in units per minute).\n"
+#~ "This is for the rapid move G00.\n"
+#~ "It is useful only for Marlin,\n"
+#~ "ignore for any other cases."
+#~ msgstr ""
+#~ "Cutting speed in the XY\n"
+#~ "plane in units per minute\n"
+#~ "(in units per minute).\n"
+#~ "This is for the rapid move G00.\n"
+#~ "It is useful only for Marlin,\n"
+#~ "ignore for any other cases."
+
+#~ msgid "Cut over 1st pt"
+#~ msgstr "Cut over 1st pt"
+
+#~ msgid "<b>Paint Area:</b>"
+#~ msgstr "<b>Paint Area:</b>"
+
+#~ msgid "<b>CNC Tools Table</b>"
+#~ msgstr "<b>CNC Tools Table</b>"
+
+#~ msgid ""
+#~ "Type here any G-Code commands you would\n"
+#~ "like to add to the beginning of the generated file."
+#~ msgstr ""
+#~ "Type here any G-Code commands you would\n"
+#~ "like to add to the beginning of the generated file."
+
+#~ msgid ""
+#~ "This is the diameter of the tool tip.\n"
+#~ "The manufacturer specifies it."
+#~ msgstr ""
+#~ "This is the diameter of the tool tip.\n"
+#~ "The manufacturer specifies it."
+
+#~ msgid "Object:"
+#~ msgstr "Object:"
+
+#~ msgid "Units:"
+#~ msgstr "Units:"
+
+#~ msgid ""
+#~ "- 'Itself': the non copper clearing extent\n"
+#~ "is based on the object that is copper cleared.\n"
+#~ " - 'Box': will do non copper clearing within the box\n"
+#~ "specified by the object selected in the Ref. Object combobox."
+#~ msgstr ""
+#~ "- 'Itself': the non copper clearing extent\n"
+#~ "is based on the object that is copper cleared.\n"
+#~ " - 'Box': will do non copper clearing within the box\n"
+#~ "specified by the object selected in the Ref. Object combobox."
+
+#~ msgid "Geometry:"
+#~ msgstr "Geometry:"
+
+#~ msgid ""
+#~ "Scale the selected object(s)\n"
+#~ "using the Scale Factor X for both axis."
+#~ msgstr ""
+#~ "Scale the selected object(s)\n"
+#~ "using the Scale Factor X for both axis."
+
+#~ msgid "<b>Excellon Format:</b>"
+#~ msgstr "<b>Excellon Format:</b>"
+
+#~ msgid "<b>Tools:</b>"
+#~ msgstr "<b>Tools:</b>"
+
+#~ msgid "<b>Export G-Code:</b>"
+#~ msgstr "<b>Export G-Code:</b>"
+
+#~ msgid "How to select the polygons to paint."
+#~ msgstr "How to select the polygons to paint."
+
+#~ msgid "<b>V-Shape Tool Calculator:</b>"
+#~ msgstr "<b>V-Shape Tool Calculator:</b>"
+
+#~ msgid "<b>ElectroPlating Calculator:</b>"
+#~ msgstr "<b>ElectroPlating Calculator:</b>"
+
+#~ msgid "<b>Name:</b>"
+#~ msgstr "<b>Name:</b>"
+
+#~ msgid "<b>Plot kind:</b>"
+#~ msgstr "<b>Plot kind:</b>"
+
+#~ msgid "<b>Display Annotation:</b>"
+#~ msgstr "<b>Display Annotation:</b>"
+
+#~ msgid "<b>GERBER:</b>"
+#~ msgstr "<b>GERBER:</b>"
+
+#~ msgid "<b>EXCELLON:</b>"
+#~ msgstr "<b>EXCELLON:</b>"
+
+#~ msgid "<b>GEOMETRY</b>:"
+#~ msgstr "<b>GEOMETRY</b>:"
+
+#~ msgid "<b>Panel Type:</b>"
+#~ msgstr "<b>Panel Type:</b>"
+
+#~ msgid "<b>Excellon format:</b>"
+#~ msgstr "<b>Excellon format:</b>"
+
+#~ msgid "<b>Gerber Objects</b>"
+#~ msgstr "<b>Gerber Objects</b>"
+
+#~ msgid "<b>Geometry Objects</b>"
+#~ msgstr "<b>Geometry Objects</b>"
+
+#~ msgid "Save &Defaults"
+#~ msgstr "Save &Defaults"
+
+#~ msgid "Tool dia:                   "
+#~ msgstr "Tool dia:                   "
+
+#~ msgid ""
+#~ "The diameter of the cutting\n"
+#~ "tool.."
+#~ msgstr ""
+#~ "The diameter of the cutting\n"
+#~ "tool.."
+
+#~ msgid "[WARNING_NOTCL] Move cancelled. No shape selected."
+#~ msgstr "[WARNING_NOTCL] Move cancelled. No shape selected."
+
+#~ msgid "Copy as &Geom"
+#~ msgstr "Copy as &Geom"
+
+#~ msgid ""
+#~ "Change the size of the selected apertures.\n"
+#~ "Factor by which to multiply\n"
+#~ "geometric features of this object."
+#~ msgstr ""
+#~ "Change the size of the selected apertures.\n"
+#~ "Factor by which to multiply\n"
+#~ "geometric features of this object."
+
+#~ msgid "Ap. Buffer Factor:"
+#~ msgstr "Ap. Buffer Factor:"
+
+#~ msgid ""
+#~ "Change the size of the selected apertures.\n"
+#~ "Factor by which to expand/shrink\n"
+#~ "geometric features of this object."
+#~ msgstr ""
+#~ "Change the size of the selected apertures.\n"
+#~ "Factor by which to expand/shrink\n"
+#~ "geometric features of this object."
+
+#~ msgid "Out"
+#~ msgstr "Out"
+
+#~ msgid "Pos"
+#~ msgstr "Pos"
+
+#~ msgid "Neg"
+#~ msgstr "Neg"
+
+#~ msgid "Solid   "
+#~ msgstr "Solid   "
+
+#~ msgid "M-Color   "
+#~ msgstr "M-Color   "
+
+#~ msgid "Click on CENTER ..."
+#~ msgstr "Click on CENTER ..."
+
+#~ msgid "[success] Done. Region completed."
+#~ msgstr "[success] Done. Region completed."
+
+#~ msgid "Del Aperture:"
+#~ msgstr "Del Aperture:"
+
+#~ msgid ""
+#~ "Delete a aperture in the aperture list.\n"
+#~ "It will delete also the associated geometry."
+#~ msgstr ""
+#~ "Delete a aperture in the aperture list.\n"
+#~ "It will delete also the associated geometry."
+
+#~ msgid "Save && Close Edit"
+#~ msgstr "Save && Close Edit"
+
+#~ msgid ""
+#~ "<b>Editor Shortcut list</b><br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#0000ff\">GEOMETRY EDITOR</span></"
+#~ "strong><br>\n"
+#~ "    \n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Draw an Arc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;Buffer Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Intersection Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>I</strong></td>\n"
+#~ "                        <td>&nbsp;Paint Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>K</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Corner Snap</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Polygon</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>O</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Circle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Path</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Draw Rectangle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Substraction Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add Text Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>U</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Union Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>X</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Y</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Editor Transformation Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+X</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
+#~ "                        <td>&nbsp;Measurement Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+X</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Space</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate Geometry</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ENTER</strong></td>\n"
+#~ "                        <td>&nbsp;Finish drawing for certain tools</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Shape</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "            <br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#ff0000\">EXCELLON EDITOR</span></"
+#~ "strong><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Drill Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Add Drill</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Resize Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add a new Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Tool(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "                    "
+#~ msgstr ""
+#~ "<b>Editor Shortcut list</b><br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#0000ff\">GEOMETRY EDITOR</span></"
+#~ "strong><br>\n"
+#~ "    \n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Draw an Arc</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>B</strong></td>\n"
+#~ "                        <td>&nbsp;Buffer Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>E</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Intersection Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>I</strong></td>\n"
+#~ "                        <td>&nbsp;Paint Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>K</strong></td>\n"
+#~ "                        <td>&nbsp;Toggle Corner Snap</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Geo Item</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>N</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Polygon</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>O</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Circle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>P</strong></td>\n"
+#~ "                        <td>&nbsp;Draw a Path</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Draw Rectangle</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>S</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Substraction Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add Text Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>U</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Union Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>X</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Y</strong></td>\n"
+#~ "                        <td>&nbsp;Flip shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
+#~ "                        <td>&nbsp;Editor Transformation Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+X</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on X axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Alt+Y</strong></td>\n"
+#~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
+#~ "                        <td>&nbsp;Measurement Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+X</strong></td>\n"
+#~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Space</strong></td>\n"
+#~ "                        <td>&nbsp;Rotate Geometry</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ENTER</strong></td>\n"
+#~ "                        <td>&nbsp;Finish drawing for certain tools</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Shape</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "            <br>\n"
+#~ "            <br>\n"
+#~ "            <strong><span style=\"color:#ff0000\">EXCELLON EDITOR</span></"
+#~ "strong><br>\n"
+#~ "            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style="
+#~ "\"width:283px\">\n"
+#~ "                <tbody>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\" width=\"89\"><strong>A</"
+#~ "strong></td>\n"
+#~ "                        <td width=\"194\">&nbsp;Add Drill Array</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>C</strong></td>\n"
+#~ "                        <td>&nbsp;Copy Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>D</strong></td>\n"
+#~ "                        <td>&nbsp;Add Drill</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>J</strong></td>\n"
+#~ "                        <td>&nbsp;Jump to Location (x, y)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>M</strong></td>\n"
+#~ "                        <td>&nbsp;Move Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>R</strong></td>\n"
+#~ "                        <td>&nbsp;Resize Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>T</strong></td>\n"
+#~ "                        <td>&nbsp;Add a new Tool</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Delete Drill(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Del</strong></td>\n"
+#~ "                        <td>&nbsp;Alternate: Delete Tool(s)</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\">&nbsp;</td>\n"
+#~ "                        <td>&nbsp;</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>ESC</strong></td>\n"
+#~ "                        <td>&nbsp;Abort and return to Select</td>\n"
+#~ "                    </tr>\n"
+#~ "                    <tr height=\"20\">\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
+#~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
+#~ "                    </tr>\n"
+#~ "                </tbody>\n"
+#~ "            </table>\n"
+#~ "                    "
+
+#~ msgid "[ERROR_NOTCL]Could not load defaults file."
+#~ msgstr "[ERROR_NOTCL]Could not load defaults file."
+
+#~ msgid ""
+#~ "[ERROR_NOTCL] The aperture scale factor value is missing or wrong format."
+#~ msgstr ""
+#~ "[ERROR_NOTCL] The aperture scale factor value is missing or wrong format."
+
+#~ msgid "[WARNING_NOTCL]Export Machine Code cancelled ..."
+#~ msgstr "[WARNING_NOTCL]Export Machine Code cancelled ..."
+
+#~ msgid "[success] GUI settings deleted ..."
+#~ msgstr "[success] GUI settings deleted ..."
+
+#~ msgid "Buffer Factor:"
+#~ msgstr "Buffer Factor:"
+
+#~ msgid "<b>Generate new Gerber Object:</b>"
+#~ msgstr "<b>Generate new Gerber Object:</b>"
+
+#~ msgid "Will generate a new Gerber object from the changed apertures."
+#~ msgstr "Will generate a new Gerber object from the changed apertures."
+
+#~ msgid ""
+#~ "Will generate a new Gerber object from the changed apertures.\n"
+#~ "This new object can then be isolated etc."
+#~ msgstr ""
+#~ "Will generate a new Gerber object from the changed apertures.\n"
+#~ "This new object can then be isolated etc."
+
+#~ msgid "[success]Offset on the %s axis done ..."
+#~ msgstr "[success]Offset on the %s axis done ..."
+
+#~ msgid ""
+#~ "How much (fraction) of the tool width to overlap each tool pass.\n"
+#~ "Example:\n"
+#~ "A value here of 0.25 means 25\\% from the tool diameter found above.\n"
+#~ "\n"
+#~ "Adjust the value starting with lower values\n"
+#~ "and increasing it if areas that should be painted are still \n"
+#~ "not painted.\n"
+#~ "Lower values = faster processing, faster execution on PCB.\n"
+#~ "Higher values = slow processing and slow execution on CNC\n"
+#~ "due of too many paths."
+#~ msgstr ""
+#~ "How much (fraction) of the tool width to overlap each tool pass.\n"
+#~ "Example:\n"
+#~ "A value here of 0.25 means 25\\% from the tool diameter found above.\n"
+#~ "\n"
+#~ "Adjust the value starting with lower values\n"
+#~ "and increasing it if areas that should be painted are still \n"
+#~ "not painted.\n"
+#~ "Lower values = faster processing, faster execution on PCB.\n"
+#~ "Higher values = slow processing and slow execution on CNC\n"
+#~ "due of too many paths."
+
+#~| msgid "z_toolchange = Z coord for Toolchange"
+#~ msgid "z_move = Z coord for Toolchange"
+#~ msgstr "z_move = Z coord for Toolchange"
+
+#~ msgid "%s/Project_%s"
+#~ msgstr "%s/Project_%s"
+
+#~ msgid "tool_tab"
+#~ msgstr "tool_tab"

+ 3 - 3
locale/it/LC_MESSAGES/strings.po

@@ -507,7 +507,7 @@ msgid "Failed. Select a Geometry Object and try again."
 msgstr ""
 
 #: FlatCAMApp.py:5652 FlatCAMApp.py:5688
-msgid "Expected a FlatCAMGeometry, got"
+msgid "Expected a GeometryObject, got"
 msgstr ""
 
 #: FlatCAMApp.py:5665
@@ -1125,7 +1125,7 @@ msgid "Open TCL script cancelled."
 msgstr ""
 
 #: FlatCAMApp.py:9993
-msgid "Executing FlatCAMScript file."
+msgid "Executing ScriptObject file."
 msgstr ""
 
 #: FlatCAMApp.py:10000 FlatCAMApp.py:10003
@@ -14705,7 +14705,7 @@ msgid "Buffer done"
 msgstr ""
 
 #: tclCommands/TclCommandBbox.py:74 tclCommands/TclCommandNregions.py:73
-msgid "Expected FlatCAMGerber or FlatCAMGeometry, got"
+msgid "Expected GerberObject or GeometryObject, got"
 msgstr ""
 
 #: tclCommands/TclCommandBounds.py:64 tclCommands/TclCommandBounds.py:68

+ 7 - 7
locale/pt_BR/LC_MESSAGES/strings.po

@@ -557,7 +557,7 @@ msgid "Failed. Select a Geometry Object and try again."
 msgstr "Falha. Selecione um Objeto de Geometria e tente novamente."
 
 #: FlatCAMApp.py:5652 FlatCAMApp.py:5688
-msgid "Expected a FlatCAMGeometry, got"
+msgid "Expected a GeometryObject, got"
 msgstr "Geometria FlatCAM esperada, recebido"
 
 #: FlatCAMApp.py:5665
@@ -1211,7 +1211,7 @@ msgid "Open TCL script cancelled."
 msgstr "Abrir script TCL cancelado."
 
 #: FlatCAMApp.py:9993
-msgid "Executing FlatCAMScript file."
+msgid "Executing ScriptObject file."
 msgstr "Executando arquivo de Script FlatCAM."
 
 #: FlatCAMApp.py:10000 FlatCAMApp.py:10003
@@ -16677,8 +16677,8 @@ msgid "Buffer done"
 msgstr "Buffer concluído"
 
 #: tclCommands/TclCommandBbox.py:74 tclCommands/TclCommandNregions.py:73
-msgid "Expected FlatCAMGerber or FlatCAMGeometry, got"
-msgstr "Esperando FlatCAMGerber ou FlatCAMGeometry, recebido"
+msgid "Expected GerberObject or GeometryObject, got"
+msgstr "Esperando GerberObject ou GeometryObject, recebido"
 
 #: tclCommands/TclCommandBounds.py:64 tclCommands/TclCommandBounds.py:68
 msgid "Expected a list of objects names separated by comma. Got"
@@ -17323,8 +17323,8 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ msgid "<span style=\"color:red;\"><b>%s</b></span>"
 #~ msgstr "<span style=\"color:red;\"><b>%s</b></span>"
 
-#~ msgid "FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() -->"
-#~ msgstr "FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() -->"
+#~ msgid "FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->"
+#~ msgstr "FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->"
 
 #~ msgid "FlatCAMCNNJob.on_edit_code_click() -->"
 #~ msgstr "FlatCAMCNNJob.on_edit_code_click() -->"
@@ -17580,7 +17580,7 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "flatcam/src/Beta/\">aqui.</a><BR>Área de <B>DOWNLOAD</B> <a href = "
 #~ "\"https://bitbucket.org/jpcgt/flatcam/downloads/\">aqui.</a><BR>"
 
-#~ msgid "Expected a FlatCAMGeometry, got %s"
+#~ msgid "Expected a GeometryObject, got %s"
 #~ msgstr "Geometria FlatCAM esperada, recebido %s"
 
 #~ msgid "Saved to: %s"

BIN
locale/ro/LC_MESSAGES/strings.mo


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 229 - 232
locale/ro/LC_MESSAGES/strings.po


+ 10 - 10
locale/ru/LC_MESSAGES/strings.po

@@ -546,8 +546,8 @@ msgid "Failed. Select a Geometry Object and try again."
 msgstr "Неудалось. Выберите объект Geometry и попробуйте снова."
 
 #: FlatCAMApp.py:5652 FlatCAMApp.py:5688
-msgid "Expected a FlatCAMGeometry, got"
-msgstr "Ожидается FlatCAMGeometry, получено"
+msgid "Expected a GeometryObject, got"
+msgstr "Ожидается GeometryObject, получено"
 
 #: FlatCAMApp.py:5665
 msgid "A Geometry object was converted to MultiGeo type."
@@ -1189,8 +1189,8 @@ msgid "Open TCL script cancelled."
 msgstr "Открытие сценария отменено."
 
 #: FlatCAMApp.py:9993
-msgid "Executing FlatCAMScript file."
-msgstr "Выполнение файла FlatCAMScript."
+msgid "Executing ScriptObject file."
+msgstr "Выполнение файла ScriptObject."
 
 #: FlatCAMApp.py:10000 FlatCAMApp.py:10003
 msgid "Run TCL script"
@@ -16746,8 +16746,8 @@ msgid "Buffer done"
 msgstr "Буфер готов"
 
 #: tclCommands/TclCommandBbox.py:74 tclCommands/TclCommandNregions.py:73
-msgid "Expected FlatCAMGerber or FlatCAMGeometry, got"
-msgstr "Ожидался FlatCAMGerber или FlatCAMGeometry, получено"
+msgid "Expected GerberObject or GeometryObject, got"
+msgstr "Ожидался GerberObject или GeometryObject, получено"
 
 #: tclCommands/TclCommandBounds.py:64 tclCommands/TclCommandBounds.py:68
 msgid "Expected a list of objects names separated by comma. Got"
@@ -17431,8 +17431,8 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ msgid "<span style=\"color:red;\"><b>%s</b></span>"
 #~ msgstr "<span style=\"color:red;\"><b>%s</b></span>"
 
-#~ msgid "FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() -->"
-#~ msgstr "FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() -->"
+#~ msgid "FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->"
+#~ msgstr "FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->"
 
 #~ msgid "FlatCAMCNNJob.on_edit_code_click() -->"
 #~ msgstr "FlatCAMCNNJob.on_edit_code_click() -->"
@@ -17603,8 +17603,8 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "a><BR><b>ЗАГРУЗИТЬ</B> можно <a href = \"https://bitbucket.org/jpcgt/"
 #~ "flatcam/downloads/\">отсюда.</a><BR>"
 
-#~ msgid "Expected a FlatCAMGeometry, got %s"
-#~ msgstr "Ожидается FlatCAMGeometry, получено %s"
+#~ msgid "Expected a GeometryObject, got %s"
+#~ msgstr "Ожидается GeometryObject, получено %s"
 
 #~ msgid "Saved to: %s"
 #~ msgstr "Сохранёно в: %s"

+ 3 - 3
locale_template/strings.pot

@@ -498,7 +498,7 @@ msgid "Failed. Select a Geometry Object and try again."
 msgstr ""
 
 #: FlatCAMApp.py:5765 FlatCAMApp.py:5801
-msgid "Expected a FlatCAMGeometry, got"
+msgid "Expected a GeometryObject, got"
 msgstr ""
 
 #: FlatCAMApp.py:5778
@@ -1056,7 +1056,7 @@ msgid "Open TCL script"
 msgstr ""
 
 #: FlatCAMApp.py:10373
-msgid "Executing FlatCAMScript file."
+msgid "Executing ScriptObject file."
 msgstr ""
 
 #: FlatCAMApp.py:10381 FlatCAMApp.py:10384
@@ -15065,7 +15065,7 @@ msgid "Buffer done"
 msgstr ""
 
 #: tclCommands/TclCommandBbox.py:76 tclCommands/TclCommandNregions.py:75
-msgid "Expected FlatCAMGerber or FlatCAMGeometry, got"
+msgid "Expected GerberObject or GeometryObject, got"
 msgstr ""
 
 #: tclCommands/TclCommandBounds.py:67 tclCommands/TclCommandBounds.py:71

+ 1 - 1
requirements.txt

@@ -18,7 +18,7 @@ vispy
 ortools>=7.0
 svg.path
 simplejson
-shapely>=1.3
+shapely>=1.7.0
 freetype-py
 fontTools
 rasterio

+ 7 - 7
tclCommands/TclCommand.py

@@ -71,7 +71,7 @@ class TclCommand(object):
         :return: none
         """
 
-        self.app.raise_tcl_error(text)
+        self.app.shell.raise_tcl_error(text)
 
     def get_current_command(self):
         """
@@ -275,7 +275,7 @@ class TclCommand(object):
         # because of signaling we cannot call error to TCL from here but when task
         # is finished also non-signaled are handled here to better exception
         # handling and  displayed after command is finished
-        raise self.app.TclErrorException(text)
+        raise self.app.shell.TclErrorException(text)
 
     def execute_wrapper(self, *args):
         """
@@ -296,7 +296,7 @@ class TclCommand(object):
         except Exception as unknown:
             error_info = sys.exc_info()
             self.log.error("TCL command '%s' failed. Error text: %s" % (str(self), str(unknown)))
-            self.app.display_tcl_error(unknown, error_info)
+            self.app.shell.display_tcl_error(unknown, error_info)
             self.raise_tcl_unknown_error(unknown)
 
     @abc.abstractmethod
@@ -400,9 +400,9 @@ class TclCommandSignaled(TclCommand):
                 raise ex[0]
 
             if status['timed_out']:
-                self.app.raise_tcl_unknown_error("Operation timed outed! Consider increasing option "
-                                                 "'-timeout <miliseconds>' for command or "
-                                                 "'set_sys global_background_timeout <miliseconds>'.")
+                self.app.shell.raise_tcl_unknown_error("Operation timed outed! Consider increasing option "
+                                                       "'-timeout <miliseconds>' for command or "
+                                                       "'set_sys global_background_timeout <miliseconds>'.")
 
         try:
             self.log.debug("TCL command '%s' executed." % str(type(self).__name__))
@@ -439,5 +439,5 @@ class TclCommandSignaled(TclCommand):
             else:
                 error_info = sys.exc_info()
             self.log.error("TCL command '%s' failed." % str(self))
-            self.app.display_tcl_error(unknown, error_info)
+            self.app.shell.display_tcl_error(unknown, error_info)
             self.raise_tcl_unknown_error(unknown)

+ 1 - 4
tclCommands/TclCommandAlignDrill.py

@@ -1,6 +1,5 @@
 import collections
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber, FlatCAMExcellon
 
 from shapely.geometry import Point
 import shapely.affinity as affinity
@@ -89,9 +88,7 @@ class TclCommandAlignDrill(TclCommandSignaled):
         if obj is None:
             return "Object not found: %s" % name
 
-        if not isinstance(obj, FlatCAMGeometry) and \
-                not isinstance(obj, FlatCAMGerber) and \
-                not isinstance(obj, FlatCAMExcellon):
+        if obj.kind != "geometry" and obj.kind != 'gerber' and obj.kind != 'excellon':
             return "ERROR: Only Gerber, Geometry and Excellon objects can be used."
 
         # Axis

+ 2 - 3
tclCommands/TclCommandBbox.py

@@ -1,6 +1,5 @@
 import collections
 from tclCommands.TclCommand import TclCommand
-from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber
 
 from shapely.ops import cascaded_union
 
@@ -73,7 +72,7 @@ class TclCommandBbox(TclCommand):
 
         if not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMGeometry):
             self.raise_tcl_error('%s %s: %s.' % (
-                _("Expected FlatCAMGerber or FlatCAMGeometry, got"), name, type(obj)))
+                _("Expected GerberObject or GeometryObject, got"), name, type(obj)))
 
         if 'margin' not in args:
             args['margin'] = float(self.app.defaults["gerber_bboxmargin"])
@@ -92,7 +91,7 @@ class TclCommandBbox(TclCommand):
 
         try:
             def geo_init(geo_obj, app_obj):
-                assert isinstance(geo_obj, FlatCAMGeometry)
+                # assert geo_obj.kind == 'geometry'
 
                 # Bounding box with rounded corners
                 geo = cascaded_union(obj.solid_geometry)

+ 2 - 3
tclCommands/TclCommandCncjob.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMGeometry
 
 import collections
 from copy import deepcopy
@@ -119,9 +118,9 @@ class TclCommandCncjob(TclCommandSignaled):
             else:
                 return "fail"
 
-        if not isinstance(obj, FlatCAMGeometry):
+        if obj.kind != 'geometry':
             if muted is False:
-                self.raise_tcl_error('Expected FlatCAMGeometry, got %s %s.' % (str(name), type(obj)))
+                self.raise_tcl_error('Expected GeometryObject, got %s %s.' % (str(name), type(obj)))
             else:
                 return
 

+ 2 - 3
tclCommands/TclCommandDrillcncjob.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMExcellon
 
 import collections
 import math
@@ -125,9 +124,9 @@ class TclCommandDrillcncjob(TclCommandSignaled):
             else:
                 return "fail"
 
-        if not isinstance(obj, FlatCAMExcellon):
+        if obj.kind != 'excellon':
             if muted is False:
-                self.raise_tcl_error('Expected FlatCAMExcellon, got %s %s.' % (name, type(obj)))
+                self.raise_tcl_error('Expected ExcellonObject, got %s %s.' % (name, type(obj)))
             else:
                 return "fail"
 

+ 2 - 3
tclCommands/TclCommandFollow.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMGerber
 
 import collections
 
@@ -56,8 +55,8 @@ class TclCommandFollow(TclCommandSignaled):
         if obj is None:
             self.raise_tcl_error("Object not found: %s" % name)
 
-        if not isinstance(obj, FlatCAMGerber):
-            self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
+        if obj.kind != 'gerber':
+            self.raise_tcl_error('Expected GerberObject, got %s %s.' % (name, type(obj)))
 
         del args['name']
         try:

+ 2 - 3
tclCommands/TclCommandGeoCutout.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry
 
 import logging
 import collections
@@ -209,7 +208,7 @@ class TclCommandGeoCutout(TclCommandSignaled):
         except ValueError:
             gaps_u = gaps
 
-        if isinstance(cutout_obj, FlatCAMGeometry):
+        if cutout_obj.kind == 'geometry':
             # rename the obj name so it can be identified as cutout
             # cutout_obj.options["name"] += "_cutout"
 
@@ -306,7 +305,7 @@ class TclCommandGeoCutout(TclCommandSignaled):
             # cutout_obj.plot()
             # self.app.inform.emit("[success] Any-form Cutout operation finished.")
             # self.app.plots_updated.emit()
-        elif isinstance(cutout_obj, FlatCAMGerber):
+        elif cutout_obj.kind == 'gerber':
 
             def geo_init(geo_obj, app_obj):
                 try:

+ 12 - 9
tclCommands/TclCommandHelp.py

@@ -65,10 +65,11 @@ class TclCommandHelp(TclCommand):
 
         if 'name' in args:
             name = args['name']
-            if name not in self.app.tcl_commands_storage:
+            if name not in self.app.shell.tcl_commands_storage:
                 return "Unknown command: %s" % name
 
-            self.app.shell.append_output(self.app.tcl_commands_storage[name]["help"])
+            help_for_command = self.app.shell.tcl_commands_storage[name]["help"] + '\n\n'
+            self.app.shell.append_output(help_for_command)
         else:
             if not args:
                 cmd_enum = '%s\n' % _("Available commands:")
@@ -77,19 +78,21 @@ class TclCommandHelp(TclCommand):
                 try:
                     # find the maximum length of a command name
                     max_len = 0
-                    for cmd_name in self.app.tcl_commands_storage:
+                    for cmd_name in self.app.shell.tcl_commands_storage:
                         curr_len = len(cmd_name)
                         if curr_len > max_len:
                             max_len = curr_len
 
                     h_space = "&nbsp;"
                     cnt = 0
-                    for cmd_name in sorted(self.app.tcl_commands_storage):
-                        cmd_description = "<span>%s</span>" % self.app.tcl_commands_storage[cmd_name]['description']
+                    for cmd_name in sorted(self.app.shell.tcl_commands_storage):
+                        cmd_description = "<span>%s</span>" % \
+                                          self.app.shell.tcl_commands_storage[cmd_name]['description']
 
                         curr_len = len(cmd_name)
 
-                        cmd_name_colored = "<span style=\" font-weight: bold; color: red;\" >%s</span>" % str(cmd_name)
+                        cmd_name_colored = "> <span style=\" font-weight: bold; color: red;\" >%s</span>" % \
+                                           str(cmd_name)
 
                         nr_chars = max_len - curr_len
 
@@ -104,11 +107,11 @@ class TclCommandHelp(TclCommand):
                         else:
                             cnt += 1
                 except Exception as err:
-                    self.app.log.debug("App.setup_shell.shelp() when run as 'help' --> %s" % str(err))
-                    displayed_text = ['> %s\n' % cmd for cmd in sorted(self.app.tcl_commands_storage)]
+                    self.app.log.debug("tclCommands.TclCommandHelp() when run as 'help' --> %s" % str(err))
+                    displayed_text = ['> %s' % cmd for cmd in sorted(self.app.shell.tcl_commands_storage)]
 
                 cmd_enum += '<br>'.join(displayed_text)
-                cmd_enum += '<br><br>%s<br>%s<br>' % (
+                cmd_enum += '<br><br>%s<br>%s<br><br>' % (
                     _("Type help <command_name> for usage."), _("Example: help open_gerber"))
 
                 self.app.shell.append_raw(cmd_enum)

+ 2 - 3
tclCommands/TclCommandIsolate.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMGerber
 
 import collections
 
@@ -96,8 +95,8 @@ class TclCommandIsolate(TclCommandSignaled):
         if obj is None:
             self.raise_tcl_error("Object not found: %s" % name)
 
-        if not isinstance(obj, FlatCAMGerber):
-            self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
+        if obj.kind != 'gerber':
+            self.raise_tcl_error('Expected GerberObject, got %s %s.' % (name, type(obj)))
 
         del args['name']
         obj.isolate(plot=False, **args)

+ 2 - 2
tclCommands/TclCommandJoinExcellon.py

@@ -1,5 +1,5 @@
 from tclCommands.TclCommand import TclCommand
-from FlatCAMObj import FlatCAMExcellon
+from flatcamObjects.FlatCAMExcellon import ExcellonObject
 
 import collections
 
@@ -62,7 +62,7 @@ class TclCommandJoinExcellon(TclCommand):
                 objs.append(obj)
 
         def initialize(obj_, app):
-            FlatCAMExcellon.merge(self, objs, obj_)
+            ExcellonObject.merge(self, objs, obj_)
 
         if objs and len(objs) >= 2:
             self.app.new_object("excellon", outname, initialize, plot=False)

+ 2 - 2
tclCommands/TclCommandJoinGeometry.py

@@ -1,5 +1,5 @@
 from tclCommands.TclCommand import TclCommand
-from FlatCAMObj import FlatCAMGeometry
+from flatcamObjects.FlatCAMGeometry import GeometryObject
 
 import collections
 
@@ -62,7 +62,7 @@ class TclCommandJoinGeometry(TclCommand):
                 objs.append(obj)
 
         def initialize(obj_, app):
-            FlatCAMGeometry.merge(self, objs, obj_)
+            GeometryObject.merge(self, objs, obj_)
 
         if objs and len(objs) >= 2:
             self.app.new_object("geometry", outname, initialize, plot=False)

+ 1 - 2
tclCommands/TclCommandMillDrills.py

@@ -6,7 +6,6 @@
 # ##########################################################
 
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMExcellon
 
 import math
 import collections
@@ -138,7 +137,7 @@ class TclCommandMillDrills(TclCommandSignaled):
         except Exception as e:
             self.raise_tcl_error("Bad tools: %s" % str(e))
 
-        if not isinstance(obj, FlatCAMExcellon):
+        if obj.kind != 'excellon':
             self.raise_tcl_error('Only Excellon objects can be mill-drilled, got %s %s.' % (name, type(obj)))
 
         if self.app.collection.has_promises():

+ 1 - 2
tclCommands/TclCommandMillSlots.py

@@ -6,7 +6,6 @@
 # ##########################################################
 
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMExcellon
 
 import collections
 import math
@@ -139,7 +138,7 @@ class TclCommandMillSlots(TclCommandSignaled):
         except Exception as e:
             self.raise_tcl_error("Bad tools: %s" % str(e))
 
-        if not isinstance(obj, FlatCAMExcellon):
+        if obj.kind != 'excellon':
             self.raise_tcl_error('Only Excellon objects can have mill-slots, got %s %s.' % (name, type(obj)))
 
         if self.app.collection.has_promises():

+ 1 - 4
tclCommands/TclCommandMirror.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommandSignaled
-from FlatCAMObj import FlatCAMExcellon, FlatCAMGeometry, FlatCAMGerber
 
 import collections
 
@@ -68,9 +67,7 @@ class TclCommandMirror(TclCommandSignaled):
         if obj is None:
             return "Object not found: %s" % name
 
-        if not isinstance(obj, FlatCAMGerber) and \
-                not isinstance(obj, FlatCAMExcellon) and \
-                not isinstance(obj, FlatCAMGeometry):
+        if obj.kind != 'gerber' and obj.kind != 'geometry' and obj.kind != 'excellon':
             return "ERROR: Only Gerber, Excellon and Geometry objects can be mirrored."
 
         # Axis

+ 3 - 4
tclCommands/TclCommandNregions.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommand
-from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber
 
 from shapely.ops import cascaded_union
 
@@ -71,8 +70,8 @@ class TclCommandNregions(TclCommand):
         if obj is None:
             self.raise_tcl_error("%s: %s" % (_("Object not found"), name))
 
-        if not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMGeometry):
-            self.raise_tcl_error('%s %s: %s.' % (_("Expected FlatCAMGerber or FlatCAMGeometry, got"), name, type(obj)))
+        if obj.kind != 'gerber' and obj.kind != 'geometry':
+            self.raise_tcl_error('%s %s: %s.' % (_("Expected GerberObject or GeometryObject, got"), name, type(obj)))
 
         if 'margin' not in args:
             args['margin'] = float(self.app.defaults["gerber_noncoppermargin"])
@@ -91,7 +90,7 @@ class TclCommandNregions(TclCommand):
 
         try:
             def geo_init(geo_obj, app_obj):
-                assert isinstance(geo_obj, FlatCAMGeometry)
+                assert geo_obj.kind == 'geometry'
 
                 geo = cascaded_union(obj.solid_geometry)
                 bounding_box = geo.envelope.buffer(float(margin))

+ 2 - 3
tclCommands/TclCommandOpenGerber.py

@@ -1,6 +1,5 @@
 from tclCommands.TclCommand import TclCommandSignaled
 from camlib import ParseError
-from FlatCAMObj import FlatCAMGerber
 
 import collections
 
@@ -53,8 +52,8 @@ class TclCommandOpenGerber(TclCommandSignaled):
         # How the object should be initialized
         def obj_init(gerber_obj, app_obj):
 
-            if not isinstance(gerber_obj, FlatCAMGerber):
-                self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (outname, type(gerber_obj)))
+            if gerber_obj.kind != 'gerber':
+                self.raise_tcl_error('Expected GerberObject, got %s %s.' % (outname, type(gerber_obj)))
 
             # Opening the file happens here
             try:

+ 7 - 8
tclCommands/TclCommandPanelize.py

@@ -1,5 +1,4 @@
 from tclCommands.TclCommand import TclCommand
-from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon
 
 import shapely.affinity as affinity
 
@@ -153,12 +152,12 @@ class TclCommandPanelize(TclCommand):
         #         objs.append(obj_init)
         #
         #     def initialize_geometry(obj_init, app):
-        #         FlatCAMGeometry.merge(objs, obj_init)
+        #         GeometryObject.merge(objs, obj_init)
         #
         #     def initialize_excellon(obj_init, app):
         #         # merge expects tools to exist in the target object
         #         obj_init.tools = obj.tools.copy()
-        #         FlatCAMExcellon.merge(objs, obj_init)
+        #         ExcellonObject.merge(objs, obj_init)
         #
         #     objs = []
         #     if obj is not None:
@@ -167,7 +166,7 @@ class TclCommandPanelize(TclCommand):
         #             currentx = 0
         #             for col in range(columns):
         #                 local_outname = outname + ".tmp." + str(col) + "." + str(row)
-        #                 if isinstance(obj, FlatCAMExcellon):
+        #                 if isinstance(obj, ExcellonObject):
         #                     self.app.new_object("excellon", local_outname, initialize_local_excellon, plot=False,
         #                                         autoselected=False)
         #                 else:
@@ -177,7 +176,7 @@ class TclCommandPanelize(TclCommand):
         #                 currentx += lenghtx
         #             currenty += lenghty
         #
-        #         if isinstance(obj, FlatCAMExcellon):
+        #         if isinstance(obj, ExcellonObject):
         #             self.app.new_object("excellon", outname, initialize_excellon)
         #         else:
         #             self.app.new_object("geometry", outname, initialize_geometry)
@@ -258,7 +257,7 @@ class TclCommandPanelize(TclCommand):
 
                     obj_fin.solid_geometry = []
 
-                    if isinstance(obj, FlatCAMGeometry):
+                    if obj.kind == 'geometry':
                         obj_fin.multigeo = obj.multigeo
                         obj_fin.tools = deepcopy(obj.tools)
                         if obj.multigeo is True:
@@ -269,7 +268,7 @@ class TclCommandPanelize(TclCommand):
                         currentx = 0.0
 
                         for col in range(columns):
-                            if isinstance(obj, FlatCAMGeometry):
+                            if obj.kind == 'geometry':
                                 if obj.multigeo is True:
                                     for tool in obj.tools:
                                         obj_fin.tools[tool]['solid_geometry'].append(translate_recursion(
@@ -287,7 +286,7 @@ class TclCommandPanelize(TclCommand):
                             currentx += lenghtx
                         currenty += lenghty
 
-                if isinstance(obj, FlatCAMExcellon):
+                if obj.kind == 'excellon':
                     self.app.new_object("excellon", outname, job_init_excellon, plot=False, autoselected=True)
                 else:
                     self.app.new_object("geometry", outname, job_init_geometry, plot=False, autoselected=True)

Vissa filer visades inte eftersom för många filer har ändrats