Przeglądaj źródła

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

Beta
Marius Stanciu 5 lat temu
rodzic
commit
92d5f7b303
100 zmienionych plików z 17975 dodań i 9321 usunięć
  1. 4255 0
      CHANGELOG.md
  2. 21 8
      FlatCAM.py
  3. 313 162
      FlatCAMApp.py
  4. 1480 16
      FlatCAMCommon.py
  5. 533 179
      FlatCAMObj.py
  6. 123 1
      FlatCAMTool.py
  7. 6 0
      FlatCAMTranslation.py
  8. 175 56
      ObjectCollection.py
  9. 78 4118
      README.md
  10. 467 138
      camlib.py
  11. 109 77
      flatcamEditors/FlatCAMExcEditor.py
  12. 347 144
      flatcamEditors/FlatCAMGeoEditor.py
  13. 246 203
      flatcamEditors/FlatCAMGrbEditor.py
  14. 11 9
      flatcamEditors/FlatCAMTextEditor.py
  15. 265 167
      flatcamGUI/FlatCAMGUI.py
  16. 289 24
      flatcamGUI/GUIElements.py
  17. 487 223
      flatcamGUI/ObjectUI.py
  18. 1 1
      flatcamGUI/PlotCanvas.py
  19. 131 84
      flatcamGUI/PlotCanvasLegacy.py
  20. 414 119
      flatcamGUI/PreferencesUI.py
  21. 2 21
      flatcamGUI/VisPyCanvas.py
  22. 8 5
      flatcamGUI/VisPyVisuals.py
  23. 84 70
      flatcamParsers/ParseExcellon.py
  24. 130 58
      flatcamParsers/ParseGerber.py
  25. 6 4
      flatcamParsers/ParseHPGL2.py
  26. 33 6
      flatcamParsers/ParseSVG.py
  27. 495 0
      flatcamTools/ToolAlignObjects.py
  28. 26 12
      flatcamTools/ToolCalculators.py
  29. 41 27
      flatcamTools/ToolCalibration.py
  30. 92 86
      flatcamTools/ToolCopperThieving.py
  31. 142 95
      flatcamTools/ToolCutOut.py
  32. 366 214
      flatcamTools/ToolDblSided.py
  33. 229 76
      flatcamTools/ToolDistance.py
  34. 15 12
      flatcamTools/ToolDistanceMin.py
  35. 694 0
      flatcamTools/ToolExtractDrills.py
  36. 35 33
      flatcamTools/ToolFiducials.py
  37. 67 50
      flatcamTools/ToolFilm.py
  38. 13 14
      flatcamTools/ToolImage.py
  39. 304 0
      flatcamTools/ToolInvertGerber.py
  40. 12 2
      flatcamTools/ToolMove.py
  41. 415 288
      flatcamTools/ToolNCC.py
  42. 12 11
      flatcamTools/ToolOptimal.py
  43. 41 41
      flatcamTools/ToolPDF.py
  44. 576 193
      flatcamTools/ToolPaint.py
  45. 53 41
      flatcamTools/ToolPanelize.py
  46. 4 4
      flatcamTools/ToolPcbWizard.py
  47. 115 109
      flatcamTools/ToolProperties.py
  48. 994 0
      flatcamTools/ToolPunchGerber.py
  49. 27 25
      flatcamTools/ToolQRCode.py
  50. 127 118
      flatcamTools/ToolRulesCheck.py
  51. 144 20
      flatcamTools/ToolShell.py
  52. 42 42
      flatcamTools/ToolSolderPaste.py
  53. 28 22
      flatcamTools/ToolSub.py
  54. 52 38
      flatcamTools/ToolTransform.py
  55. 7 3
      flatcamTools/__init__.py
  56. 143 143
      locale/de/LC_MESSAGES/strings.po
  57. BIN
      locale/en/LC_MESSAGES/strings.mo
  58. 326 266
      locale/en/LC_MESSAGES/strings.po
  59. 57 57
      locale/es/LC_MESSAGES/strings.po
  60. 55 55
      locale/fr/LC_MESSAGES/strings.po
  61. 32 32
      locale/it/LC_MESSAGES/strings.po
  62. 150 150
      locale/pt_BR/LC_MESSAGES/strings.po
  63. BIN
      locale/ro/LC_MESSAGES/strings.mo
  64. 326 266
      locale/ro/LC_MESSAGES/strings.po
  65. 150 150
      locale/ru/LC_MESSAGES/strings.po
  66. 423 480
      locale_template/strings.pot
  67. 2 2
      make_freezed.py
  68. 49 16
      preprocessors/Berta_CNC.py
  69. 17 11
      preprocessors/GRBL_laser.py
  70. 46 18
      preprocessors/ISEL_CNC.py
  71. 61 26
      preprocessors/ISEL_ICP_CNC.py
  72. 56 26
      preprocessors/Marlin.py
  73. 120 0
      preprocessors/Marlin_laser_FAN_pin.py
  74. 122 0
      preprocessors/Marlin_laser_Spindle_pin.py
  75. 2 2
      preprocessors/Paste_1.py
  76. 50 22
      preprocessors/Repetier.py
  77. 46 18
      preprocessors/Toolchange_Custom.py
  78. 51 20
      preprocessors/Toolchange_Manual.py
  79. 48 22
      preprocessors/Toolchange_Probe_MACH3.py
  80. 55 26
      preprocessors/default.py
  81. 52 21
      preprocessors/grbl_11.py
  82. 48 18
      preprocessors/line_xyz.py
  83. 7 3
      requirements.txt
  84. 3 2
      setup_ubuntu.sh
  85. BIN
      share/align16.png
  86. BIN
      share/align32.png
  87. BIN
      share/black32.png
  88. 296 0
      share/dark_resources/Makefile
  89. BIN
      share/dark_resources/active_static.png
  90. BIN
      share/dark_resources/addarray16.png
  91. BIN
      share/dark_resources/addarray20.png
  92. BIN
      share/dark_resources/addarray32.png
  93. BIN
      share/dark_resources/aero.png
  94. BIN
      share/dark_resources/aero_arc.png
  95. BIN
      share/dark_resources/aero_array.png
  96. BIN
      share/dark_resources/aero_buffer.png
  97. BIN
      share/dark_resources/aero_circle.png
  98. BIN
      share/dark_resources/aero_circle_geo.png
  99. BIN
      share/dark_resources/aero_disc.png
  100. BIN
      share/dark_resources/aero_drill.png

+ 4255 - 0
CHANGELOG.md

@@ -0,0 +1,4255 @@
+FlatCAM BETA (c) 2019 - by Marius Stanciu
+Based on FlatCAM: 
+2D Computer-Aided PCB Manufacturing by (c) 2014-2016 Juan Pablo Caram
+=================================================
+
+CHANGELOG for FlatCAM beta
+
+=================================================
+
+24.04.2020
+
+- some PEP changes, some method descriptions updated
+- added a placeholder text to 2Sided Tool
+- added a new menu entry in the context menu of the Tcl Shell: 'Save Log' which will save the content of the Tcl Shell browser window to a file
+- the status bar messages that are echoed in the Tcl Shell will no longer have all text colored but only the identifier
+- some message strings cleanup
+- added possibility to save as text file the content in Tcl Shell browser window when clicking the Save log context menu entry
+- fixed an issue regarding the statusbar pixmap selection
+- update the language template strings.pot and updated the Romanian translation
+- updated the Readme file with the steps for installation for MacOS
+- updated the requirements.txt file
+- updated some of the icons in the dark_resources folder (some added, some modified)
+- updated Paint Tool for the new Tool DB
+- updated the Tcl commands CopperClear and Paint
+
+23.04.2020 
+
+- fixed the Tcl Command Help to work as expected; made the text of the commands to be colored in Red color and bold
+- added a 'Close' menu entry in the Tcl Shell context menu that will close (hide) the Tcl Shell Dock widget
+- on launching the Tcl Shell the Edit line will take focus immediately 
+- in App.on_mouse_move_over_plot() method no longer will be done a setFocus() on every move, only when it is needed
+- added an extra check if old preferences files are detected, a check if the type of the values is the same with the type in the current preferences file. If the type is not the same then the current type is preferred.
+- aligned the Tcl commands display when the Help Tcl command is run without parameters
+- fixed the Tcl command Plot_All that malfunctioned if there were any FlatCAM scripts (or FlatCAM documents) open
+- updated the shortcuts list
+
+22.04.2020 
+
+- added a new feature, project auto-saving controlled from Edit -> Preferences -> General -> APP. Preferences -> Enable Auto Save checkbox
+- fixed some bugs in the Tcl Commands
+- modified the Tcl Commands to be able to use as boolean values keywords with lower case like 'false' instead of expected 'False'
+- refactored some of the code in the App class and created a new Tcl Command named Help
+
+20.04.2020
+
+- made the Grid icon in the status bar clickable and it will toggle the snap to grid function
+- some mods in the Distance Tool
+- added ability to use line width when adding shapes for both Legacy and OpenGL graphic engines
+- added the linewidth=2 parameter for the Tool Distance utility geometry
+- fixed a selection issue in Legacy graphic mode for single click
+- added a CHANGELOG file and changed the README file to contain the installation instructions
+- updated the README file
+- in Project Tab added tooltips for the loaded objects
+- fixed a bug in loading objects by drag&drop into the Project Tab where only one object in the selection was loaded
+
+19.04.2020 
+
+- fixed a bug that did not allow to edit GUI elements of type FCDoubleSpinner if it contained the percent symbol
+- some small optimizations in the GUI of Cutout Tool
+- fixed more issues (new) in NCC Tool
+- added a new layout named 'minimal'
+- some PEP8 changes in Geometry Editor
+
+15.04.2020 
+
+- made sure that the Tcl commands descriptions listed on help command are aligned
+
+14.04.2020 
+
+- lightened the hue of the color for 'success' messages printed in the Tcl Shell browser
+- modified the extensions all over such the names include also the extension name. For Linux who does not display the extensions in the native FileDialog.
+- added descriptions for some of the methods in the app.
+- added lightened icons for the dark theme from Leandro Heck 
+
+13.04.2020 
+
+- added the outname parameter for the geocutout Tcl command
+- multiple fixes in the Tcl commands (especially regarding the interchange between True/false and 1/0 values)
+- updated the help for all Tcl Commands
+- in Tcl Shell, the 'help' command will add also a brief description for each command in the list
+- updated the App.plot_all() method giving it the possibility to be run as threaded or not
+- updated the Tcl command PlotAll to be able to run threaded or not
+- updated the Tcl commands PlotAll and PlotObjects to have a parameter that control if the objects are to be plotted or not on canvas; it serve as a disable/enable
+- minor update to the autocomplete dictionary
+- the Show Shell in Edit -> Preferences will now toggle the Tcl shell based on the current status of the Tcl Shell
+- updated the Tcl command Isolate help for follow parameter 
+- updated DrillCncJob Tcl Command with new parameters and fixed it to work in the new format of the Excellon methods
+- fixed issue #399
+- changed CncJob Tcl Command parameter 'depthperpass' to a shorter 'dpp'
+
+11.04.2020 
+
+- fixed issue #394 - the saveDialog in Linux did not added the selected extension
+- when the Save button is clicked in the Edit -> Preferences the Preferences tab is closed.
+
+10.04.2020 
+
+- made sure that the timeout parameter used by some Tcl Commands is seen as an integer in all cases - fixed issue #389
+- minor changes in Paint Tool
+- minor changes in GUI (Save locations in Menu -> File) and the key shortcuts - fixed issue #391
+
+
+9.04.2020 
+
+- if FlatCAM is not run with Python version >= 3.5 it will exit.
+- modified all CTRL+ with Ctrl+ and all ALT+ with Alt+ and all SHIFT+ with Shift+. Fixed issue #387.
+- removed some packages from setup_ubuntu.sh as they are not needed in FlatCAM beta
+
+8.4.2020 
+
+- fixed the Tcl Command Delete to have an argument -f that will force deletion evading the popup (if the popup is enabled). The sme command without a name now will delete all objects
+- fixed the Tcl Command JoinExcellons
+- fixed the Tcl Command JoinGeometry
+- fixed the Tcl Command Mirror
+- updated the Tcl Command Mirror to use a (X,Y) origin parameter. Works if the -box parameter is not used.
+- updated the Tcl Command Offset. Now it can use only -x or -y parameter no longer is mandatory to have both. The one that is not present will be assumed 0.0
+- updated the Tcl Command Panelize. The -rows and -columns parameters are no longer both required. If one is not present then it is assumed to be zero.
+- updated the Tcl Command Scale. THe -origin parameter can now be a tuple of (x,y) coordinates.
+- updated the Tcl Command Skew. Now it can use only -x or -y parameter no longer is mandatory to have both. The one that is not present will be assumed 0.0
+- updated the help for all the Tcl Commands
+
+6.04.2020 
+
+- added key shortcuts (arrow up/down) that will select the objects in the Project tab if the focus is in that tab
+- added a minor change to the ListSys Tcl command
+- fixed an crash generated when running the Tool Database from the Menu -> Options menu entry
+- fixed a bug in handling the UP/DOWN key shortcuts that caused a crash when no object was selected in the Project Tab; also made sure that the said keys are handled only for the Project Tab
+- some PEP8 changes and other minor changes
+- updated the requirements file
+- updated the 2Sided Tool by not allowing the Gerber file to be mirrored without a valid reference and added some placeholder texts
+
+5.04.2020 
+
+- made sure that the HDPI scaling attribute is set before the QApplication is started
+- made sure that when saving a project, the app will try to update the active object from UI form only if there is an active object
+- fix for contextual menus on canvas when using PyQt versions > 5.12.1
+- decision on which mouse button to use for panning is done now once when setting the plotcanvas
+- fix to work with Python 3.8 (closing the application)
+- fixed bug in Gerber parser that allowed loading as Gerber of a file that is not a Gerber
+- fixed a bug in extension detection for Gerber files that allowed in the filtered list files that extension *.gb*
+- added a processEvents method in the Gerber parser parse_lines() method
+- fixed issue #386 - multiple Cut operation on a edited object created a crash due of the bounds() method
+- some changes in the Geometry UI
+
+4.04.2020 
+
+- fixed the Repeated code parsing in Excellon Parse
+
+1.04.2020 
+
+- updated the SVG parser to take into consideration the 'Close' svg element and paths that are made from a single line (we may need to switch to svgpathtools module)
+- minor changes to increase compatibility with Python 3.8
+- PEP8 changes
+
+30.03.2020
+
+- working to update the Paint Tool
+- fixed some issues in Paint Tool
+
+29.03.2020
+
+- modified the new database to accept data from NCC and Paint Tools
+- fixed issues in the new database when adding the tool in a Geometry object
+- fixed a bug in Geometry object that generated a change of dictionary while iterating over it
+- started to add the new database links in the NCC and Paint Tools
+- in the new Tools DB added ability to double click on the ID in the tree widget to execute adding a tool from DB
+- working in updating NCC Tool
+
+28.03.2020
+
+- finished the new database based on a QTreeWidget
+
+21.03.2020
+
+- fixed Cutout Tool to work with negative values for Margin parameter
+
+20.03.2020
+
+- updated the "re-cut" feature in Geometry object; now if the re-cut parameter is non zero it will cut half of the entered distance before the isolation end and half of it after the isolation end
+- added to Paint and NCC Tool a feature that allow polygon area selection when the reference is selected as Area Selection
+- in Paint Tool and NCC Tool added ability to use Escape Tool to cancel Area Selection and for Paint Tool to cancel Polygon Selection
+- fixed issue in "re-cut" feature when combined with multi-depth feature
+- fixed bugs in cncjob TclCommand
+
+13.03.2020
+
+- fixed a bug in CNCJob generation out of a Excellon object; the plot failed in case some of the geometry of the CNCJob was invalid
+- fixed Properties Tool due of recent changes to the FCTree widget
+
+12.03.2020
+
+- working on the new database
+- fix a bug in the TextInputTool in FlatCAM Geometry Editor that crashed the sw when some fonts are not loaded correctly
+
+4.03.2020
+
+- updated all the FlatCAM Tools and the Gerber UI FCComboBoxes to update the box value with the latest object loaded in the App
+- some fixes in the NCC Tool
+- modified some strings
+
+02.03.2020
+
+- added property that allow the FCComboBox to update the view with the last item loaded; updated the app to use this property
+
+01.03.2020
+
+- updated the CutOut Tool such that while adding manual gaps, the cutting geometry is updated on-the-fly if the gap size or tool diameter parameters are adjusted
+- updated the UI in Geometry Editor
+
+29.02.2020
+
+- compacted the NCC Tool UI by replacing some Radio buttons with Combo boxes due of too many elements
+- fixed error in CutOut Tool when trying to create a FreeFrom Cutout out of a Gerber object with the Convex Shape checked
+- working on a new type of database
+
+28.02.2020
+
+- some small changes in preprocessors
+- solved issue #381 where there was an error when trying to generate CNCJob out of an Excellon file that have a tool with only slots and no drills
+- solved some issues in the preprocessors regarding the newly introduced feature that allow control of the final move X,Y positions
+
+25.02.2020
+
+- fixed bug in Gerber parser: it tried to calculate a len() for a single element and not a list - a Gerber generated by Eagle exhibited this
+- added a new parameter named 'End Move X,Y' for the Geometry and Excellon objects. Adding a tuple of coordinates in this field will control the X,Y position of the final move; not entering a value there will cause not to make an end move
+
+20.02.2020
+
+- in Paint Tool replaced the Selection radio with a combobox GUI element that is more compact
+- in NCC Tool modified the UI
+
+19.02.2020
+
+- fixed some issues in the Geometry Editor; the jump signal disconnect was failing for repeated Editor tool operation
+- fixed an issue in Gerber Editor where the multiprocessing pool was reported as closed and an ValueError exception was raised in a certain scneraio
+- on Set Origin, Move to Origin and Move actions for Gerber and Excellon objects the source file will be also updated (the export functions will export an updated object)
+- in FlatCAMObj.export_gerber() method took into account the possibility of polygons of type 'clear' (the ones found in the Gerber files under the LPC command)
+
+17.02.2020
+
+- updated the Excellon UI to hold data for each tool
+- in Excellon UI removed the tools table column for Offset Z and used the UI form parameter
+- updated the Excellon Editor to add for each tool a 'data' dictionary
+- updated all FlatCAM tools to use the new confirmation message that show if the entered value is within range or outside
+- updated all FlatCAM tools to use the new confirmation message for QSpinBoxes, too
+- in Excellon UI protected the values that are common parameters from change on tool selection change
+- fixed some issues related to the usage of the new confirmation message in FlatCAM Tools
+- made sure that the FlatCAM Tools UI initialization is done only in set_tool_ui() method and not in the constructor
+- adapted the GCode generation from Excellon to work with multiple tools data and modified the preprocessors header
+- when multiple tools are selected in Excellon UI and parameters are modified it will applied to all selected
+- in Excellon UI, Paint Tool and NCC Tool finished the "Apply parameters to all tools" functionality
+- updated Paint Tool and NCC Tool in the UI functionality
+- fixed the Offset spinbox not being controller by offset checkbox in NCC Tool
+
+16.02.2020
+
+- small update to NCC Tool UI
+
+15.02.2020
+
+- in Paint Tool added a new method of painting named Combo who will pass through all the methods until the polygon is cleared
+- in Paint Tool attempting to add a new mode suitable for Laser usage
+- more work in the new Laser Mode in the Paint Tool
+- modified the Paint Tool UI
+
+14.02.2020
+
+- adjusted the UI for Excellon and Geometry objects
+- added a new FlatCAM Tool: Gerber Invert Tool. It will invert the copper features in a Gerber file: where is copper there will be empty and where is empty it will be copper
+- added the Preferences entries for the Gerber Invert Tool
+
+13.02.2020
+
+- finished Punch Gerber Tool
+- minor changes in the Tool Transform and Tool Calculators UI to bring them up2date with the other tools
+
+12.02.2020
+
+- working on fixing a bug in FlatCAMGeometry.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
+
+11.02.2020
+
+- working on Tool Punch; finished the geometry update with the clear geometry for the case of Excellon method
+- working on Tool Punch; finished the geometry update with the clear geometry for the case of Fixed Diameter method
+
+10.02.2020
+
+- optimized the Paint and NCC Tools. When the Lines type of painting/clearing is used, the lines will try to arrange themselves on the direction that the lines length clearing the polygon are bigger
+- solved bug that made drilling with Marlin preprocessor very slow
+- applied the fix for above bug to the TclCommand Drillcncjob too
+- started a new way to clear the Gerber polygons based on the 'follow' lines
+- some cleanup and bug fixes for the Paint Tool
+
+
+8.02.2020
+
+- added a new preprocessor for using laser on a Marlin 3D printer named 'Marlin_laser_use_Spindle_pin'
+- modified the Geometry UI when using laser preprocessors
+- added a new preprocessor file for using laser on a Marlin motion controller but with the laser connected to one of the FAN pins, named 'Marlin_laser_use_FAN_pin'
+- modified the Excellon GCode generation so now it can use multi depth drilling; modified the preprocessors to show the number of passes
+
+5.02.2020
+
+- Modified the Distance Tool such that the Measure button can't be clicked while measuring is in progress
+- optimized selection of drills in the Excellon Editor
+- fixed bugs in multiple selection in Excellon Editor
+- fixed selection problems in Gerber Editor
+- in Distance Tool, when run in the Excellon or Gerber Editor, added a new option to snap to center of the geometry (drill for Excellon, pad for Gerber)
+
+3.02.2020
+
+- modified Spinbox and DoubleSpinbox Custom UI elements such that they issue a warning status message when the typed value is out of range
+- fixed the preprocessors with 'laser' in the name to use the spindle direction set in the Preferences
+- increased the upper limit for feedrates by an order of magnitude
+
+2.02.2020
+
+- fixed issue #376 where the V-Shape parameters from Gerber UI are not transferred to the resulting Geometry object if the 'combine' checkbox is not checked in the Gerber UI
+- in Excellon UI, if Basic application mode is selected in Preferences, the Plot column 'P' is hidden now because some inexperienced users mistake this column checkboxes for tool selection
+- fixed an error in Gerber Parser; the initial values for current_x, current_y were None but should have been 0.0
+- limited the lower limit of angle of V-tip to a value of 1 because 0 makes no sense 
+- small changes in Gerber UI
+- in Geometry Editor make sure that after an edit is finished (correctly or forced) the QTree in the Editor UI is cleared of items
+
+31.01.2020
+
+- added a new functionality, a variation of Set Origin named Move to Origin. It will move a selection of objects to origin such as the bottom left corner of the bounding box that fit them all is in origin.
+- fixed some bugs
+- fixed a division by zero error: fixed #377
+
+30.01.2020
+
+- remade GUI in Tool Cutout, Tool Align Objects, Tool Panelize
+- some changed in the Excellon UI
+- some UI changes in the common object UI
+
+29.01.2020
+
+- changes in how the Editor exit is handled
+- small fix in some pywin32 imports
+- remade the GUI + small fixes in 2Sided Tool
+- updated 2Sided Tool
+
+28.01.2020
+
+- some changes in Excellon Editor
+
+27.01.2020
+
+- in Geometry Editor made sure that on final save, for MultiLineString geometry all the connected lines are merged into one LineString to minimize the number of vertical movements in GCode
+- more work in Punch Gerber Tool
+- the Jump To popup window will now autoselect the LineEdit therefore no more need for an extra click after launching the function
+- made some structural changes in Properties Tool
+- started to make some changes in Geometry Editor
+- finished adding in Geometry Editor a TreeWidget with the geometry shapes found in the edited object
+
+24.02.2020
+
+- small changes to the Toolchange manual preprocessor
+- fix for plotting Excellon objects if the color is changed and then the object is moved
+- laying the GUI for a new Tool: Punch Gerber Tool which will add holes in the Gerber apertures
+- fixed bugs in Minimum Distance Tool
+- update in the GUI for the Punch Gerber Tool
+
+22.01.2020
+
+- fixed a bug in the bounding box generation
+
+19.01.2020
+
+- fixed some bugs that are visible in Linux regarding the ArgsThread class: on app close we need to quit the QThread running the ArgsThread class and also close the opened Socket
+- make sure that the fixes above apply when rebooting app for theme change or for language change
+- fixed and issue that made setting colors for the Gerber file not possible if using a translation
+- made possible to set the colors for Excellon objects too
+- added to the possible colors the fundamentals: black and white
+- in the project context menu for setting colors added the option to set the transparency and also a default option which revert the color to the default value set in the Preferences
+
+17.01.2020
+
+- more changes to Excellon UI
+- changes to Geometry UI
+- more work in NCC Tool upgrade; each tool now work with it's own set of parameters
+- some updates in NCC Tool
+- optimized the object envelope generation in the redesigned NCC Tool
+
+16.01.2020
+
+- updated/optimized the GUI in Preferences for Paint Tool and for NCC Tool
+- work in Paint Tool to bring it up to date with NCC Tool
+- updated the GUI in preferences for Calculator Tool
+- a small change in the Excellon UI
+- updated the Excellon and Geometry UI to be similar
+- put bases for future changes to Excellon Object UI such that each tool will hold it's own parameters
+- in ParseExcellon.Excellon the self.tools dict has now a key 'data' which holds a dict with all the default values for Excellon and Geometry
+- Excellon and Geometry objects, when started with multiple tools selected, the parameters tool name reflect this situation
+- moved default_data data update from Excellon parser to the Excellon object constructor
+
+15.01.2020
+
+- added key shortcuts and toolbar icons for the new tools: Align Object Tool (Alt+A) and Extract Drills (Alt+I)
+- added new functionality (key shortcut Shift+J) to locate the corners of the bounding box (and center) in a selected object
+- modified the NCC Tool GUI to prepare for accepting a tool from a tool database
+- started to modify the Paint Tool to be similar to NCC Tool and to accept a tool from a database
+- work in Paint Tool GUI functionality
+
+14.01.2020
+
+- in Extract Drill Tool added a new method of drills extraction. The methods are: fixed diameter, fixed annular ring and proportional
+- in Align Objects Tool finished the Single Point method of alignment
+- working on the Dual Point option in Align Objects Tool - angle has to be recalculated
+- finished Dual Point option in Align Objects Tool
+
+13.01.2020
+
+- fixed a small GUI issue in Excellon UI when Basic mode is active
+- started the add of a new Tool: Align Objects Tool which will align (sync) objects of Gerber or Excellon type
+- fixed an issue in Gerber parser introduced recently due of changes made to make Gerber files produced by Sprint Layout
+- working on the Align Objects Tool
+
+12.01.2020
+
+- improved the circle approximation resolution
+- fixed an issue in Gerber parser with detecting old kind of units
+- if CTRL key is pressed during app startup the app will start in the Legacy(2D) graphic engine compatibility mode
+
+11.01.2020
+
+- fixed an issue in the Distance Tool
+- expanded the Extract Drills Tool to use a particular annular ring for each type of aperture flash (pad)
+- Extract Drills Tool: fixed issue with oblong pads and with pads made from aperture macros
+- Extract Drills Tool: added controls in Edit -> Preferences
+
+10.02.2020
+
+- working on a new tool: Extract Drills Tool who will create a Excellon object out of the apertures of a Gerber object
+- finished the GUI in the Extract Drills Tool
+- fixed issue in Film Tool where some parameters names in calls of method export_positive() were not matching the actual parameters name
+- finished the Extract Drills Tool
+- fixed a small issue in the DoubleSided Tool
+
+8.01.2020
+
+- working in NCC Tool
+- selected rows in the Tools Tables will stay colored in blue after loosing focus instead of the default gray
+- in NCC Tool the Tool name in the Parameters section will be the Tool ID in the Tool Table
+- added an exception catch in case the plotcanvas init failed for the OpenGL graphic engine and warn user about what happened
+
+7.01.2020
+
+- solved issue #368 - when using the Enable/Disable prj context menu entries the plotted status is not updated in the object properties
+- updates in NCC Tool
+
+6.01.2020
+
+- working on new NCC Tool
+
+2.01.2020
+
+- started to rework the NCC Tool GUI in preparation for adding a Tool DB feature
+- for auto-completer, now clicking an entry in the completer popup will select that entry and insert it
+- made available only for Linux and Windows (not OSX) the starting of the thread that checks if another instance of FlatCAM is already running at the launch of FLatCAM
+- modified Toggle Workspace function to work in the new Preferences UI configuration
+- cleaned the app from progress signal usage since it is not used anymore
+
+1.01.2020
+
+- fixed bug in NCC Tool: after trying to add a tool already in the Tool Table when trying to change the Tool Type the GUI does not change
+- final fix for app not quiting when running a script as argument, script that has the quit_flatcam Tcl command; fixed issue #360
+- fixed issue #363. The Tcl command drillcncjob does not create tool cut, does not allow creation of gcode, it forces the usage of dwell and dwelltime parameters
+- in NCC Tool I've added a warning so the user is warned that the NCC margin has to have a value of at least the tool diameter that is doing an iso_op job in the Tool Table
+- modified the Drillcncjob and Cncjob Tcl commands to be allowed to work without the 'dwell' and 'toolchange' arguments. If 'dwelltime' argument is present it will be assumed that the 'dwell' is True and the same for 'toolchangez' parameter, if present then 'toolchange' will be assumed to be True, else False
+- modified the extracut and multidepth parameters in Cncjob Tcl command like for dwell and toolchange
+- added ability for Tcl commands to have optional arguments with None value (meaning missing value). This case should be treated for each Tcl command in execute() method
+- fixed the Drillcncjob Tcl command by adding an custom self.options key "Tools_in_use" and build it's value, in case it does not exist, to make the toolchange command work
+- middle mouse click on closable tabs will close them
+
+30.12.2019
+
+- Buffer sub-tool in Transform Tool: added the possibility to apply a factor effectively scaling the aperture size thus the copper features sizes
+- in Transform Tool adjusted the GUI
+- fixed some decimals issues in NCC Tool, Paint Tool and Excellon Editor (they were still using the hardcoded values)
+- some small updates in the NCC Tool
+- changes in the Preferences UI for NCC and Paint Tool in Tool Dia entry field
+- fixed Tcl commands that use the overlap parameter to switch from fraction to percentage
+- in Transform Tool made sure that the buffer sub-tool parameters are better explained in tooltips
+- attempt to make TclCommand quit_flatcam work under Linux
+- some fixes in the NCC Tcl command (using the bool() method on some params)
+- another attempt to make TclCommand quit_flatcam work under Linux
+- another attempt to make TclCommand quit_flatcam work under Linux - use signal to call a hard exit when in Linux
+- TclCommand quit_flatcam work under Linux
+
+29.12.2019
+
+- the Apply button text in Preferences is now made red when changes were made and require to be applied
+- the Gerber UI is built only once now so the process is lighter on CPU
+- the Gerber apertures marking shapes storage is now built only once because the more are built the more sluggish is the interface
+- added a new function called by shortcut key combo Ctrl+G when the current widget in Plot Area is an Code Editor. It will jump to the specified line in the text.
+- fixed a small bug where the app tried to hide a label that I've removed previously
+- in Paint Tool Preferences is allowed to add a list of initial tools separated by comma
+- in Geometry Paint Tool fixed the Overlap rate to work between 0 and 99.9999%
+
+28.12.2019
+
+- more updates to the Preferences window and in some other parts of the GUI
+- updated the translations (less Russian)
+- fixed a minor issue that when saving a project with CNCJob objects, the variable that holds the origin of the CNCJob object was not saved in the project. Added to the serializable objects also the exc_cnc_tools dictionary 
+- some changes in the File menu
+
+28.12.2019
+
+- updated all the translations files
+- fixed the big mouse cursor in OpenGL(3D) graphic mode to get the set color
+- fixed the cursor to have the set color and set cursor width in the Legacy(2D) graphic engine
+- in Legacy(2D) graphic mode fixed the cursor toggle when the big cursor is activated
+- in Legacy(2D) fixed big mouse cursor to snap to the grid
+- RELEASE 8.991
+
+27.12.2019
+
+- updated the POT file and the translation files for German, Spanish and French languages
+- fixed some typos
+
+26.12.2019
+
+- modified the ToolDB class and changed some strings
+- Preferences classes now have access to the App attributes through app.setup_obj_classes() method
+- moved app.setup_obj_classes() upper in the App.__init__()
+- added a new Preferences setting allowing to modify the mouse cursor color
+- remade the GUI in Preferences -> General grouping the settings in a more clear way
+- made available the Jump To function in Excellon Editor
+- added a clean_up() method in all the Editor Tools that need it, to be run when aborting using the ESC key
+- fixed an error in the Gerber parser; it did not took into consideration the aperture size declared before the beginning of a Gerber region. Detected for Gerber files generated by KiCAD 5.x
+- in Panelize Tool made sure that for Gerber objects if one of the apertures is without geometry then it is ignored
+- further modifications in Preferences -> General GUI
+- further modifications in Preferences -> General GUI - extended the changes
+- in Legacy(2D) graphic engine made to work the mouse color change
+- theme changing is no longer auto-reboot upon change; it require now to press a button
+- cleaned the Preferences classes and added the signals and signal slots in those classes, removing them from the main app class
+- each FlatCAM object found in Preferences has it's own set of controls for changing the colors
+- added a set of gray icons to be used when the theme is complete dark (for now it is useful only for MacOS with dark theme because at the moment the app is not styled to dark UI except the plot area)
+
+25.12.2019
+
+- fixed an issue in old default file detection and in saving the factory defaults file
+- in Preferences window removed the Import/Export Preferences buttons because they are redundant with the entries in the File -> Menu -> Backup. and added a button to Restore Defaults
+- when in Basic mode the Tool type of the tool in the Geometry UI Tool Table after isolating a Gerber object is automatically selected as 'C1'
+- let the multiprocessing Pool have as many processes as needed
+- added a new Preferences setting allowing a custom mouse line width (to make it thicker or thinner)
+- changed the extension of the Tool Database file to FlatDB for easy recognition (in the future double clicking such a file might import the new tools in the FC database)
+
+24.12.2019
+
+- edited some icons so they don't contain white background
+- fixed an incorrect usage of object in the app.select_objects() method
+- fixed a typo in ToolDB.on_tool_add()
+
+23.12.2019
+
+- some fixes in the Legacy(2D) graphic mode regarding the possibility of changing the color of the Gerber objects
+- added a method to darken the outline color for Gerber objects when they have the color set
+- when Printing as PDF Gerber objects now the rendered color is the print color
+- speed up the plotting in OpenGL(3D) graphic mode
+- speed up the color setting for Gerber object when using the OpenGL(3D) graphic mode
+- setting color for Gerber objects work on a selection of Gerber objects
+- ~~when the selection is changed in the Project Tree the selection shape on canvas is deleted~~
+- if an object is selected on Project Tree and it does not have the selection shape drawn, first click on canvas over it will draw the selection shape 
+- in Tool Transform added a new feature named 'Buffer'. For Geometry and Gerber objects will create (and replace) a geometry at a distance from the original geometry and for Excellon will adjust the Tool diameters
+- solved issue #355 - when the tool diameter field in the Edit → Preferences → Geometry → Geometry General → Tools → Tool dia is only one the app failed to read it
+- solved issue #356 - in Tools DB can not be added more than one tool if a translation is active 
+- some changes related to the fact that the geometry default tool diameter value can be comma separated string of tool diameters
+
+22.12.2019
+
+- added a new option for the Gerber objects: on the project context menu now can be chosen a color for the selected Gerber object
+- fixed issue in Gerber UI where a label was not hidden when in Basic mode
+- added the color parameters of the objects to the serializable attributes
+- fixed Gerber object color set for Legacy(2D) graphic engine; glitch on the OpenGL(3D) graphic engine
+- fixed the above mentioned glitch in the OpenGL(3D) graphic engine when an Gerber object has been set with a color
+
+21.12.2019
+
+- fixed a typo in Distance Tool
+
+20.12.2019
+
+- fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab)
+- Print function is now printing a PDF file for a selection of objects in the colors from canvas 
+- added an icon in the infobar that will show more clearly the status of the grid snapping
+- in Geometry Object UI (selected tab) when a tool type is changed from no matter what to V-shape, the cut_z value is saved and when the tool type is changed back to something different than V-shape, this saved cut-z value is restored
+- fixed re-cut length entry not staying disabled when the re-cut cb is not checked
+
+19.12.2019
+
+- in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid
+- in 2-Sided Tool fixed the Reset Tool button handler to reset the bounds value too; changed a string
+- added Preferences values for PDF margins when saving text in Code Editor as PDF
+- when clicking Cancel in Preferences now the values are reverted to what they used to be before opening Preferences tab and start changing values
+- starting to work to a general Print function; for now it will generate PDF files; currently it works only for one object not for a selection
+- added shortcut key Ctrl+P for printing to PDF method
+
+18.12.2019
+
+- added new parameters to improve Gerber parsing
+- small optimizations in the Preferences UI
+- the Jump To function reference is now saving it's last used value
+- added the ability to use the Jump To method in the Gerber Editor
+- improved the loading of Config File by using the advanced code editor
+- fixed a bug in the new feature 'extra buffering'
+- fixed the creation of CNCJob objects out of multigeo Geometry objects (objects with multiple tools)
+- optimized the NCC Tool
+
+17.12.2019
+
+- more optimizations in NCC Tool
+- optimizations in Paint Tool
+- maximum range for Cut Z is now zero to deal with the situation when using V-shape with tip-dia same value with cut width
+- modified QValidator in FCDoubleSpinner() GUI element to allow entering the minus sign when the range maximum is set as 0.0; also for positive numbers allowed entering the symbol plus
+- made sure that if in Gerber UI the isolation is made with a V-Shape tool then the tool type is automatically updated on the generated Geometry Object
+- added ability to save the Source File as PDF (still have to adjust the page size)
+- fixed the generate_from_geometry_2() method to use the default values in case the parameters are None
+- added ability to save the Source File as PDF - fixed page size and added line breaks
+- more mods to generate_from_geometry_2() method
+- fixed bug saving the FlatCAM project saying the file is used by another application
+- fixed issue #347 - a Gerber generated by Sprint Layout with copper pour ON will not have rendered the copper pour
+
+16.12.2019
+
+- in Geometry Editor added support for Jump To function such as that it works within the Editor Tools themselves. For now it works only in absolute jumps
+- 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
+- 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
+- fixed the CNCJob geometry created with HPGL preprocessor
+- fixed GCode generated with HPGL preprocessor to output only integer coordinates
+- fixed the HPGL2 import parsing for absolute linear movements
+- fixed the line endings for setup_ubuntu.sh
+
+15.12.2019
+
+- fixed a bug that created a crash in special conditions; it's related to the QSettings in FlatCAMGui.py
+- added a script to remove the bad profiles from resource pictures. From here: https://stackoverflow.com/questions/22745076/libpng-warning-iccp-known-incorrect-srgb-profile/43415650, link mentioned by @camellan (Andrey Kultyapov)
+- prepared the application for usage of dark icons in case of using the dark theme
+- updated the languages
+- fixed a typo
+- fixed layout on first launch of the app
+- fixed some issues with the recent preparation for dark icons resource usage
+- added a new preprocessor file contributed by Daniel Friderich and added fixes for it
+- modified the export_gcode() method and the preprocessors such that the preprocessors now have the information if to include the gcode header
+- updated all the translation PO files and the POT file
+- RELEASE 8.99
+
+14.12.2019
+
+- finished the strings update in the Google-translated Spanish
+- finished the strings update in the Google-translated French
+
+13.12.2019
+
+- HPGL2 import: added support for circles, arcs and 3-point arcs. Everything works only for absolute coordinates.
+- removed the .plt extension from Gcode extensions
+- some strings updated; update on the Romanian translate
+- more strings updated; finished the Romanian translation update
+- some work in updating the Spanish Google-translation
+- small updates (Google Translate) in Russian and Brazilian-PT languages
+
+12.12.2019
+
+- finished the Calibration Tool
+- 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
+- some changes in the ObjectUI and for the Geometry UI
+- finished a very rough and limited HPGL2 file import 
+
+11.12.2019
+
+- started work in HPGL2 parser
+- some more work in Calibration Tool
+
+10.12.2019
+
+- small changes in the Geometry UI
+- now extracut option in the Geometry Object will recut as many points as many they are within the specified re-cut length
+- if extracut_length is zero then the extracut will cut up until the first point in path no matter what the distance is
+- in Gerber isolation, when selection mode is checked, now area selection works too
+- in CNCJob UI, now the CNCJob objects made out of Excellon objects will display their CNC tools (drill bits)
+- fixed a cumulative error when using the Tool Offset for Excellon objects
+- added the display of the real depth of cut (cut z + offset_z) for CNC tools made out of an Excellon object
+- for OpenGL graphic mode added a fit_view() execution on canvas initialization
+- fixed Excellon scaling the UI values
+- replaced the SpindleSpeed entry with a FCSpinner() GUI element; if speed is set to 0 it will amount to None
+
+9.12.2019 
+
+- updated the border for fit view on OpenGL graphic mode
+- Calibration Tool - added preferences values
+- Calibration Tool - more work on it
+- 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
+- Calibration Tool - finished the calibrated object creation method
+- updated the POT file
+- fixed an error in the German PO file
+- updated the languages PO files
+- some fixes on the app.jump_to() method
+- made sure that the ToolFilm will not start saving a file if there are no objects loaded
+- some fixes on the app.jump_to() method for the Legacy(2D) graphic mode
+
+8.12.2019
+
+- Calibrate Tool - rearranged the GUI
+- in Geometry UI made sure that the Label that points to the Tool parameters show clearly that those parameters apply only for the selected tool
+- fixed an small issue in Object UI
+- small fixes: selected object in Project used to ask twice for UI build; if scale factor in Object UI is 1 do nothing as there is no point in scaling with a factor of 1
+- in Geometry UI added a button that allow updating all the tools in the Tool Table with the current values in the UI form
+- updated Tcl commands to make use of either 0 or False for False value or 1 or True for True in case of a parameter with type Bool
+
+7.12.2019 
+
+- renamed Calibrate Excellon Tool to a simpler Calibrate Tool
+- Calibrate Tool - when generating verification GCode it will always load into an Editor from which it can be edited and/or saved. On save the editor will close.
+- updated the CNCJob and Drillcncjob Tcl Commands to use 0 and 1 as values for the parameters that are stated as of bool type, beside the normal keywords of False and True
+- Calibrate Tool - working on it
+
+6.12.2019
+
+- fixed the toggle_units() method so now the grid values are accurate to the decimal
+- cleaned up the Excellon parser and fixed some bugs (old and new); Excellon parser has it's own convert_units() method no longer inheriting from Geometry
+- in Excellon UI fixed bug that did not allow editing of the Offset Z parameter from the Tool table
+- in Properties Tool added new information's for the tools in the CNCjob objects
+- few bugs solved regarding the newly created empty objects
+- changed everywhere the name "preprocessor" with "preprocessor"
+- updated the preprocessor files in the toolchange section in order to avoid a graphical representation of travel lines glitch
+- fixed a GUI glitch in the Excellon tool table
+- added units to some of the parameters in the Properties Tool
+
+5.12.2019 
+
+- in NCC Tool, the new Geometry object that is created on copper clear now has the solid_geometry attribute where the geometry is stored not only in the obj.tools attribute
+- Copper Thieving Tool - added units label for the pattern plated area
+- Properties Tool - added a new parameter, the copper area which show the area of the copper features for the Gerber objects
+- Copper Thieving Tool - added a default value for the mask clearance when generating pattern plating mask
+- application wide change: introduced the precision parameters in Edit -> Preferences who will control how many decimals to use in the app parameters
+- changed the FCDoubleSpinner, FCSpinner and FCEntry GUI elements to allow passing an alignment value: left, right or center (not yet available in the app)
+- fixed the GUI of the Geometry Editor Tool Transform and adapted it to use the precision setting
+- updated Gerber Editor to use the precision setting and the Gerber Editor Transform Tool to use the FCDoubleSpinner GUI element
+- in Properties Tool added more information's regarding the Excellon tools, about travelled distance and job time; fixed issues when doing Properties on the CNCjob objects
+- TODO: I need to solve the mess in units conversion: it's too convoluted 
+
+4.12.2019 
+
+- made sure that if an older preferences file is detected then there are no errors and only the parameters that are currently active are loaded; the factory defaults file is deleted and recreated in the new format
+- in Preferences added a new button: 'Close' to close the Preferences window without saving
+- fixed bug in FCSpinner and FCDoubleSpinner GUI elements, that are now the main GUI element in FlatCAM, that made partial selection difficult
+- updated the Paint Tool in Geometry Editor to use the FCDoubleSpinner
+- added the possibility for suffix presence on the FCSpinner and FCDoubleSpinner GUI Elements
+- added the '%' symbol for overlap fields; I still need to divide the content by 100 to get the original (0 ... 1) value
+- fixed the overlap parameter all over the app to reflect the change to percentage
+- in Copper Thieving Tool added the display of the patterned plated area (approximate area) 
+- Copper Thieving Tool - updated the way plated area is calculated making it a bit more precise but still it is a bit bigger than the actual area
+- fixed the Copy Object function to copy also the source_file content
+- Copper Thieving Tool - when the clearance value for the pattern plating mask is negative it will be applied to the origin soldermask too
+- modified the GUI in all tools making the text of the buttons bold and adding a new button named Reset Tool which have to reset the tool GUI and variables (need to check all tools to see if happen)
+- when the Tool tab is in focus, clicking on canvas will no longer change the focus to Project tab
+- Copper Thieving Tool - when creating the pattern platting mask will make a new Gerber object with it
+- small fix in the GUI layout in Gerber Editor
+
+3.12.2019
+
+- in Preferences added an Apply button which apply the modified preferences but does not save to a file, minimizing the file IO operations; Ctrl+S key combo does the Apply now.
+- updated some of the default values to metric, values that were missed previously
+- remade the Gerber Editor way to import an Gerber object into the editor in such a way to use the multiprocessing
+- various small fixes
+- fix for toggle grid lines updating canvas only after moving the mouse (hack, actually)
+- some changes in the UI layout in Cutout Tool
+- added some geometry parameters in Cutout Tool as a convenience, to be passed to the generated Geometry objects
+
+2.12.2019
+
+- fixed issue #343; updated the Image Tool
+- improvements in Importing SVG as Gerber - added an automatic source generation (it is not infallible)
+- a hack to import correctly the QRCode exported as SVG from FlatCAM
+- added 3 new tcl commands: export dxf, export excellon and export gerber
+- added a Cancel button in Tools DB when requesting to add a tool in the Geometry Tool Table
+- modified the default values for the METRIC system; the app now starts in the METRIC units since the majority of the world use the METRIC units system
+- small changes, updated the estimated release date
+- Tool Copper Thieving - added pattern plating mask generation feature
+
+28.11.2019
+
+- small fixes in NCC Tool and in the FlatCAMGeometry class
+
+27.11.2019
+
+- in Tool Film added the page size and page orientation in case of saving the film as PDF file
+- the application workspace has now a lot more options selectable in the Edit -> Preferences -> General -> GUI Preferences
+- updated the drawing of the workspace such that the application overall start time is improved and after first turn on of the workspace, toggling it will have no performance penalty
+- updated the workspace functions to work in Legacy(2D) graphic mode
+- adjusted the selection color transparency for the Legacy(2D) graphic mode because it was too transparent for the fill
+
+26.11.2019
+
+- updated the Film Tool to allow exporting PDF and PNG file (besides the SVG file)
+
+25.11.2019
+
+- 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
+- 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
+- in Gerber isolation added the option to selectively isolate only certain polygons - made it to work for Legacy(2D) graphic mode
+- remade the Paint Tool - single polygon painting; now it can single paint a list of polygons that are clicked onto (right click will start the actual painting)
+
+23.11.2019
+
+- 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
+- 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
+- added Robber Bar option to Copper Thieving Tool
+
+22.11.2019
+
+- Tool Fiducials - added GUI in Preferences and entries in self.defaults dict
+- 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
+
+21.11.2019
+
+- Tool Fiducials - finished the part with adding copper fiducials: manual and auto
+- Tool Fiducials - added choice of shapes: circular or non-standard cross
+- Tool Fiducials - finished the work on adding soldermask openings
+- Tool Fiducials - finished the tool
+- updated requirements.txt and setup_ubuntu.sh files
+
+20.11.2019
+
+- Tool Fiducials - added the GUI and the shortcut key
+- Tool Fiducials - updated the icon
+
+19.11.2019
+
+- removed the f-strings replacing them with the traditional string formatting due of not being supported by older versions of Python 3
+- fixed some TclCommands: MillDrills and OpenGerber
+- fixed bug in Tool Subtract that did not allow subtracting Gerber objects
+- starting to work on Tool Fiducials - created the file
+
+18.11.2019
+
+- finished the Dots and Squares options in the Copper Thieving Tool
+- working on the Lines option in Copper Thieving Tool
+- finished the Lines option in the Copper Thieving Tool; still have to add threading to maximize performance
+- finished Copper Thieving Tool improvements
+- working on the Calibrate Excellon Tool - remade the UI
+
+17.11.2019
+
+- optimized the storage of the Gerber mark shapes by making them one layer only
+- optimized the Distance Tool such that the distance utility geometry will be shown even when the mark shapes are plotted.
+- updated the make_freezed.py file to make sure that all the required files are included
+- updated the setup_ubuntu.sh to include the sudo command (courtesy of Krishna Torque on bitbucket)
+
+16.11.2019
+
+- fixed issue #341 that affected both preprocessors that have inlined feedrate: marlin and repetier. The used feedrate was the Feedrate X-Y and instead had to be Feedrate Z.
+
+15.11.2019
+
+- added all the recognized extensions to the save dialog filters; latest extension used will be preselected next time a save operation occur
+- fixed issue #335. The FCDoubleSPinBox (and FCSpinBox) value was not used when the user entered data but just hovered away the mouse expecting the data to be already confirmed
+- converted setup_ubuntu.sh to Linux line endings
+
+14.11.2019
+
+- made sure that the 'default' preprocessor file is always loaded first such that this name is always first in the GUI comboboxes
+- added a class in GUIElements for a TextEdit box with line numbers and highlight
+
+13.11.2019
+
+- 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
+
+12.11.2019
+
+- added two new preprocessor files for ISEL CNC and for BERTA CNC
+- clicking on a FCTable GUI element empty space will also clear the focus now
+
+11.11.2019
+
+- in Tools Database added a contextual menu to add/copy/delete tool; Ctrl+C, DEL keys work too; key T for adding a tool is now only partially working
+- in Tools Database made the status bar messages show when adding/copying/deleting tools in DB
+- changed all Except statements that were single to except Exception as recommended in some PEP
+- renamed the Copper Fill Tool to Copper Thieving Tool as this is a more appropriate name; started to add ability for more types of copper thieving besides solid
+- fixed some issues recently introduced in ParseSVG
+- updated POT file
+- fixed GUI in 2Sided Tool
+- extending the Copper Thieving Tool - wip
+
+9.11.2019
+
+- fixed a new bug that did not allow to open the FlatCAM Preferences files by doubleclick in Windows
+- added a new feature: Tools Database for Geometry objects; resolved issue #308
+- added tooltips for the Tools Database table headers and buttons
+
+8.11.2019
+
+- updated the make file for frozen executable
+
+7.11.2019
+
+- added the '.ngc' file extension to the GCode Save file dialog filter
+- made the 'M2' Gcode command footer optional, default is False (can be set using the TclCommand: set_sys cncjob_footer True)
+- added a setting in Preferences to force the GCode output to have the Windows line-endings even for non-Windows OS's
+
+6.11.2019
+
+- 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
+- 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
+
+5.11.2019
+
+- added a new setting named 'Allow Machinist Unsafe Settings' that will allow the Travel Z and Cut Z to take both positive and negative values
+- fixed some issues when editing a multigeo geometry
+
+4.11.2019
+
+- wip
+- getting rid of all the Options GUI and related functions as it is no longer supported
+- updated the UI in Geometry UI
+- optimized the order of the defaults storage declaration and the update of the Preferences GUI from the defaults
+- started to add a Tool Database
+
+3.11.2019
+
+- fixed the V-shape tool diameter calculation in NCC Tool
+- in NCC Tool made the new tool dia (circular type) a parameter in Preferences
+- fixed a small issue with clicking in a disabled FCDoubleSpinner or FCSpinner still doing a selection
+
+30.10.2019
+
+- converted SolderPaste Tool to usage of SpinBoxes; changed the SolderPaste Tool UI in Preferences too
+- fixed a bug in SolderPaste Tool that did not allow to view the GCode
+
+29.10.2019
+
+- a bug fix in Geometry Object
+- fixed some missing properties in Tool Calculators
+
+28.10.2019
+
+- in Tools: Paint, NCC and Copper Fill, when using the Area Selection, now the selected areas will stay drawn as markers until the user click RMB
+- in legacy2D graphic engine, adding an utility geometry no longer draw the older ones, overwriting them
+- fixed some issues in the Gerber Editor (Aperture add was double adding an aperture)
+- converted Gerber Editor to usage of SpinBoxes
+- working on the Calibrate Excellon Tool
+- converted Excellon Editor to usage of SpinBoxes
+- Calibrate Excellon Tool: working on self.calculate_factors() method
+
+27.10.2019
+
+- Copper Fill Tool: some PEP8 corrections
+
+26.10.2019
+
+- fixed an error in the FCDoubleSpinner class when FlatCAM is run on system with locale that use the comma as decimal separator
+
+25.10.2019
+
+- QRCode Tool: added ability to add negative QRCodes (perhaps they can be isolated on copper?); added a clear area surrounding the QRCode in case it is dropped on a copper pour (region); fixed the Gerber export
+- QRCode Tool: all parameters are hard-coded for now
+- small update
+- fixed imports in all TclCommands
+- fixed the requirements.txt and setup_ubuntu.sh files
+- QRCode Tool: change the plot method parameter
+- QRCode Tool: added ability to save the generated QRCode as SVG file or PNG file
+- QRCode Tool: added the feature to save the PNG file with transparent background
+- QRCode Tool: added GUI category in Preferences window
+- QRCode Tool: shortcut key for this tool is now Alt+Q while PDF import Tool was relegated to Ctrl+Q combo key shortcut
+- added a new FlatCAM Tool: Copper Fill Tool. It will pour copper into a Gerber filling all empty space with copper, at a clearance distance of the Gerber features
+- Copper Fill Tool: added possibility to select between a bounding box rectangular or convex hull when the reference is the geometry of the source Gerber object
+- Copper Fill Tool: cleanup on not regular tool exit
+- Copper Fill Tool: added GUI category in Edit -> Preferences window
+- QRCode Tool: added a selection limit parameter to control the selection shape vs utility geo
+
+24.10.2019
+
+- added some placeholder texts in the TextBoxes.
+- working on QRCode Tool; added the utility geometry and initial functional layout
+- working on QRCode Tool; finished adding the QRCode geometry to the selected Gerber object and also finished adding the 'follow' geometry needed when exporting the Gerber object as a Gerber file in addition to the 'solid' geometry in the obj.apertures
+- working on QRCode Tool; finished offsetting the geometry both in apertures and in solid_geometry; updated the source_file of the source object
+
+23.10.2019
+
+- QRCode Tool - a SVG object is generated and plotted on screen having the QRCode data
+- fixed an import error in Distance Tool
+- fixed the Toggle Grid Lines functionality
+
+22.10.2019
+
+- working on the Calibrate Excellon Tool
+- finished the GUI layout for the Calibrate Excellon Tool
+- start working on QRCode Tool - not working yet
+- start working on QRCode Tool - searching for alternatives
+
+21.10.2019
+
+- the context menu for the Tabs in notebook and PlotTabArea is launched now on right mouse click on tabs themselves
+- fixed an error when trying to view the source file and there is no object selected
+- updated the Objects menu signals so whenever an object is (de)selected in the Project Tab, it's state will reflect the (un)checked state of the actions in the Object menu
+- fixed issue in Gerber Object UI of not updating the value of CutZ entry on TipDia or TipAngle entries change. Fixed issue #324
+
+18.10.2019
+
+- fixed a small bug in BETA status change
+- updated the About FlatCAM window
+- reverted change in tool dia being able to take only positive values in Gerber Object UI
+- started to work to a new tool: Calibrate Excellon Tool
+- solved the issue #329
+
+18.10.2019
+
+- finished the update on the Google translated Spanish translation.
+- updated the new objects icons for Gerber, Geometry and Excellon
+- small import problem fixed
+- RELEASE 8.98
+
+17.10.2019
+
+- fixed a bug in milling holes due of a message wrongly formatted
+- added an translator email address
+- finished the update on German Google translation. Part of it was corrected by Jens Karstedt
+- finished the update of the Romanian translation.
+- finished the Objects menu by adding the ability of actions to be checked so they will show the selected status of the objects and by adding to actions to (de)select all objects
+- fixed and optimized the click selection on canvas
+- fixed Gerber parsing for very simple Gerber files that have only one Polygon but many LPC zones
+- fixed SVG export; fix bug #327
+- finished the update on French Google translation.
+
+16.10.2019
+
+- small update to Romanian translation files
+
+15.10.2019
+
+- adjusted the layout in NCC Tool
+- fixed bug in Panelization Tool for which in case of Excellon objects, the panel kept a reference to the source object which created issues when moving or disabling/enabling the plots
+- cleaned up the module imports throughout the app (the TclCommands are not yet verified)
+- removed the styling on the comboboxes cellWidget's in the Tool Tables
+- replaced some of the icons that did not looked Ok on the dark theme
+- added a new toolbar button for the Copy object functionality
+- changed the Panelize tool icon
+- corrected some strings
+
+14.10.2019
+
+- modified the result highlight color in Check Rules Tool
+- added the Check Rules Tool parameters to the unit conversion list
+- converted more of the Preferences entries to FCDoubleSpinner and FCSpinner
+- converted all ObjectUI entries to FCDoubleSpinner and FCSpinner
+- updated the translation files (~ 89% translation level)
+- changed the splash screen as it seems that FlatCAM beta will never be more than beta
+- changed some of the signals from returnPressed to editingFinished due of now using the SpinBoxes
+- fixed an issue that caused the impossibility to load a GCode file that contained the % symbol even when was loaded in a regular way from the File menu
+- re-added the CNC tool diameter entry for the CNCjob object in Selected tab.FCSpinner
+- since the CNCjob geometry creation is only useful for graphical purposes and have no impact on the GCode creation I have removed the cascaded union on the GCode geometry therefore speeding up the Gcode display by many factors (perhaps hundreds of times faster)
+- added a secondary link in the bookmark manager
+- fixed the bookmark manager order of bookmark links; first two links are always protected from deletion or drag-and-drop to other positions
+- fixed a whole load of PyQT signal problems generated by recent changes to the usage of SpinBoxes; added a signal returnPressed for the FCSpinner and for FCDoubleSpinner
+- fixed issue in Paint Tool where the first added tool was expected to have a float diameter but it was a string
+- updated the translation files to the latest state in the app
+
+13.10.2019
+
+- fixed a bug in the Merge functions
+- fixed the Export PNG function when using the 2D legacy graphic engine
+- added a new capability to toggle the grid lines for both graphic engines: menu link in View and key shortcut combo Alt+G
+- changed the grid colors for 3D graphic engine when in Dark mode
+- enhanced the Tool Film adding the Film adjustments and added the GUI in Preferences
+- set the GUI layout in Preferences for a new category named Tools 2
+- added the Preferences for Check Rules Tool and for Optimal Tool and also updated the Film Tool to use the default settings in Preferences
+
+12.10.2019
+
+- fixed the Gerber Parser convert units unnecessary usage. The only units conversion should be done when creating the new object, after the parsing
+- more fixes in Rules Check Tool
+- optimized the Move Tool
+- added support for key-based panning in 3D graphic engine. Moving the mouse wheel while pressing the CTRL key will pan up-down and while pressing SHIFT key will pan left-right
+- fixed a bug in NCC Tool and start trying to make the App responsive while the NCC tool is run in a non-threaded way
+- fixed a GUI bug with the QMenuBar recently introduced
+
+11.10.2019
+
+- added a Bookmark Manager and a Bookmark menu in the Help Menu
+- added an initial support for rows drag and drop in FCTable in GUIElements; it crashes for CellWidgets for now, if CellWidgetsare in the table rows
+- fixed some issues in the Bookmark Manager
+- modified the Bookmark manager to be installed as a widget tab in Plot Area; fixed the drag & drop function for the table rows that have CellWidgets inside
+- marked in gray color the rows in the Bookmark Manager table that will populate the BookMark menu
+- made sure that only one instance of the BookmarkManager class is active at one time
+
+10.10.2019
+
+- fixed Tool Move to work only for objects that are selected but also plotted, therefore disabled objects will not be moved even if selected
+
+9.10.2019
+
+- updated the Rules Check Tool - solved some issues
+- made FCDoubleSpinner to use either comma or dot as a decimal separator
+- fixed the FCDoubleSpinner to only allow the amount of decimals already set with set_precision()
+- fixed ToolPanelize to use FCDoubleSpinner in some places
+
+8.10.2019
+
+- modified the FCSpinner and FCDoubleSpinner GUI elements such that the wheel event will not change the values inside unless there is a focus in the lineedit of the SpinBox
+- in Preferences General, Gerber, Geometry, Excellon, CNCJob sections made all the input fields of type SpinBox (where possible)
+- updated the Distance Tool utility geometry color to adapt to the dark theme canvas
+- Toggle Code Editor now works as expected even when the user is closing the Editor tab and not using the command Toggle Code Editor
+- more changes in Preferences GUI, replacing the FCEntries with Spinners
+- some small fixes in toggle units conversion
+- small GUI changes
+
+7.10.2019
+
+- fixed an conflict in a signal usage that was triggered by Tool SolderPaste when a new project was created
+- updated Optimal Tool to display both points coordinates that made a distance (and the minimum) not only the middle point (which is still the place where the jump happen)
+- 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
+- added inverted icons
+
+6.10.2019
+
+- remade the Mark area Tool in Gerber Editor to be able to clear the markings and also to delete the marked polygons (Gerber apertures)
+- working in adding to the Optimal Tool the rest of the distances found in the Gerber and the locations associated; added GUI
+- added display of the results for the Rules Check Tool in a formatted way
+- made the Rules Check Tool document window Read Only
+- made Excellon and Gerber classes from camlib into their own files in the flatcamParser folder
+- moved the ApertureMacro class from camlib to ParseGerber file
+- moved back the ApertureMacro class to camlib for now and made some import changes in the new ParseGerber and ParseExcellon classes
+- some changes to the tests - perhaps I will try adding a few tests in the future
+- changed the Jump To icon and reverted some changes to the parseGerber and ParseExcellon classes
+- updated Tool Optimal with display of all distances (and locations of the middle point between where they happen) found in the Gerber Object
+
+5.10.2019
+
+- remade the Tool Calculators to use the QSpinBox in order to simplify the user interaction and remove possible errors
+- remade: Tool Cutout, Tool 2Sided, Tool Image, Panelize Tool, NCC Tool, Paint Tool  to use the QSpinBox GUI elements
+- optimized the Transformation Tool both in GUI and in functionality and replaced the entries with QSpinBox
+- fixed an issue with the tool table context menu in Paint Tool
+- made some changes in the GUI in Paint Tool, NCC Tool and SolderPaste Tool
+- changed some of the icons; added attributions for icons source in the About FlatCAM window
+- added a new tool in the Geometry Editor named Explode which is the opposite of Union Tool: it will explode the polygons into lines
+
+4.10.2019
+
+- updated the Film Tool and added the ability to generate Punched Positive films (holes in the pads) when a Gerber file is the film's source. The punch holes source can be either an Excellon file or the pads center
+- optimized Rules Check Tool so it runs faster when doing Copper 2 Copper rule
+- small GUI changes in Optimal Tool and in Film Tool
+- some PEP8 corrections
+- some code annotations to make it easier to navigate in the FlatCAMGUI.py
+- fixed exit FullScreen with Escape key
+- added a new menu category in the MenuBar named 'Objects'. It will hold the objects found in the Project tab. Useful when working in FullScreen
+- disabled a log.debug in ObjectColection.get_by_name()
+- added a Toggle Notebook button named 'NB' in the QMenBar which toggle the notebook
+- in Gerber isolation section, the tool dia value is updated when changing from Circular to V-shape and reverse
+- in Tool Film, when punching holes in a positive film, if the resulting object geometry is the same as the source object geometry, the film will not ge generated
+- fixed a bug that when a Gerber object is edited and it has as solid_geometry a single Polygon, saving the result was failing due of len() function not working on a single Polygon
+- added the Distance Tool, Distance Min Tool, Jump To and Set Origin functions to the Edit Toolbar
+
+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
+
+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
+- 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
+- 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
+
+1.10.2019
+
+- fixed the FCSpinner and FCDoubleSpinner GUI elements to select all on first click and deselect on second click in the Spinbox LineEdit
+- for Gerber object in Selected Tab added ability to chose a V-Shape tool and therefore control the isolation better by adjusting the cut width of the isolation in function of the cut depth, tip width of the tool and the tip angle of the tool
+- when in Gerber UI is selected the V-Shape tool, all those parameters (tip dia, tip angle, tool_type = 'V' and cut Z) are transferred to the generated Geometry and prefilled in the Geoemtry UI
+- added a fix in the Gerber parser to work even when there is no information about zero suppression in the Gerber file
+- added new settings in Edit -> Preferences -> Gerber for Gerber Units and Gerber Zeros to be used as defaults in case that those informations are missing from the Gerber file
+- 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
+
+30.09.2019
+
+- modified the Distance Tool such that the number of decimals all over the tool is set in one place by the self.decimals
+- added a new tool named Minimum Distance Tool who will calculate the minimum distance between two objects; key shortcut: SHIFT + M
+- finished the Minimum Distance Tool in case of using it at the object level (not in Editors)
+- completed the Minimum Distance Tool by adding the usage in Editors
+- made the Minimum Distance Tool more precise for the Excellon Editor since in the Excellon Editor the holes shape are represented as a cross line but in reality they should be evaluated as circles
+- small change in the UI layout for Check Rules Tool by adding a new rule (Check trace size)
+- changed a tooltip in Optimal Tool
+- in Optimal Tool added display of how frequent that minimum distance is found
+- in Tool Distance and Tool Minimal Distance made the entry fields read-only
+- in Optimal Tool added the display of the locations where the minimum distance was detected
+- added support to use Multi Processing (multi core usage, not simple threading) in Rules Check Tool
+- in Rules Check Tool added the functionality for the following rules: Hole Size, Trace Size, Hole to Hole Clearance
+- in Rules Check Tool added the functionality for Copper to Copper Clearance
+- in Rules Check Tool added the functionality for Copper to Outline Clearance, Silk to Silk Clearance, Silk to Solder Mask Clearance, Silk to Outline Clearance, Minimum Solder Mask Sliver, Minimum Annular Ring
+- fixes to cover all possible situations for the Minimum Annular Ring Rule in Rules Check Tool
+- some fixes in Rules Check Tool and added a QSignal that is fired at the end of the job
+
+29.09.2019
+
+- work done for the GUI layout of the Rule Check Tool
+- setup signals in the Rules Check Tool GUI
+- changed the name of the Measurement Tool to Distance Tool. Moved it's location to the Edit Menu
+- added Angle parameter which is continuously updated to the Distance Tool
+
+28.09.2019
+
+- changed the icon for Open Script and reused it for the Check Rules Tool
+- added a new tool named "Optimal Tool" which will determine the minimum distance between the copper features for a Gerber object, in fact determining the maximum diameter for a isolation tool that can be used for a complete isolation
+- fixed the ToolMeasurement geometry not being displayed
+- fixed a bug in Excellon Editor that crashed the app when editing the first tool added automatically into a new black Excellon file
+- made sure that if the big mouse cursor is selected, the utility geometry in Excellon Editor has a thicker line width (2 pixels now) so it is visible over the geometry of the mouse cursor
+- fixed issue #319 where generating a CNCJob from a geometry made with NCC Tool made the app crash; also #328 which is the same
+- replaced in FlatCAM Tools and in FLatCAMObj.py  and in Editors all references to hardcoded decimals in string formats for tools with a variable declared in the __init__()
+- fixed a small bug that made app crash when the splash screen is disabled: it was trying to close it without being open
+
+27.09.2019
+
+- optimized the toggle axis command
+- added possibility of using a big mouse cursor or a small mouse cursor. The big mouse cursor is made from 2 infinite lines. This was implemented for both graphic engines
+- added ability to change the cursor size when the small mouse cursor is selected in Preferences -> General
+- removed the line that remove the spaces from the path parameter in the Tcl commands that open something (Gerber, Gcode, Excellon)
+- fixed issue with the old SysTray icon not hidden when the application is restarted programmatically
+- if an object is edited but the result is not saved, the app will reload the edited object UI and set the Selected tab as active
+- made the mouse cursor (big, small) change in real time for both graphic engines
+- started to work on a new FlatCAM tool: Rules Check
+- created the GUI for the Rule Check Tool
+- if there are (x, y) coordinates in the clipboard, when launching the "Jump to" function, those coordinates will be preloaded in the Dialog box.
+- when the combo SHIFT + LMB is executed there is no longer a deselection of objects
+- when the "Jump to" function is called, the mouse cursor (if active) will be moved to the new position and the screen position labels will be updated accordingly
+
+
+27.09.2019
+
+- RELEASE FlatCAM 8.97
+
+26.09.2019
+
+- added a Copy All button in the Code Editor, clicking this button will copy all text in the editor to the clipboard
+- added a 'Milling Type' radio button in Geometry Editor Preferences to contorl the type of geometry will be generated in the Geo Editor (for conventional milling or for the climb milling)
+- added the functionality to allow climb/conventional milling selection for the geometry created in the Geometry Editor
+- now any Geometry that is edited in Geometry editor will have coordinates ordered such that the resulting Gcode will allow the selected milling type in the 'Milling Type' radio button in Geometry Editor Preferences (which depends also of the spindle direction)
+- some strings update
+- French Google-translation at 100%
+- German Google-translation update to 100%
+- updated the other languages and the .POT file
+- changed some strings (that should not have been included for translation) and updated language files and the .POT file
+- fixed issue when rebooting from within in cx_freezed state (it issued a startup arg with the path to FlatCAM.exe but that triggered the last sys.exit(2) that I had in the App.args_at_startup())
+- modified the make_win script for the presence of MatPlotLib
+
+25.09.2019
+
+- French translation at 33%
+- fixed the 'Jump To' function to work in legacy graphic engine
+- in legacy graphic engine fixed the mouse cursor shape when grid snapping is ON, such that it fits with the shape from the OpenGL graphic engine
+- in legacy graphic engine fixed the axis toggle
+- French Google-translation at 48%
+
+24.09.2019
+
+- fixed the fullscreen method to show the application window in fullscreen wherever the mouse pointer it is therefore on the screen we are working on; before it was showing always on the primary screen
+- fixed setup_ubuntu.sh to include the matplotlib package required by the Legacy (2D) graphic engine
+- in legacy graphic engine, fixed issue where immediately after changing the mouse cursor snapping the mouse cursor shape was not updated
+- in legacy graphic engine, fixed issue where while zooming the mouse cursor shape was not updated
+- in legacy graphic engine, fixed issue where immediately after panning finished the mouse cursor shape was not updated
+- unfortunately the fix for issue where while zooming the mouse cursor shape was not updated braked something in way that Matplotlib work with PyQt5, therefore I removed it
+- fixed a bug in legacy graphic engine: when doing the self.app.collection.delete_all() in new_project an app crash occurred
+- implemented the Annotation change in CNCJob Selected Tab for the legacy graphic engine
+
+23.09.2019
+
+- in legacy graphic engine, fixed bug that made the old object disappear when a new object was loaded
+- in legacy graphic engine, fixed bug that crashed the app when creating a new project
+- in legacy graphic engine, fixed a bug that when deleting an object all objects where deleted
+- added a new TclCommand named "set_origin" which will set the origin for all loaded objects to zero if the -auto True argument is used and to a certain x,y location if the format is: set_origin 5,7
+- added a new TclCommand named "bounds" which will return a list of bounds values from a supplied list of objects names. For use in Tcl Scripts
+- updated strings in the translations and the .POT file
+- added the new keywords to the default keywords list
+- fixed the FullScreen option not working for the 3D graphic engine (due bug of Qt5 when OpenGL window is fullscreen) by creating a sort of fullscreen
+- added a final fix that allow full coverage of the screen in FullScreen in Windows and still the menus are working
+- optimized the Gerber mark shapes display
+- fixed a color format bug in Tool Move for 3D engine
+- made sure that when the Tool Move is used on a Gerber file with mark shapes active, those mark shapes are deleted before the actual move
+- in legacy graphic engine, fixed issue with Delete shortcut key trying to delete twice
+- 26% in Google-translated French translation and updated some strings too
+
+22.09.2019
+
+- fixed zoom directions legacy graphic engine (previous commit)
+- fixed display of MultiGeo geometries in legacy graphic engine
+- fixed Paint tool to work in legacy graphic engine
+- fixed CutOut Tool to work in legacy graphic engine
+- fixed display of distance labels and code optimizations in ToolPaint and NCC Tool
+- adjusted axis at startup for legacy graphic engine plotcanvas
+- when the graphic engine is changed in Edit -> Preferences -> General -> App Preferences, the application will restart
+- made hover shapes work in legacy graphic engine
+- fixed bug in display of the apertures marked in the Aperture table found in the Gerber Selected tab and through this made it to also work with the legacy graphic engine
+- fixed annotation in Mark Area Tool in Gerber Editor to work in legacy graphic engine
+- fixed the MultiColor plot option Gerber selected tab to work in legacy graphic engine
+- documented some methods in the ShapeCollectionLegacy class
+- updated the files: setup_ubuntu.sh and requirements.txt
+- some strings changed to be easier for translation
+- updated the .POT file and the translation files
+- updated and corrected the Romanian and Spanish translations
+- updated the .PO files for the rest of the translations, they need to be filled in.
+- fixed crash when trying to set a workspace in FlatCAM in the Legacy engine 2D mode by disabling this function for the case of 2D mode
+- fixed exception when trying to Fit View (shortcut key 'V') with no object loaded, in legacy graphic engine
+
+21.09.2019
+
+- fixed Measuring Tool in legacy graphic engine
+- fixed Gerber plotting in legacy graphic engine
+- fixed Geometry plotting in legacy graphic engine
+- fixed CNCJob and Excellon plotting in legacy graphic engine
+- in legacy graphic engine fixed the travel vs cut lines in CNCJob objects
+- final fix for key shortcuts with modifier in legacy graphic engine
+- refactored some of the code in the legacy graphic engine
+- fixed drawing of selection box when dragging mouse on screen and the selection shape drawing on the selected objects
+- fixed the moving drawing shape in Tool Move in legacy graphic engine
+- fixed moving geometry in Tool Measurement in legacy graphic engine
+- fixed Geometry Editor to work in legacy graphic engine
+- fixed Excellon Editor to work in legacy graphic engine
+- fixed Gerber Editor to work in legacy graphic engine
+- fixed NCC tool to work in legacy graphic engine
+
+20.09.2019
+
+- final fix for the --shellvar having spaces within the assigned value; now they are retained
+- legacy graphic engine - made the mouse events work (click, release, doubleclick, dragging)
+- legacy graphic engine - made the key events work (simple or with modifiers)
+- legacy graphic engine - made the mouse cursor work (enabled/disabled, position report); snapping is not moving the cursor yet
+- made the mouse cursor snap to the grid when grid snapping is active
+- changed the axis color to the one used in the OpenGL graphic engine
+- work on ShapeCollectionLegacy
+- fixed mouse cursor to work for all objects
+- fixed event signals to work in both graphic engines: 2D and 3D
+
+19.09.2019
+
+- made sure that if FlatCAM is registered with a file extension that it does not recognize it will exit
+- added some fixes in the the file extension detection
+- added some status messages for the Tcl script related methods
+- made sure that optionally, when a script is run then it is also loaded into the code editor
+- added control over the display of Sys Tray Icon in Edit -> Preferences -> General -> GUI Settings -> Sys Tray Icon checkbox
+- updated some of the default values to more reasonable ones
+- FlatCAM can be run in HEADLESS mode now. This mode can be selected by using the --headless=1 command line argument or by changing the line headless=False to True in config/configuration.txt file. In this mod the Sys Tray Icon menu will hold only the Run Scrip menu entry and Exit entry.
+- added a new TclCommand named quit_flatcam which will ... quit FlatCAM from Tcl Shell or from a script
+- fixed the command line argument --shellvar to work when there are spaces in the argument value
+- fixed bug in Gerber editor that did not allow to display all shapes after it encountered one shape without 'solid' geometry
+- fixed bug in Gerber Editor -> selection area handler where if some of the selected shapes did not had the 'solid' geometry will silently abort selection of further shapes
+- added new control in Edit -> Preferences -> General -> Gui Preferences -> Activity Icon. Will select a GIF from a selection, the one used to show that FlatCAM is working.
+- changed the script icon to a smaller one in the sys tray menu
+- fixed bug with losing the visibility of toolbars if at first startup the user tries to change something in the Preferences before doing a first save of Preferences
+- changed a bit the splash PNG file
+- moved all the GUI Preferences classes into it's own file flatcamGUI.PreferencesUI.py
+- changed the default method for Paint Tool to 'all'
+
+18.09.2019
+
+- added more functionality to the Extension registration with FLatCAM and added to the GUI in Edit -> Preferences -> Utilities
+- fixed the parsing of the Manufacturing files when double clicking them and they are registered with FlatCAM
+- fixed showing the GUI when some settings (maximized_GUI) are missing from QSettings
+- added sys tray menu
+- added possibility to edit the custom keywords used by the autocompleter (in Tcl Shell and in the Code Editor). It is done in the Edit -> Preferences -> Utilities
+- added a new setting in Edit -> Preferences -> General -> GUI Settings -> Textbox Font which control the font on the Textbox GUI elements
+- fixed issue with the sys tray icon not hiding after application close
+- added option to run a script from the context menu of the sys tray icon. Changed the color of the sys tray icon to a green one so it will be visible on light and dark themes
+
+17.09.2019
+
+- added more programmers that contributed to FlatCAM over the years, in the "About FlatCAM" -> Programmers window
+- fixed issue #315 where a script run with the --shellfile argument crashed the program if it contained a TclCommand New
+- added messages in the Splash Screen when running FlatCAM with arguments at startup
+- fixed issue #313 where TclCommand drillcncjob is spitting errors in Tcl Shell which should be ignored
+- fixed an bug where the pywrapcp name from Google OR-Tools is not defined; fix issue #316
+- if FlatCAM is started with the 'quit' or 'exit' as argument it will close immediately and it will close also another instance of FlatCAM that may be running
+- added a new command line parameter for FlatCAM named '--shellvars' which can load a text file with variables for Tcl Shell in the format: one variable assignment per line and looking like: 'a=3' without quotes
+- made --shellvars into --shellvar and make it only one list of commands passed to the Tcl. The list is separated by comma but without spaces. The variables are accessed in Tcl with the names shellvar_x where x is the index in the list of command comma separated values
+- fixed an issue in the TclShell that generated an exception IndexError which crashed the software
+- fixed the --shellvar and --shellfile FlatCAM arguments to work together but the --shellvar has precedence over --shellfile as it is most likely that whatever variable set by --shellvar will be used in the script file run by --shellfile
+
+16.09.2019
+
+- modified the TclCommand New so it will no longer close all tabs when called (it closed the Code Editor tab which may have been holding the code that run)
+- fixed the App.on_view_source() method for CNCJob objects: the Gcode will now contain the Prepend and Append code from the Edit -> Preferences -> CNCJob -> CNCJob Options
+- added a new parameter named 'muted' for the TclCommands: cncjob, drillcncjob and write_gcode. Setting it as -muted 1 will disable the error reporting in TCL Shell
+- some GUI optimizations
+- more GUI optimizations related to being part of the Advanced category or not
+- added possibility to change the positive SVG exported file color in Tool Film
+- fixed some issues recently introduced in the TclCommands CNCJob, DrillCNCJob and write_gcode; changed some parameters names
+- fixed issue in the Laser preprocessor where the laser was turned on as soon as the GCode started creating an unwanted cut up until the job start
+- added new links in Menu -> Help (Excellon, Gerber specifications and a Report Bug)
+- made the splashscreen to be showed on the current monitor on systems with multiple monitors
+- added a new entry in Menu -> View -> Redraw All which is doing what the name says: redraw all loaded objects
+- fixed issue where in TCl Shell the Windows paths were not understood due of backslash symbol understood as escape symbol instead of path separator
+- made sure that in for the TclCommand cncjob and for the drillcncjob if one of the args is stated but no value then the value used will be the default one
+- made available the TSA algorithm for drill path optimization when the used OS is 64bit. When used OS is 32bit the only available algorithm is TSA
+
+15.09.2019
+
+- refactored FlatCAMGeometry.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)
+- fixed UI showing before the initialization of FlatCAM is finished when the last state of GUI was maximized
+- finished updating the TclCommand cncjob to work for multi-geo Geometry objects with the parameters from the args
+- fixed the TclCommand cncjob to use the -outname parameter
+- added some more keywords in the data_model for auto-completer
+- fixed isolate TclCommand to use correctly the -outname parameter
+- added possibility to see the GCode when right clicking on the Project tab on a CNCJob object and then clicking View Source
+- added a new TclCommand named PlotObjects which will plot a list of FlatCAM objects
+- made that after opening an object in FlatCAM it is not automatically plotted. If the user wants to plot it can use the TclCommands PlotAll or PlotObjects
+- modified the TclCommands so that open files do not plot the opened files automatically
+- made all TclCommands not to be plotted automatically
+- made sure that all TclCommands are not threaded
+- added new TclCommands: NewExcellon, NewGerber
+- fixed the TclCommand open_project
+- added the outname parameter (and established an default name when outname not used) for the AlignDrillGrid and AlignDrill TclCommands
+- fixed Scripts repeating multiple time when the Code Editor is used. This repetition was correlated with multiple openings of the Code Editor window (especially after an error)
+- added the autocomplete keywords that can be changed to the defaults dictionary
+
+14.09.2019
+
+- more string changes
+- updated translation files
+- fixed a small bug
+- minor changes in the Code Editor GUI
+- minor changes in the 'FlatCAM About' GUI
+- added a new shortcut key F5 for doing the 'Plot All'
+- updated the google-translated Spanish translation strings
+- fixed the layouts to include toolbars breaks where it was needed
+- whenever the user changes the Excellon format values for loading files, the Export Excellon Format values will be updated
+- made optional the behavior of Excellon Export values following the values in the Excellon Loading section
+- updated the translations (except RU) and the POT file
+- added to the NonCopperClear.clear_copper() a parameter to be able to run it non-threaded
+
+13.09.2019
+
+- added control for simplification when loading a Gerber file in Preferences -> Gerber -> Gerber General -> Simplify
+- added some messages for the Edit -> Conversions -> Join methods() to make sure that there are at least 2 objects selected for join
+- added a grid layout in on_about()
+- upgraded the Script Editor to be able to run Tcl commands in batches
+- added some ToolTips for the buttons in the Code Editor
+- converted the big strings that hold the shortcut keys descriptions to smaller string to make translations easier
+- fixed some of the strings that were left in the old way
+- updated the POT file
+- updated Romanian language partially
+- added a new way to handle scripts with repeating Tcl commands
+- added new buttons in the Tools toolbar for running, opening and adding new scripts
+- finished the Romanian translation update and updated the POT file
+
+12.09.2019
+
+- small changes in the TclCommands: MillDrills, MillSlots, DrillCNCJob: the new parameter for tolerance is now named: diatol
+- cleaned up the 'About FlatCAM' window, started to give credits for the translation team
+- started to add an application splash screen
+- now, Excellon and Gerber edited objects will have the source_code updated and ready to be saved
+- the edited Gerber (or Excellon) object now is kept in the app after editing and the edited object is a new object
+- added a message to the splash screen
+- remade the splash screen to show multiple messages on app initialization
+- added a new splash image
+- added a control in Preferences -> General -> GUI Settings -> Splash Screen that control if the splash screen is shown at startup
+
+11.09.2019
+
+- added the Gerber code as source for the panelized object in Panelize Tool
+- whenever a Gerber file is deleted, the mark_shapes objects are deleted also
+- made faster the Gerber parser for the case of having a not valid geometry when loading a Gerber file without buffering
+- updated code in self.on_view_source() to make it more responsive
+- fixed the TclCommand MillHoles
+- changed the name of TclCommand MillHoles to MillDrills and added a new TclCommand named MillSlots
+- modified the MillDrills and MillSlots TclCommands to accept as parameter a list of tool diameters to be milled instead of tool indexes
+- fixed issue #302 where a copied object lost all the tools
+- modified the TclCommand DrillCncJob to have as parameter a list of tool diameters to be drilled instead of tool indexes
+- updated the Spanish translation (Google-translation)
+- added a new parameter in the TclCommands: DrillCNCJob, MillDrills, MillSlots named tol (from tolerance). If the diameters of the milled (drilled) dias are within the tolerance specified of the diameters in the Excellon object than those diameters will be processed. This is to help account for rounding errors when having units conversion
+
+10.09.2019
+
+- made isolation threaded
+- fixed a small typo in TclCommandCopperCLear
+- made changing the Plot kind in CNCJob selected tab, threaded
+- fixed an object used before declaring it in NCC Tool - Area option
+- added progress for the generation of Isolation geometry
+- added progress and possibility of graceful exit in Panel Tool
+- added graceful exit possibility when creating Isolation
+- changed the workers thread priority back to Normal
+- when disabling plots, if the selection shape is visible, it will be deleted
+- small changes in Tool Panel (eliminating some deepcopy() calls)
+- made sure that all the progress counters count to 100%
+
+9.09.2019
+
+- changed the triangulation type in VisPyVisuals for ShapeCollectionVisual class
+- added a setting in Preferences -> Gerber -> Gerber General named Buffering. If set to 'no' the Gerber objects load a lot more faster (perhaps 10 times faster than when set to 'full') but the visual look is not so great as all the aperture polygons can be seen
+- added for NCC Tool and Paint Tool a setting in the Preferences -> Tools --> (NCC Tool/ Paint Tool) that can set a progressive plotting (plot shapes as they are processed)
+- some fixes in Paint Tool when done over the Gerber objects in case that the progressive plotting is selected
+- some fixes in Gerber isolation in case that the progressive plotting is selected; added a 'Buffer solid geometry' button shown only when progressive plotting for Gerber object is selected. It will buffer the entire geometry of the object and plot it, in a threaded way.
+- modified FlatCAMObj.py file to the new string format that will allow easier translations
+- modified camlib.py, FlatCAMAPp.py and ObjectCollection.py files to the new string format that will allow easier translations
+- updated the POT file and the German language
+- fixed issue when loading unbuffered a Gerber file that has negative regions
+- fixed Panelize Tool to save the aperture geometries into the panel apertures. Also made the tool faster by removing the buffering at the end of the job
+- modified FlatCAMEditor's files to the new string format that will allow easier translations
+- updated POT file and the Romanian translation
+
+8.09.2019
+
+- added some documentation strings for methods in FlatCAMApp.App class
+- removed some @pyqtSlot() decorators as they interfere with the current way the program works
+
+7.09.2019
+
+- added a method to gracefully exit from threaded tasks and implemented it for the NCC Tool and for the Paint Tool
+- modified the on_about() function to reflect the reality in 2019 - FlatCAM it is an Open Source contributed software
+- remade the handlers for the Enable/Disable Project Tree context menu so they are threaded and activity is shown in the lower right corner of the main window
+- added to GUI new options for the Gerber object related to area subtraction
+- added new feature in the Gerber object isolation allowing for the isolation to avoid an area defined by another object (Gerber or Geometry)
+- all transformation functions show now the progress (rotate, mirror, scale, offset, skew)
+- made threaded the Offset and Scale operations found in the Selected tab of the object
+- corrected some issues and made Move Tool to show correctly when it is plotting and when it is offsetting the objects position
+- made Set Origin feature, threaded
+- updated German language translation files
+- separated the Plotting thread from the transformations threads
+
+6.09.2019
+
+- remade visibility threaded
+- reimplemented the thread listening for new FlatCAM process starting with args so it is no longer subclassed but using the moveToThread function
+- added percentage display for work done in NCC Tool
+- added percentage display for work done in Paint Tool
+- some fixes and prepared the activity monitor area to receive updated texts
+- added progress display in status bar for generating CNCJob from Excellon objects
+- added progress display in status bar for generating CNCJob from Geometry objects
+- modified all the FlatCAM tools strings to the new format in which the status is no longer included in the translated strings to make it easier for the future translations
+- more customization for the progress display in case of NCC Tool, Paint Tool and for the Gcode generation
+- updated POT file with the new strings
+- made the objects offset (therefore the Move Tool) show progress display
+
+5.09.2019
+
+- fixed issue with loading files at start-up
+- fixed issue with generating bounding box geometry for CNCJob objects
+- added some more infobar messages and log.debug
+- increased the priority for the worker tasks
+- hidden the configuration for G91 coordinates due of deciding to leave this development for another time; it require too much refactoring
+- added some messages for the G-code generation so the user know in which stage the process is
+
+4.09.2019
+
+- started to work on support for G91 in Gcode (relative coordinates)
+- added support for G91 coordinates
+- working in plotting the CNCjob generated with G91 coordinates
+
+3.09.2019
+
+- in NCC tool there is now a depth of cut parameter named 'Cut Z' which will dictate how deep the tool will enter into the PCB material
+- in NCC tool added possibility to choose between the type of tools to be used and when V-shape is used then the tool diameter is calculated from the desired depth of cut and from the V-tip parameters
+- small changes in NCC tool regarding the usage of the V-shape tool
+- fixed the isolation distance in NCC Tool for the tools with iso_op type
+- in NCC Tool now the Area adding is continuous until RMB is clicked (no key modifier is needed anymore)
+- fixed German language translation
+- in NCC Tool added a warning in case there are isolation tools and if those isolation's are interrupted by an area or a box
+- in Paint Tool made that the area selection is repeated until RMB click
+- in Paint Tool and NCC Tool fixed the RMB click detection when Area selection is used
+- finished the work on file extensions registration with FlatCAM. If the file extensions are deleted in the Preferences -> File Associations then those extensions are unregistered with FlatCAM
+- fixed bug in NCC Tools and in SolderPaste Tool if in Edit -> Preferences only one tool is entered
+- fixed bug in camblib.clear_polygon3() which caused that some copper clearing / paintings were not complete (some polygons were not processed) when the Straight Lines method was used
+- some changes in NCC Tools regarding of the clearing itself
+
+2.09.2019
+
+- fixed issue in NCC Tool when using area option
+- added formatting for some strings in the app strings, making the future translations easier
+- made changes in the Excellon Tools Table to make it more clear that the tools are selected in the # column and not in the Plot column
+- in Excellon and Gerber Selected tab made the Plot (mark) columns not selectable
+- some ToolTips were modified
+- in Properties Tool made threaded the calculation of convex_hull area and also made it to work for multi-geo objects
+- in NCC tool the type of tool that is used is transferred to the Geometry object
+- in NCC tool the type of isolation done with the tools selected as isolation tools can now be selected and it has also an Edit -> Preferences entry
+- in Properties Tool fixed the dimensions calculations (length, width, area) to work for multi-geo objects
+
+1.09.2019
+
+- fixed open handlers
+- fixed issue in NCC Tool where the tool table context menu could be installed multiple times
+- added new ability to create simple isolation's in the NCC Tool
+- fixed an issue when multi depth step is larger than the depth of cut
+
+27.08.2019
+
+- made FlatCAM so that whenever an associated file is double clicked, if there is an opened instance of FlatCAM, the file will be opened in the first instance without launching a new instance of FlatCAM. If FlatCAM is launched again it will spawn a new process (hopefully it will work when freezed).
+
+26.08.2019
+
+- added support for file associations with FlatCAM, for Windows
+
+25.08.2019
+
+- initial add of a new Tcl Command named CopperClear
+- remade the NCC Tool in preparation for the newly added TclCommand CopperClear
+- finished adding the TclCommandCopperClear that can be called with alias: 'ncc'
+- added new capability in NCC Tool when the reference object is of Gerber type and fixed some newly introduced errors
+- fixed issue #298. The changes in preprocessors done in Preferences dis not update the object UI layout as it was supposed to. The selection of Marlin postproc. did not unhidden the Feedrate Rapids entry.
+- fixed minor issues
+- fixed Tcl Command AddPolygon, AddPolyline
+- fixed Tcl Command CncJob
+- fixed crash due of Properties Tool trying to have a convex hull area on FlatCAMCNCJob objects which is not possible due of their nature
+- modified Tcl Command SubtractRectangle
+- fixed and modernized the Tcl Command Scale to be able to scale on X axis or on Y axis or on both and having as scale reference either the (0, 0) point or the minimum point of the bounding box or the center of the bounding box.
+- fixed and modernized the Tcl Command Skew
+
+24.08.2019
+
+- modified CutOut Tool so now the manual gaps adding will continue until the user is clicking the RMB
+- added ability to turn on/off the grid snapping and to jump to a location while in CutOut Tool manual gap adding action
+- made PlotCanvas class inherit from VisPy Canvas instead of creating an instance of it (work of JP)
+- fixed selection by dragging a selection shape in Geometry Editor
+- modified the Paint Tool. Now the Single Polygon and Area/Reference Object painting works with multiple tools too. The tools have to be selected in the Tool Table.
+- remade the TclCommand Paint to work in the new configuration of the the app (the painting functions are now in their own tool, Paint Tool)
+- fixed a bug in the Properties Tool
+- added a new TcL Command named Nregions who generate non-copper regions
+- added a new TclCommand named Bbox who generate a bounding box.
+
+23.08.2019
+
+- in Tool Cutout for the manual gaps, right mouse button click will exit from the action of adding gaps
+- in Tool Cutout tool I've added the possibility to create a cutout without bridge gaps; added the 'None' option in the Gaps combobox
+- in NCC Tool added ability to add multiple zones to clear when Area option is checked and the modifier key is pressed (either CTRL or SHIFT as set in Preferences). Right click of the mouse is an additional way to finish the job.
+- fixed a bug in Excellon Editor that made that the selection of drills is always cumulative
+- in Paint Tool added ability to add multiple zones to paint when Area option is checked and the modifier key is pressed (either CTRL or SHIFT as set in Preferences). Right click of the mouse is an additional way to finish the job.
+- in Paint Tool and NCC Tool, for the Area option, now mouse panning is allowed while adding areas to process
+- for all the FlatCAM tools launched from toolbar the behavior is modified: first click it will launch the tool; second click: if the Tool tab has focus it will close the tool but if another tab is selected, the tool will have focus
+- modified the NCC Tool and Paint Tool to work multiple times after first launch
+- fixed the issue with GUI entries content being deselected on right click in the box in order to copy the value
+- some changes in GUI tooltips
+- modified the way key modifiers are detected in Gerber Editor Selection class and in Excellon Editor Selection class
+- updated the translations
+- fixed aperture move in Gerber Editor
+- fixed drills/slots move in Excellon Editor
+- RELEASE 8.96
+
+22.08.2019
+
+- added ability to turn ON/OFF the detachable capability of the tabs in Notebook through a context menu activated by right mouse button click on the Notebook header
+- added ability to turn ON/OFF the detachable capability of the tabs in Plot Tab Area through a context menu activated by right mouse button click on the Notebook header
+- added possibility to turn application portable from the Edit -> Preferences -> General -> App. Preferences -> Portable checkbox
+- moved the canvas setup into it's own function and called it in the init() function
+- fixed the Buffer Tool in Geometry Editor; made the Buffer entry field a QDoubleSpinner and set the lower limit to zero.
+- fixed Tool Cutout so when the target Gerber is a single Polygon then the created manual geometry will follow the shape if shape is freeform
+- fixed TclCommandFollow command; an older function name was used who yielded wrong results
+- in Tool Cutout for the manual gaps, now the moving geometry that cuts gaps will orient itself to fit the angle of the cutout geometry
+
+21.08.2019
+
+- added feature in Paint Tool allowing the painting to be done on Gerber objects
+- added feature in Paint Tool to set how (and if) the tools are sorted
+- added Edit -> Preferences GUI entries for the above just added features
+- added new entry in Properties Tool which is the calculated Convex Hull Area (should give a more precise area for the irregular shapes than the box area)
+- added some more strings in Properties Tool for the translation
+- in NCC Tool added area selection feature
+- fixed bug in Excellon parser for the Excellon files that do not put the type of zero suppression they use in the file (like DipTrace eCAD)
+- fixed some issues introduced in NCC Tool
+
+20.08.2019
+
+- added ability to do copper clearing through NCC Tool on Geometry objects
+- replaced the layout from Grid to Form for the Reference objects comboboxes in Paint Tool and in NCC Tool
+
+19.08.2019
+
+- updated the Edit -> Preferences to include also the Gerber Editor complete Preferences
+- started to update the app strings to make it easier for future translations
+- fixed the POT file and the German translation
+- some mods in the Tool Sub
+- fixed bug in Tool Sub that created issues when toggling visibility of the plots
+- fixed the Spanish, Brazilian Portuguese and Romanian translations
+
+18.08.2019
+
+- made the exported preferences formatted therefore more easily read
+- projects at startup don't work in another thread so there is no multithreading if I want to double click an project and to load it
+- added messages in the application window title which show the progress in loading a project (which is not thread-safe therefore keeping the app from fully initialize until finished)
+- in NCC Tool added a new parameter (radio button) that offer the choice on the order of the tools both in tools table and in execution of engraving; added as a parameter also in Edit -> Preferences -> Tools -> NCC Tool
+- added possibility to drag & drop FlatCAM config files (*.FlatConfig) into the canvas to be opened into the application
+- added GUI in Paint tool in beginning to add Paint by external reference object 
+- finished adding in Paint Tool the usage of an external object to set the extent of th area painted. For simple shapes (single Polygon) the shape can be anything, for the rest will be a convex hull of the reference object
+- modified NCC tool so for simple objects (single Polygon) the external object used as reference can have any shape, for the other types of objects the copper cleared area will be the convex hull of the reference object
+- modified the strings of the app wherever they contained the char seq <b> </b> so it is not included in the translated string
+- updated the translation files for the modified strings (and for the newly added strings)
+- added ability to lock toolbars within the context menu that is popped up on any toolbars right mouse click. The value is saved in QSettings and it is persistent between application startup's.
+
+17.08.2019
+
+- added estimated time of routing for the CNCJob and added travelled distance parameter for geometry, too
+- fixed error when creating CNCJob due of having the annotations disabled from preferences but the plot2() function from camlib.CNCJob class still performed operations who yielded TypeError exceptions
+- coded a more accurate way to estimate the job time in CNCJob, taking into consideration if there is a usage of multi depth which generate more passes
+- another fix (final one) for the Exception generated by the annotations set not to show in Preferences
+- updated translations and changed version
+- fixed installer issue for the x64 version due of the used CX_FREEZE python package which was in unofficial version (obviously not ready to be used)
+- fixed bug in Geometry Editor, in disconnect_canvas_event_handlers() where I left some part of code without adding a try - except block which was required
+- moved the initialization of the FlatCAM editors after a read of the default values. If I don't do this then only at the first start of the application the Editors are not functional as the Editor objects are most likely destroyed
+- fixed bug in FlatCAM editors that caused the shapes to be drawn without resolution when the app units where INCH
+- modified the transformation functions in all classes in camlib.py and FlatCAMObj.py to work with empty geometries
+- RELEASE 8.95
+
+17.08.2019
+
+- updated the translations for the new strings
+- RELEASE 8.94
+
+16.08.2019
+
+- working in Excellon Editor to Tool Resize to consider the slots, too
+- fixed a weird error that created a crash in the following scenario: create a new excellon, edit it, add some drills/slots, delete it without saving, create a new excellon, try to edit and a crash is issued due of a wrapped C++ error
+- fixed bug selection in Excellon editor that caused not to select the corresponding row (tool dia) in the tool table when a selection rectangle selected an even number of geometric elements
+- updated the default values to more convenient ones
+- remade the enable/disable plots functions to work only where it needs to (no sense in disabling a plot already disabled)
+- made sure that if multi depth is choosed when creating GCode then if the multidepth is more than the depth of cut only one cut is made (to the depth of cut)
+- each CNCJob object has now it's own text_collection for the annotations which allow for the individual enabling and disabling of the annotations
+- added new menu category in File -> Backup with two menu entries that duplicate the functions of the export/import preferences buttons from the bottom of the Preferences window
+- in Excellon Editor fixed the display of the number of slots in the Tool Table after the resize done with the Resize tool
+- in Excellon Editor -> Resize tool, made sure that when the slot is resized, it's length remain the same, because the tool should influence only the 'thickness' of the slot. Since I don't know anything but the geometry and tool diameters (old and new), this is only an approximation and computationally intensive
+- in Excellon Editor -> remade the Tool edit made by editing the diameter values in the Tools Table to work for slots too
+- In Excellon Editor -> fixed bug that caused incorrect display of the relative coordinates in the status bar
+
+15.08.2019
+
+- added Edit -> Preferences GUI and storage for the Excellon Editor Add Slots
+- added a confirmation message for objects delete and a setting to activate it in Edit -> Preferences -> Global
+- merged pull request from Mike Smith which fix an application crash when attempting to open a not-a-FlatCAM-project file as project
+- merged pull request from Mike Smith that add support for a new SVG element: <use>
+- stored inside FlatCAM app the VisPy data files and at the first start the application will try to copy those files to the APPDATA (roaming) folder in case of running under Windows OS
+- created a configuration file in the root/config/configuration.txt with a configuration line for portability. Set portable to True to run the app as portable
+- working on the Slots Array in Excellon Editor - building the GUI
+- added a failsafe path to the source folder from which to copy the VisPy data
+- fixed the GUI for Slot Arrays in Excellon Editor
+- finished the Slot Array tool in Excellon Editor
+- added the key shortcut handlers for Add Slot and Add Slot Array tools in Excellon Editor
+- started to work on the Resize tool for the case of Excellon slots in Excellon Editor
+- final fix for the VisPy data files; the defaults files are saved to the Config folder when the app is set to be portable
+- added the Slot Type parameter for exporting Excellon in Edit -> Preferences -> Excellon -> Export Excellon. Now the Excellon object can be exported also with drilled slot command G85
+- fixed bug in Excellon export when there are no zero suppression (coordinates with decimals)
+
+14.08.2019
+
+- fixed the loading of Excellon with slots and the saving of edited Excellon object in regard of slots, in Excellon Editor
+- fixed the Delete tool, Select tool in Excellon Editor to work for Slots too
+- changes in the way the edited Excellon with added slots is saved
+- added more icons and cursor in Excellon Editor for Slots related functions
+- in Excellon Editor fixed the selection issue which in a certain step created a failure in the Copy and Move tools.
+- in Excellon Editor fixed the selection with key modifier pressed
+- edited the mouse cursors and saved them without included thumbnail in a bid to remove some CRC warnings made by libpng
+
+13.08.2019
+
+- added new option in ToolSub: the ability to close (or not) the resulting paths when using tool on Geometry objects. Added also a new category in the Edit -> Preferences -> Tools, the Substractor Tool Options
+- some PEP8 changes in FlatCAMApp.py
+- added new settings in Edit -> Preferences -> General for Notebook Font size (set font size for the items in Project Tree and for text in Selected Tab) and for canvas Axis font size. The values are stored in QSettings.
+- updated translations
+- fixed a bug in FCDoubleSpinner GUI element
+- added a new parameter in NCC tool named offset. If the offset is used then the copper clearing will finish to a set distance of the copper features
+- fixed bugs in Geometry Editor
+- added protection's against the 'bowtie' geometries for Subtract Tool in Geometry Editor
+- added all the tools from Geometry Editor to the the contextual menu
+- fixed bug in Add Text Tool in Geometry Editor that gave error when clicking to place text without having text in the box
+- added all the tools from Gerber Editor to the the contextual menu
+- added the menu entry "Edit" in the Project contextual menu for Gerber objects
+- started to work in adding slots and slots array in Excellon Editor
+- in FCSlot finished the utility geometry and the GUI for it
+
+12.08.2019
+
+- 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)
+- 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
+
+11.08.2019
+
+- small changes regarding the Project Title
+- trying to fix reported bugs
+- made sure that the annotations are deleted when the object that contain them is deleted
+- fixed issue where the annotations for all the CNCJob objects are toggled together whenever the ones for an single object are toggled
+- optimizations in GeoEditor
+- updated translations
+
+10.08.2019
+
+- added new feature in NCC Tool: now another object can be used as reference for the area extent to be cleared of copper
+- fixed issue in the latest feature in NCC Tool: now it works also with reference objects made out of LineStrings (tool 'Path' in Geometry Editor)
+- translation files updated for the new strings (Google Translate)
+- RELEASE 8.93
+
+9.08.2019
+
+- added Exception handing for the case when the user is trying to save & overwrite a file already opened in another file
+- finished added 'Area' type of Paint in Paint Tool
+- fixed bug that created a choppy geometry for CNCJob when working in INCH
+- fixed bug that did not asked the user to save the preferences after importing a new set of preferences, after the user is trying to close the Preferences tab window
+
+7.08.2019
+
+- replaced setFixedWidth calls with setMinimumWidth
+- recoded the camlib.Geometry.isolation_geometry() function
+- started to work on Paint Area in Paint Tool
+
+6.08.2019
+
+- fixed bug that crashed the app after creating a new geometry, if a new object is loaded and the new geometry is deleted and then trying to select the just loaded new object
+- made some GUI elements in Edit -> Preferences to have a minimum width as opposed to the previous fixed one
+- fixed issue in the isolation function, if the isolation can't be done there will be generated no Geometry object 
+- some minor UI changes
+- strings added and translations updated
+
+5.08.2019
+
+- made sure that if using an negative Gerber isolation diameter, the resulting Geometry object will use a tool with positive diameter
+- fixed bug that when isolating a Gerber file made out of a single polygon, an RecursionException was issued together with inability to create tbe isolation
+- when applying a new language if there are any changes in the current project, the app will offer to save the project before the reboot
+
+3.08.2019
+
+- added project name to the window title
+- fulfilled request: When saving a CNC file, if the file name is changed in the OS window, the new name does appear in the “Selected” (in name) and “Project” tabs (in cnc_job)
+- solved bug such that the app is not crashing when some apertures in the Gerber file have no geometry. More than that, now the apertures that have geometry elements are bolded as opposed to the ones without geometry for which the text is unbolded
+- merged a pull request with language changes for Russian translate
+- updated the other translations
+
+31.07.2019
+
+- changed the order of the menu entries in the FIle -> Open ...
+- organized the list of recent files so the Project entries are to the top and separated from the other types of file
+- work on identification of changes in Preferences tab
+- added categories names for the recent files
+- added a detection if any values are changed in the Edit -> Preferences window and on close it will ask the user if he wants to save the changes or not
+- created a new menu entry in the File menu named Recent projects that will hold the recent projects and the previous "Recent files" will hold only the previous loaded files
+- updated all translations for the new strings
+- fixed bug recently introduced that when changing the units in the Edit -> Preferences it did not converted the values
+- fixed another bug that when selecting an Excellon object after disabling it it crashed the app
+- RELEASE 8.92
+
+30.07.2019
+
+- fixed bug that crashed the software when trying to edit a GUI value in Geometry selected tab without having a tool in the Tools Table
+- fixed bug that crashed the app when trying to add a tool without a tool diameter value
+- Spanish Google translation at 77%
+- changed the Disable plots menu entry in the context menu, into a Toggle Visibility menu entry
+- Spanish Google translation 100% but two strings (big ones) - needs review
+- added two more strings to translation strings (due of German language)
+- completed the Russian translation using the Google and Yandex translation engines (minus two big strings) - needs review
+
+28.07.2019
+
+- fixed issue with not using the current units in the tool tables after unit conversion
+- after unit conversion from Preferences, the default values are automatically saved by the app
+- in Basic mode, the tool type column is no longer hidden as it may create issues when using an painted geometry
+- some PEP8 clean-up in FlatCAMGui.py
+- fixed Panelize Tool to do panelization for multiple passes type of geometry that comes out of the isolation done with multiple passes
+
+20.07.2019
+
+- updated the CutOut tool so it will work on single PCB Gerbers or on PCB panel Gerbers
+- updated languages
+- 70% progress in Spanish Google translation
+
+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
+- some changes in the widgets used in the Selected tab for Geometry object
+- some PEP8 cleanup in FlatCAMObj.py
+- updated languages
+- 60% progress in Spanish Google translation
+
+17.07.2019
+
+- added some more strings to the translatable ones, especially the radio button labels
+- updated the .POT file and the available translations
+- 51% progress in Spanish Google translation
+- version date change
+
+16.07.2019
+
+- PEP8 correction in flatcamTools
+- merged the Brazilian-portuguese language from a pull request made by Carlos Stein
+- more PEP8 corrections
+
+15.07.2019
+
+- some PEP8 corrections
+
+13.07.2019
+
+- fixed a possible issue in Gerber Object class
+- added a new tool in Gerber Editor: Mark Area Tool. It will mark the polygons in a edited Gerber object with areas within a defined range, allowing to delete some of the not necessary  copper features
+- added new menu links in the Gerber Editor menu for Eraser Tool and Mark Area Tool
+- added key shortcuts for Eraser Tool (Ctrl+E) and Mark Area Tool (Alt+A) and updated the shortcuts list
+
+9.07.2019
+
+- some changes in the app.on_togle_units() to make sure we don't try to convert empty parameters which may cause crashes on FlatCAM units change
+- updated setup_ubuntu.sh file
+- made sure to import certain libraries in some of the FlatCAM files and not to rely on chained imports
+
+8.07.2019
+
+- fixed bug that allowed empty tool in the tools generated in Geometry object
+- fixed bug in Tool Cutout that did not allow the transfer of used cutout tool diameter to the cutout geometry object
+
+5.07.2019
+
+- fixed bug in CutOut Tool
+- some other bug in CutOut tool fixed
+
+1.07.2019
+
+- Spanish translation at 36%
+
+28.06.2019
+
+- Spanish translation (Google Translate) at 21%
+
+27.06.2019
+
+- added new translation: Spanish. Finished 10%
+
+23.06.2019
+
+- fixes issues with units conversion when the tool diameters are a list of comma separated values (NCC Tool, SolderPaste Tool and Geometry Object)
+- fixed a "typo" kind of bug in SolderPaste Tool
+- RELEASE 8.919
+
+22.06.2019
+
+- some GUI layout optimizations in Edit -> Preferences
+- added the possibility for multiple tool diameters in the Edit -> Preferences -> Geometry -> Geometry General -> Tool dia separated by comma
+- fixed scaling for the multiple tool diameters in Edit -> Preferences -> Geometry -> Geometry General -> Tool dia, for NCC tools more than 2 and for Solderpaste nozzles more than 2
+- fixed bug in CNCJob where the CNC Tools table will show always only 2 decimals for Tool diameters regardless of the current measuring units
+- made the tools diameters decimals in case of INCH FlatCAM units to be 4 instead of 3
+- fixed bug in updating Grid values whenever toggling the FlatCAM units and the X, Y Grid values are linked, bugs which caused the Y value to be scaled incorrectly
+- set the decimals for Grid values to be set to 6 if the units of FlatCAM is INCH and to set to 4 if FlatCAM units are METRIC
+- updated translations
+- updated the Russian translation from 51% complete to 69% complete using the Yandex translation engine
+- fixed recently introduced bug in milling drills/slots functions
+- moved Substract Tool from Menu -> Edit -> Conversions to Menu -> Tool
+- fixed bug in Gerber isolation (Geometry expects now a value in string format and not float)
+- fixed bug in Paint tool: now it is possible to paint geometry generated by External Isolation (or Internal isolation)
+- fixed bug in editing a multigeo Geometry object if previously a tool was deleted
+- optimized the toggle of annotations; now there is no need to replot the entire CNCJob object too on toggling of the annotations
+- on toggling off the plot visibility the annotations are turned off too
+- updated translations; Russian translation at 76% (using Yandex translator engine - needs verification by a native speaker of Russian)
+
+20.06.2019
+
+- fixed Scale and Buffer Tool in Gerber Editor
+- fixed Editor Transform Tool in Gerber Editor
+- added a message in the status bar when copying coordinates to clipboard with SHIFT + LMB click combo
+- languages update
+
+19.06.2019
+
+- milling an Excellon file (holes and/or slots) will now transfer the chosen milling bit diameter to the resulting Geometry object
+
+17.06.2019
+
+- fixed bug where for Geometry objects after a successful object rename done in the Object collection view (Project tab), deselect the object and reselect it and then in the Selected tab the name is not the new one but the old one
+- for Geometry objects, adding a new tool to the Tools table after a successful rename will now store the new name in the tool data
+
+15.06.2019
+
+- fixed bug in Gerber parser that made the Gerber files generated by Altium Designer 18 not to be loaded
+- fixed bug in Gerber editor - on multiple edits on the same object, the aperture size and dims were continuously multiplied due of the file units not being updated
+- restored the FlatCAMObj.visible() to a non-threaded default
+
+11.06.2019
+
+- fixed the Edit -> Conversion -> Join ... functions (merge() functions)
+- updated translations
+- Russian translate by @camellan is not finished yet
+- some PEP8 cleanup in camlib.py
+- RELEASE 8.918
+
+9.06.2019
+
+- updated translations
+- fixed the the labels for shortcut keys for zoom in and zoom out both in the Menu links and in the Shortcut list
+- made sure the zoom functions use the global_zoom_ratio parameter from App.self.defaults dictionary.
+- some PEP8 cleanup
+
+8.06.2019
+
+- make sure that the annotation shapes are deleted on creation of a new project
+- added folder for the Russian translation
+- made sure that visibility for TextGroup is set only if index is not None in VisPyVisuals.TextGroup.visible() setter
+
+7.06.2019
+
+- 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
+- updated translations
+
+5.06.2019
+
+- updated translations
+- some layout changes in Edit -> Preferences such that the German translation (longer words than English) to fit correctly
+- after editing an parameter the focus is lost so the user knows that something happened
+
+4.06.2019
+
+- PEP8 updates in FlatCAMExcEditor.py
+- added the Excellon Editor parameters to the Edit -> Preferences -> Excellon GUI
+- fixed a small bug in Excellon Editor
+- PEP8 cleanup in FlatCAMGui
+- finished adding the Excellon Editor parameters into the app logic and added a selection limit within Excellon Editor just like in the other editors
+
+3.06.2019
+
+- TclCommand Geocutout is now creating a new geometry object when working on a geometry, preserving also the origin object
+- added a new parameter in Edit -> Preferences -> CNCJob named Annotation Color; it controls the color of the font used for annotations
+- added a new parameter in Edit -> Preferences -> CNCJob named Annotation Size; it controls the size of the font used for annotations
+- made visibility change threaded in FlatCAMObj()
+
+2.06.2019
+
+- fixed issue with geometry name not being updated immediately after change while doing geocutout TclCommand
+- some changes to enable/disable project context menu entry handlers
+
+1.06.2019
+
+- fixed text annotation for CNC job so there are no overlapping numbers when 2 lines meet on the same point
+- fixed issue in CNC job plotting where some of the isolation polygons are painted incorrectly
+- fixed issue in CNCJob where the set circle steps is not used 
+
+31.05.2019
+
+- added the possibility to display text annotation for the CNC travel lines. The setting is both in Preferences and in the CNC object properties
+
+30.05.2019
+
+- editing a multi geometry will no longer pop-up a Tcl window
+- solved issue #292 where a new geometry renamed with many underscores failed to store the name in a saved project
+- the name for the saved projects are updated to the current time and not to the time of the app startup
+- some PEP8 changes related to comments starting with only one '#' symbol
+- more PEP8 cleanup
+- solved issue where after the opening of an object the file path is not saved for further open operations
+
+24.05.2019
+
+- added a toggle Grid button to the canvas context menu in the Grids submenu
+- added a toggle left panel button to the canvas context menu
+
+23.05.2019
+
+- fixed bug in Gerber editor FCDisk and FCSemiDisc that the resulting geometry was not stored into the '0' aperture where all the solids are stored
+- fixed minor issue in Gerber Editor where apertures were included in the saved object even if there was no geometric data for that aperture
+- some PEP8 cleanup in FlatCAMApp.py
+
+22.05.2019
+
+- Geo Editor - added a new editor tool, Eraser
+- some PEP8 cleanup of the Geo Editor
+- fixed some selection issues in the new tool Eraser in Geometry Editor
+- updated the translation files
+- RELEASE 8.917
+
+21.05.2019
+
+- added the file extension .ncd to the Excellon file extension list
+- solved parsing issue for Excellon files generated by older Eagle versions (v6.x)
+- Gerber Editor: finished a new tool: Eraser. It will erase certain parts of Gerber geometries having the shape of a selected shape.
+
+20.05.2019
+
+- more PEP8 changes in Gerber editor
+- Gerber Editor - started to work on a new editor tool: Eraser
+
+19.05.2019
+
+- fixed the Circle Steps parameter for both Gerber and Geometry objects not being applied and instead the app internal defaults were used.
+- fixed the Tcl command Geocutout issue that gave an error when using the 4 or 8 value for gaps parameter
+- made wider the '#' column for Apertures Table for Gerber Object and for Gerber Editor; in this way numbers with 3 digits can be seen
+- PEP8 corrections in FlatCAMGrbEditor.py
+- added a selection limit parameter for Geometry Editor
+- added entries in Edit -> Preferences for the new parameter Selection limit for both the Gerber and Geometry Editors.
+- set the buttons in the lower part of the Preferences Window to have a preferred minimum width instead of fixed width
+- updated the translation files
+
+18.05.2019
+
+- added a new toggle option in Edit -> Preferences -> General Tab -> App Preferences -> "Open" Behavior. It controls which path is used when opening a new file. If checked the last saved path is used when saving files and the last opened path is used when opening files. If unchecked then the path for the last action (either open or save) is used.
+- fixed App.convert_any2gerber to work with the new Gerber apertures data structure
+- fixed Tool Sub to work with the new Gerber apertures data structure
+- fixed Tool PDF to work with the new Gerber apertures data structure
+
+17.05.2019
+
+- remade the Tool Cutout to work on panels
+- remade the Tool Cutout such that on multiple applications on the same object it will yield the same result
+- fixed an issue in the remade Cutout Tool where when applied on a single Gerber object, the Freeform Cutout produced no cutout Geometry object
+- remade the Properties Tool such that it works with the new Gerber data structure in the obj.apertures. Also changed the view for the Gerber object in Properties
+- fixed issue with false warning that the Gerber object has no geometry after an empty Gerber was edited and added geometry elements
+
+16.05.2019
+
+- Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy
+- added a new parameter/feature: now the spindle can work in clockwise mode (CW) or counter clockwise mode (CCW)
+
+15.05.2019
+
+- rewrited the Gerber Parser in camlib - success
+- moved the self.apertures[aperture]['geometry'] processing for clear_geometry (geometry made with Gerber LPC command) in Gerber Editor
+- Gerber Editor: fixed the Poligonize Tool to work with new geometric structure and took care of a special case
+- Gerber Export is fixed to work with the new Gerber object data structure and it now works also for Gerber objects edited in Gerber Editor
+- Gerber Editor: fixed units conversion for obj.apertures keys that require it
+- camlib Gerber parser - made sure that we don't loose goemetry in regions
+- Gerber Editor - made sure that for some tools the added geometry is clean (the coordinates are non repeating)
+- covered some possible issues in Gerber Export
+
+12.05.2019
+
+- some modifications to ToolCutout
+
+11.05.2019
+
+- fixed issue in camlib.CNCjob.generate_from_excellon_by_tool() in the drill path optimization algorithm selection when selecting the MH algorithm. The new API's for Google OR-tools required some changes and also the time parameter can be now just an integer therefore I modified the GUI
+- made the Feedrate Rapids parameter to depend on the type of preprocessor choosed. It will be showed only for a preprocessor which the name contain 'marlin' and for any preprocessor's that have 'custom' in the name
+- fixed the camlib.Gerber functions of mirror, scale, offset, skew and rotate to work with the new data structure for apertures geometry
+- fixed Gerber Editor selection to work with the new Gerber data structure in self.apertures
+- fixed Gerber Editor FCPad class to work with the new Gerber data structure in self.apertures
+- fixed camlib.Gerber issues related to what happen after parsing rectangular apertures 
+- wip in camblib.Gerber
+- completely converted the Gerber editor to the new data structure
+- Gerber Editor: added a threshold limit for how many elements a move selection can have. If above the threshold only a bounding box Poly will be painted on canvas as utility geometry.
+
+10.05.2019
+
+- Gerber Editor - working in conversion to the new data format
+- made sure that only units toggle done in Edit -> Preferences will toggle the data in Preferences. The menu entry Edit -> Toggle Units and the shortcut key 'Q' will change only the display units in the app
+- optimized Transform tool
+- RELEASE 8.916
+
+9.05.2019
+
+- reworked the Gerber parser
+
+8.05.2019
+
+- added zoom fit for Set Origin command
+- added move action for solid_geometry stored in the gerber_obj.apertures
+- fixed camlib.Gerber skew, rotate, offset, mirror functions to work for geometry stored in the Gerber apertures
+- fixed Gerber Editor follow_geometry reconstruction
+- Geometry Editor: made the tool to be able to continuously move until the tool is exited either by ESC key or by right mouse button click
+- Geometry Editor Move Tool: if no shape is selected when triggering this tool, now it is possible to make the selection inside the tool
+- Gerber editor Move Tool: fixed a bug that repeated the plotting function unnecessarily 
+- Gerber editor Move Tool: if no shape is selected the tool will exit
+
+7.05.2019
+
+- remade the Tool Panelize GUI
+- work in Gerber Export: finished the header export
+- fixed the Gerber Object and Gerber Editor Apertures Table to not show extra rows when there are aperture macros in the object
+- work in Gerber Export: finished the body export but have some errors with clear geometry (LPC)
+- Gerber Export - finished
+
+6.05.2019
+
+- made units change from shortcut key 'Q' not to affect the preferences
+- made units change from Edit -> Toggle Units not to affect the preferences
+- remade the way the aperture marks are plotted in Gerber Object
+- 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 it can be edited
+- modified the default values for toolchangez and endz parameters so they are now safe in all cases
+
+5.05.2019
+
+- another fix for bug in clear geometry processing for Gerber apertures
+- added a protection for the case that the aperture table is part of a deleted object
+- in Script Editor added support for auto-add closing parenthesis, brace and bracket
+- in Script Editor added support for "CTRL + / " key combo to comment/uncomment line
+
+4.05.2019
+
+- fixed bug in camlib.parse_lines() in the clear_geometry processing section for self.apertures
+- fixed bug in parsing Gerber regions (a point was added unnecessary)
+- renamed the menu entry Edit -> Copy as Geo to Convert Any to Geo and moved it in the Edit -> Conversion
+- created a new function named Convert Any to Gerber and installed it in Edit -> Conversion. It's doing what the name say: it will convert an Geometry or Excellon FlatCAM object to a Gerber object.
+
+01.05.2019
+
+- the project items color is now controlled from Foreground Role in ObjectCollection.data()
+- made again plot functions threaded but moved the dataChanged signal (update_view() ) to the main thread by using an already existing signal (plots_updated signal) to avoid the errors with register QVector
+- Enable/Disable Object toggle key ("Space" key) will trigger also the datChanged signal for the Project MVC
+- added a new setting for the color of the Project items, the color when they are disabled.
+- fixed a crash when triggering 'Jump To' menu action (shortcut key 'J' worked ok)
+- made some mods to what can be translated as some of the translations interfered with the correct functioning of FlatCAM
+- updated the translations
+- fixed bugs in Excellon Editor
+- Excellon Editor:  made Add Pad tool to work until right click
+- Excellon Editor: fixed mouse right click was always doing popup context menu
+- GUIElements.FCEntry2(): added a try-except clause
+- made sure that the Tools Tab is cleared on Editors exit
+- Geometry Editor: restored the old behavior: a tool is active until it is voluntarily exited: either by using the 'ESC' key, or selecting the Select tool or new: right click on canvas
+- RELEASE 8.915
+
+30.04.2019
+
+- in ObjectCollection class, made sure that renaming an object in Project View does not result in an empty name. If new name is blank the rename is cancelled.
+- made ObjectCollection.TreeItem() inherit KeySensitiveListVIew and implicitly QTreeView (in the hope that the theme applied on app will be applied on the tree items, too (for MacOs new DarkUI theme)
+- renamed SilkScreen Tool to Substract Tool and move it's menu location in Edit -> Conversion
+- started to modify the Substract Tool to work on Geometry objects too
+- progress in the new Substract Tool for Geometry Objects
+- finished the new Substract Tool
+- added new setting for the color of the Project Tree items; it helps in providing contrast when using dark theme like the one in MacOS
+
+29.04.2019
+
+- solved bug in Gerber Editor: the '0' aperture (the region aperture) had no size which created errors. Made the size to be zero.
+- solved bug in editors: the canvas selection shape was not deleted on mouse release if the grid snap was OFF
+- solved bug in Excellon Editor: when selecting a drill hole on canvas the selected row in the Tools Table was not the correct one but the next highest row
+- finished the Silkscreen Tool but there are some limitations (some wires fragments from silkscreen are lost)
+- solved the issue in Silkscreen Tool with losing some fragments of wires from silkscreen
+
+26.04.2019
+
+- small changes in GUI; optimized contextual menu display
+- made sure that the Project Tab is disabled while one of the Editors is active and it is restored after returning to app
+- fixed some bugs recently introduced in Editors due of the changes done to the way mouse panning is detected 
+- cleaned up the context menu's when in Editors; made some structural changes
+- updated the code in camlib.CNCJob.generate_from_excellon_by_tools() to work with the new API from Google OR-Tools
+- all Gerber regions (G36 G37) are stored in the '0' aperture
+- fixed a bug that added geometry with clear polarity in the apertures where was not supposed to be
+
+25.04.2019
+
+- Geometry Editor: modified the intersection (if the selected shapes don't intersects preserve them) and substract functions (delete all shapes that were used in the process)
+- work in the ToolSub
+- for all objects, if in Selected the object name is changed to the same name, the rename is not done (because there is nothing changed)
+- fixed Edit -> Copy as Geom function handler to work for Excellon objects, too
+- made sure that the mouse pointer is restored to default on Editor exit
+- added a toggle button in Preferences to toggle on/off the display of the selection box on canvas when the user is clicking an object or selecting it by mouse dragging.
+
+24.04.2019
+
+- PDF import tool: working in making the PDF layer rendering multithreaded in itself (one layer rendered on each worker)
+- PDF import tool: solved a bug in parsing the rectangle subpath (an extra point was added to the subpath creating nonexisting geometry)
+- PDF import tool: finished layer rendering multithreading
+- New tool: Silkscreen Tool: I am trying to remove the overlapped geo with the soldermask layer from overlay layer; layed out the class and functions - not working yet
+
+23.04.2019
+
+- Gerber Editor: added two new tools: Add Disc and Add SemiDisc (porting of Circle and Arc from Geometry Editor)
+- Gerber Editor: made Add Pad repeat until user exits the Add Pad through either mouse right click, or ESC key or deselecting the Add Pad menu item
+- Gerber and Geometry Editors: fixed some issues with the Add Arc/Add Semidisc; in mode 132, the norm() function was not the one from numpy but from a FlatCAM Class. Also fixed some of the texts and made sure that when changing the mode, the current points are reset to prepare for the newly selected mode.
+- Fixed Measurement Tool to show the mouse coordinates on the status bar (it was broken at some point)
+- updated the translation files
+- added more custom mouse cursors in Geometry and Gerber Editors
+- RELEASE 8.914
+
+22.04.2019
+
+- added PDF file as type in the Recent File list and capability to load it from there
+- PDF's can be drag & dropped on the GUI to be loaded
+- PDF import tool: added support for save/restore Graphics stack. Only for scale and offset transformations and for the linewidth. This is the final fix for Microsoft PDF printer who saves in PDF format 1.7
+- PDF Import tool: added support for PDF files that embed multiple Gerber layers (top, bottom, outline, silkscreen etc). Each will be opened in it's own Gerber file. The requirement is that each one is drawn in a different color
+- PDF Import tool: fixed bugs when drag & dropping PDF files on canvas the files geometry previously opened was added to the new one. Also scaling issues. Solved.
+- PDF Import tool: added support for detection of circular geometry drawn with white color which means actually invisible color. When detected, FlatCAM will build an Excellon file out of those geoms.
+- PDF Import tool: fixed storing geometries in apertures with the right size (before they were all stored in aperture D10)
+
+21.04.2019
+
+- fixed the PDF import tool to work with files generated by the Microsoft PDF printer (chained subpaths)
+- in PDF import tool added support for paths filled and at the same time stroked ('B' and 'B*'commands)
+- added a shortcut key for PDF Import Tool (Alt+Q) and updated the Shortcut list (also with the 'T' and 'R' keys for Gerber Editor where they control the bend in Track and Region tool and the 'M' and 'D' keys for Add Arc tool in Geometry Editor)
+
+20.04.2019
+
+- finished adding the PDF import tool although it does not support all kinds of outputs from PDF printers. Microsoft PDF printer is not supported.
+
+19.04.2019
+
+- started to work on PDF import tool
+
+
+18.04.2019
+
+- Gerber Editor: added custom mouse cursors for each mode in Add Track Tool
+- Gerber Editor: Poligonize Tool will first fuse polygons that touch each other and at a second try will create a polygon. The polygon will be automatically moved to Aperture '0' (regions).
+- Gerber Editor: Region Tool will add regions only in '0' aperture
+- Gerber Editor: the bending mode will now survive until the tool is exited
+- Gerber Editor: solved some bugs related with deleting an aperture and updating the last_selected_aperture
+
+17.04.2019
+
+- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
+- fixed version check
+- added custom mouse cursors for some tools in Gerber Editor
+- Gerber Editor: added multiple modes to lay a Region: 45-degrees, reverse 45-degrees, 90-degrees, reverse 90-degrees and free-angle. Added also key shortcuts 'T' and 'R' to cycle forward, respectively in reverse through the modes.
+- Excellon Editor: fixed issue not remembering last tool after adding a new tool
+- added custom mouse cursors for Excellon and Geometry Editors in some of their tools
+
+16.04.2019
+
+- added ability to use ENTER key to finish tool adding in Editors, NCC Tool, Paint Tool and SolderPaste Tool.
+- Gerber Editor: started to add modes of laying a track
+- Gerber Editor: Add Track Tool: added 5 modes for laying a track: 45-degrees, reverse-45 degrees, 90-degrees, reverse 90-degrees and free angle. Key 'T' will cycle forward through the modes and key 'R' will cycle in reverse through the track laying modes.
+- Gerber Editor: Add Track Tool: first right click will finish the track. Second right click will exit the Track Tool and return to Select Tool.
+- Gerber Editor: added protections for the Pad Array and Pad Tool for the case when the aperture size is zero (the aperture where to store the regions)
+
+15.04.2019
+
+- working on a new tool to process automatically PcbWizard Excellon files which are generated in 2 files
+- finished ToolPcbWizard; it will autodetect the Excellon format, units from the INF file
+- Gerber Editor: reduced the delay to show UI when editing an empty Gerber object
+- update the order of event handlers connection in Editors to first connect new handlers then disconnect old handlers. It seems that if nothing is connected some VispY functions like canvas panning no longer works if there is at least once nothing connected to the 'mouse_move' event
+- Excellon Editor: update so always there is a tool selected even after the Excellon object was just edited; before it always required a click inside of the tool table, not you do it only if needed.
+- fixed the menu File -> Edit -> Edit/Close Editor entry to reflect the status of the app (Editor active or not)
+- added support in Excellon parser for autodetection of Excellon file format for the Excellon files generated by the following ECAD sw: DipTrace, Eagle, Altium, Sprint Layout
+- Gerber Editor: finished a new tool: Poligonize Tool (Alt+N in Editor). It will fuse a selection of tracks into a polygon. It will fill a selection of polygons if they are apart and it will make a single polygon if the selection is overlapped. All the newly created filled polygons will be stored in aperture '0' (if it does not exist it will be automatically created)
+- fixed a bug in Move command in context menu who crashed the app when triggered
+- Gerber Editor: when adding a new aperture it will be store as the last selected and it will be used for any tools that are triggered until a new aperture is selected.
+
+14.04.2019
+
+- Gerber Editor: Remade the processing of 'clear_geometry' (geometry generated by polygons made with Gerber LPC command) to work if more than one such polygon exists
+- Gerber Editor: a disabled/enabled sequence for the VisPy cursor on Gerber edit make the graphics better
+- Editors: activated an old function that was no longer active: each tool can have it's own set of shortcut keys, the Editor general shortcut keys that are letters are overridden
+- Gerber and Geometry editors, when using the Backspace keys for certain tools, they will backtrack one point but now the utility geometry is immediately updated
+- In Geometry Editor I fixed bug in Arc modes. Arc mode shortcut key is now key 'M' and arc direction change shortcut key is 'D'
+- moved the key handler out of the Measurement tool to flatcamGUI.FlatCAMGui.keyPressEvent()
+- Gerber Editor: started to add new function of poligonize which should make a filled polygon out of a shape
+- cleaned up Measuring Tool
+- solved bug in Gerber apertures size and dimensions values conversion when file units are different than app units
+
+13.04.2019
+
+- updating the German translation
+- Gerber Editor: added ability to change on the fly the aperture after one of the tools: Add Pad or Add Pad Array is activated
+- Gerber Editor: if a tool is cancelled via key shortcut ESCAPE, the selection is now deleted and any other action require a new selection
+- finished German translation (Google translated with some adjustments)
+- final fix for issue #277. Previous fix was applied only for one case out of three.
+- RELEASE 8.913
+
+12.04.2019
+
+- Gerber Editor: added support for Oblong type of aperture
+- fixed an issue with automatically filled in aperture code when the edited Gerber file has no apertures; established an default with value 10 (according to Gerber specifications)
+- fixed a bug in editing a blank Gerber object
+- added handlers for the Gerber Editor context menu
+- updated the translation template POT file and the EN PO/MO files
+- Gerber Editor: added toggle effect to the Transform Tool
+- Gerber Editor: added shortcut for Transform Tool and also toggle effect here, too
+- updated the shortcut list with the Gerber Editor shortcut keys
+- Gerber Editor: fixed error when adding an aperture with code value lower than the ones that already exists
+- when adding an aperture with code '0' (zero) it will automatically be set with size zero and type: 'REG' (from region); here we store all the regions from a Gerber file, the ones without a declared aperture
+- Gerber Editor: added support for Gerber polarity change commands (LPD, LPC)
+- moved the polarity change processing from FlatCAMGrbEditor() class to camlib.Gerber().parse_lines()
+- made optional the saving of an edited object. Now the user can cancel the changes to the object.
+- replaced the standard buttons in the QMessageBox's used in the app with custom ones that can have text translated
+- updated the POT translation file and the MO/PO files for English and Romanian language
+
+11.04.2019
+
+- changed the color of the marked apertures to the global_selection_color
+- Gerber Editor: added Transformation Tool and Rotation key shortcut
+- in all Editors, manually deactivating a button in the editor toolbar will automatically select the 'Select' button
+- fixed Excellon Editor selection: when a tool is selected in Tools Table, all the drills belonging to that tool are selected. When a drill is selected on canvas, the associated tool will be selected without automatically selecting all other drills with same tool
+- Gerber Editor: added Add Pad Array tool
+- Gerber Editor: in Add Pad Array tool, if the pad is not circular type, for circular array the pad will be rotated to match the array angle
+- Gerber Editor: fixed multiple selection with key modifier such that first click selects, second deselects
+
+10.04.2019
+
+- Gerber Editor: added Add Track and Add Region functions
+- Gerber Editor: fixed key shortcuts
+- fixed setting the Layout combobox in Preferences according to the current layout
+- created menu links and shortcut keys for adding a new empty Gerber objects; on update of the edited Gerber, if the source object was an empty one (new blank one) this source obj will be deleted
+- removed the old apertures editing from Gerber Obj selected tab
+- Gerber Editor: added Add Pad (circular or rectangular type only)
+- Gerber Editor: autoincrement aperture code when adding new apertures
+- Gerber Editor: automatically calculate the size of the rectangular aperture
+
+9.04.2019
+
+- Gerber Editor: added buffer and scale tools
+- Gerber Editor: working on aperture selection to show on Aperture Table
+- Gerber Editor: finished the selection on canvas; should be used as an template for the other Editors
+- Gerber Editor: finished the Copy, Aperture Add, Buffer, Scale, Move including the Utility geometry
+- Trying to fix bug in Measurement Tool: the mouse events don't disconnect
+- fixed above bug in Measurement Tool (but there is a TODO there)
+
+7.04.2019
+
+- default values for Jump To function is jumping to origin (0, 0)
+
+6.04.2019
+
+- fixed bug in Geometry Editor in buffer_int() function that created an Circular Reference Error when applying buffer interior on a geometry.
+- fixed issue with not possible to close the app after a project save.
+- preliminary Gerber Editor.on_aperture_delete() 
+- fixed 'circular reference' error when creating the new Gerber file in Gerber Editor
+- preliminary Gerber Editor.on_aperture_add()
+
+5.04.2019
+
+- Gerber Editor: made geometry transfer (which is slow) to Editor to be multithreaded
+- Gerber Editor: plotting process is showed in the status bar
+- increased the number of workers in FlatCAM and made the number of workers customizable from Preferences
+- WIP in Gerber Editor: geometry is no longer stored in a Rtree storage as it is not needed
+- changed the way delayed plot is working in Gerber Editor to use a Qtimer instead of python threading module
+- WIP in Gerber Editor
+- fixed bug in saving the maximized state
+- fixed bug in applying default language on first start
+~~- on activating 'V' key shortcut (zoom fit) the mouse cursor is now jumping to origin (0, 0)~~
+- fixed bug in saving toolbars state; the file was saved before setting the self.defaults['global_toolbar_view]
+
+4.04.2019
+
+- added support for Gerber format specification D (no zero suppression) - PCBWizard Gerber files support
+- added support for Excellon file with no info about tool diameters - PCB Wizard Excellon file support
+- modified the bogus diameters series for Excellon objects that do not have tool diameter info
+- made Excellon Editor aware of the fact that the Excellon object that is edited has fake (bogus) tool diameters and therefore it will not sort the tools based on diameter but based on tool number
+- fixed bug on Excellon Editor: when diameter is edited in Tools Table and the target diameter is already in the tool table, the drills from current tool are moved to the new tool (with new dia) - before it crashed
+- fixed offset after editing drill diameters in Excellon Editor.
+
+3.04.2019
+
+- fixed plotting in Gerber Editor
+- working on GUI in Gerber Editor
+- added a Gcode end_command: default is M02
+- modified the calling of the editor2object() slot function to fix an issue with updating geometry imported from SVG file, after edit
+- working on Gerber Editor - added the key shortcuts: wip
+- made saving of the project file non-blocking and also while saving the project file, if the user tries again to close the app while project file is being saved, the app will close only after saving is complete (the project file size is non zero)
+- fixed the camlib.Geometry.import_svg() and camlib.Gerber.bounds() to work when importing SVG files as Gerber
+
+31.03.2019
+
+- fixed issue #281 by making generation of a convex shape for the freeform cutout in Tool Cutout a choice rather than the default
+- fixed bug in Tool Cutout, now in manual cutout mode the gap size reflect the value set
+- changed Measuring Tool to use the mouse click release instead of mouse click press; also fixed a bug when using the ESC key.
+- fixed errors when the File -> New Project is initiated while an Editor is still active.
+- the File->Exit action handler is now self.final_save() 
+- wip in Gerber editor
+
+29.03.2019
+
+- update the TCL keyword list
+- fix on the Gerber parser that makes searching for '%%' char optional when doing regex search for mode, units or image polarity. This allow loading Gerber files generated by the ECAD software TCl4.4
+- fix error in plotting Excellon when toggling units
+- FlatCAM editors now are separated each in it's own file
+- fixed TextTool in Geometry Editor so it will open the notebook on activation and close it after finishing text adding
+- started to work on a Gerber Editor
+- added a fix in the Excellon parser by allowing a comma in the tool definitions between the diameter and the rest
+
+28.03.2019
+
+- About 45% progress in German translation
+- new feature: added ability to edit MultiGeo geometry (geometry from Paint Tool)
+- changed all the info messages that are of type warning, error or success so they have a space added after the keyword
+- changed the Romanian translation by adding more diacritics  
+- modified Gerber parser to copy the follow_geometry in the self.apertures
+- modified the Properties Tool to show the number of elements in the follow_geometry for each aperture
+- modified the copy functions to copy the follow_geometry and also the apertures if it's possible (only for Gerber objects)
+
+27.03.2019
+
+- added new feature: user can delete apertures in Advanced mode and then create a new FlatCAM Gerber object
+- progress in German translation. About 27% done.
+- fixed issue #278. Crash on name change in the Name field in the Selected Tab.
+
+26.03.2019
+
+- fixed an issue where the Geometry plot function protested that it does not have an parameter that is used by the CNCJob plot function. But both inherit from FaltCAMObj plot function which does not have that parameter so something may need to be changed. Until then I provided a phony keyboard parameter to make that function 'shut up'
+- fixed bug: after using Paint Tool shortcut keys are disabled
+- added CNCJob geometry for the holes created by the drills from Excellon objects
+
+25.03.2019
+
+- in the TCL completer if the word is already complete don't add it again but add a space
+- added all the TCL keywords in the completer keyword list
+- work in progress in German translation ~7%
+- after any autocomplete in TCL completer, a space is added
+- fixed an module import issue in NCC Tool
+- minor change (optimization) of the CNCJob UI
+- work in progress in German translation ~20%
+
+22.03.2019
+
+- fixed an error that created a situation that when saving a project with some of the CNCJob objects disabled, on project reload the CNCJob objects are no longer loaded
+- fixed the Gerber.merge() function. When some of the Gerber files have apertures with same id, create a new aperture id for the object that is fused because each aperture id may hold different geometries.
+- changed the autoname for saving Preferences, Project and PNG file
+
+20.03.2019
+
+- added autocomplete finish with ENTER key for the TCL Shell
+- made sure that the autocomplete function works only for FlatCAM Scripts
+- ESC key will trigger normal view if in full screen and the ESC key is pressed
+- added an icon and title text for the Toggle Units QMessageBox
+
+19.03.2019
+
+- added autocomplete for Code editor;
+- autocomplete in Code Editor is finished by hitting either TAB key or ENTER key
+- fixed the Gerber.merge() to work for the case when one of the merged Gerber objects solid_geometry type is Polygon and not a list
+
+18.03.2019
+
+- added ability to create new scripts and open scripts in FlatCAM Script Editor
+- the Code Editor tab name is changed according to the task; 'save' and 'open' buttons will have filters installed for the QOpenDialog fit to the task
+- added ability to run a FlatCAM Tcl script by double-clicking on the file
+- in Code Editor added shortcut combo key Ctrl+Shift+V to function as a Special Paste that will replace the '\' char with '/' so the Windows paths will be pasted correctly for TCL Shell. Also doing SHIFT + LMB on the Paste in contextual menu is doing the same.
+
+17.03.2019
+
+- remade the layout in 2Sided Tool
+- work in progress for translation in Romanian - 91%
+- changed some of the app strings formatting to work better with Poedit translation software
+- fixed bug in Drillcncjob TclCommand
+- finished translation in Romanian
+- made the translations work when the app is frozen with CX_freeze
+- some formatting changes for the application strings
+- some changes on how the first layout is applied
+- minor bug fixes (typos from copy/paste from another part of the program)
+
+16.03.2019
+
+- fixed bug in Paint Tool - Single Poly: no geometry was generated
+- work in progress for translation in Romanian - 70%
+
+13.03.2019
+
+- made the layout combobox current item from Preferences -> General window to reflect the current layout
+- remade the POT translate file
+- work in progress in translation for Romanian language 44%
+- fix for showing tools by activating them from the Menu - final fix.
+
+11.03.2019
+
+- changed some icons here and there
+- fixed the Properties Project menu entry to work on the new way
+- in Properties tool now the Gerber apertures show the number of polygons in 'solid_geometry' instead of listing the objects
+- added a visual cue in Menu -> Edit about the entries to enter the Editor and to Save & Exit Editor. When one is enabled the other is disabled.
+- grouped all the UI files in flatcamGUI folder
+- grouped all parser files in flatcamParsers folder
+- another changes to the final_save() function
+- some strings were left outside the translation formatting - fixed
+- finished the replacement of '_' symbols throughout the app which conflicted with the _() function used by the i18n
+- reverted changes in Tools regarding the toggle effect - now they work as expected
+
+10.03.2019
+
+- added a fix in the Gerber parser when adding the geometry in the self.apertures dict for the case that the current aperture is None (Allegro does that)
+- finished support for internationalization by adding a set of .po/.mo files for the English language. Unfortunately the final action can be done only when Beta will be out of Beta (no more changes) or when I will decide to stop working on this app.
+- changed the tooltip for 'feedrate_rapids' parameter to point out that this parameter is useful only for the Marlin preprocessor
+- fix app crash for the case that there are no translation files
+- fixed some forgotten strings to be prepared for internationalization in ToolCalculators
+- fixed Tools menu no longer working due of changes
+- added some test translation for the ToolCalculators (in Romanian)
+- fixed bug in ToolCutOut where for each tool invocation the signals were reconnected
+- fixed some issues with ToolMeasurement due of above changes
+- updated the App.final_save() function
+- fixed an issue created by the fact that I used the '_' char inside the app to designate unused info and that conflicted with the _() function used by gettext
+- made impossible to try to reapply current language that it's already applied (un-necessary)
+
+8.03.2019
+
+- fixed issue when doing th CTRL (or SHIFT) + LMB, the focus is automatically moved to Project Tab
+- further work in internationalization, added a fallback to English language in case there is no translation for a string
+- fix for issue #262: when doing Edit-> Save & Close Editor on a Geometry that is not generated through first entering into an Editor, the geometry disappear
+- finished preparing for internationalization for the files: camlib and objectCollection
+- fixed tools shortcuts not working anymore due of the new toggle parameter for the .run().
+- finished preparing for internationalization for the files: FlatCAMEditor, FlatCAMGUI
+- finished preparing for internationalization for the files: FlatCAMObj, ObjectUI
+- sorted the languages in the Preferences combobox
+
+7.03.2019
+
+- made showing a shape when hovering over objects, optional, by adding a Preferences -> General parameter
+- starting to work in internationalization using gettext()
+- Finished adding _() in FlatCAM Tools
+- fixed Measuring Tool - after doing a measurement the Notebook was switching to Project Tab without letting the user see the results
+- more work on the translation engine; the app now restarts after a language is applied
+- added protection against using Travel Z parameter with negative or zero value (in Geometry).
+- made sure that when the Measuring Tools is active after last click the Status bar is no longer deleted
+
+6.03.2019
+
+- modified the way the FlatCAM Tools are run from toolbar as opposed of running them from other sources
+- some Gerber UI changes
+
+5.03.2019
+
+- modified the grbl-laser preprocessor lift_code()
+- treated an error created by Z_Cut parameter being None
+- changed the hover and selection box transparency
+
+4.03.2019
+
+- finished work on object hovering
+- fixed Excellon object move and all the other transformations
+- starting to work on Manual Cutout Tool
+- remade the CutOut Tool
+- finished Manual Cutout Tool by adding utility geometry to the cutting geometry
+- added CTRL + click behavior for adding manual bridge gaps in Cutout Tool
+- in Tool Cutout added shortcut key 'Escape' to cancel the current adding of bridge gaps
+
+3.03.2019
+
+- minor UI changes for Gerber UI
+- ~~after an object move, the apertures plotted shapes are deleted from canvas and the 'mark all' button is deselected~~
+- after move tool action or any other transform (rotate, skew, scale, mirror, offset), the plotted apertures are kept plotted.
+- changing units now will convert all the default values from one unit type to another
+- prettified the selection shape and the moving shape
+- initial work in object hovering shape
+
+02.03.2019
+
+- fixed offset, rotate, scale, skew for follow_geometry. Fixed the move tool also.
+- fixed offset, rotate, scale, skew for 'solid_geometry' inside the self.apertures.
+
+28.02.2019
+
+- added a change that when a double click is performed in a object on canvas resulting in a selection, if the notebook is hidden then it will be displayed
+- progress in ToolChange Custom commands replacement and rename
+
+27.02.2019
+
+- made the Custom ToolChange Text area in CNCJob Selected Tab depend on the status of the ToolChange Enable Checkbox even in the init stage.
+- added some parameters throughout camlib gcode generation functions; handled some possible errors (e.g like when attempting to use an empty Custom GCode Toolchange)
+- added toggle effect for the tools in the toolbar.
+- enhanced the toggle effect for the tools in the Tools Toolbar and also for Notebook Tab selection: if the current tool is activated it will toggle the notebook side but only if the installed widget is itself. If coming from another tool, the notebook will stay visible
+- upgraded the Tool Cutout when done from Gerber file to create a convex_hull around the Gerber file rather than trying to isolate it
+- added some protections for the FlatCAM Tools run after an object was loaded
+
+26.02.2019
+
+- added a function to read the parameters from ToolChange macro Text Box (I need to move it from CNCJob to Excellon and Geometry)
+- fixed the geometry adding to the self.apertures in the case when regions are done without declaring any aperture first (Allegro does that). Now, that geometry will be stored in the '0' aperture with type REG
+- work in progress to Toolchange_Custom code replacement -> finished the parse and replace function
+- fixed mouse selection on canvas, mouse drag, mouse click and mouse double click
+- fixed Gerber Aperture Table dimensions
+- added a Mark All button in the Gerber aperture table.
+- because adding shapes to the shapes collection (when doing Mark or Mark All) is time consuming I made the plot_aperture() threaded.
+- made the polygon fusing in modified Gerber creation, a list comprehension in an attempt for optimization
+- when right clicking the files in Project tab, the Save option for Excellon no longer export it but really save the original. 
+- in ToolChange Custom Code replacement, the Text Box in the CNCJob Selected tab will be active only if there is a 'toolchange_custom' in the name of the preprocessor file. This assume that it is, or was created having as template the Toolchange Custom preprocessor file.
+
+
+25.02.2019
+
+- fixed the Gerber object UI layout
+- 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
+- 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
+
+24.02.2019
+
+- fixed a small bug in the Tool Solder Paste: the App don't take into consideration pads already filled with solder paste.
+- prettified the defaults files and the recent file. Now they are ordered and human readable
+- added a Toggle Code Editor Menu and key shortcut
+- added the ability to open FlatConfig configuration files in Code Editor, Modify them and then save them.
+- added ability to double click the FlatConfig files and open them in the FlatCAM Code Editor (to be verified)
+- when saving a file from Code Editor and there is no object active then the OpenFileDialog filters are reset to FlatConfig files.
+- reverted a change in GCode that might affect Gerber polarity change in Gerber parser
+- ability to double click the FlatConfig files and open them in the FlatCAM Code Editor - fixed and verified
+- fixed the Set To Origin function when Escape was clicked
+- added all the Tools in a new ToolBar
+- fixed bug that after changing the layout all the toolbar actions are no longer working
+- fixed bug in Set Origin function
+- fixed a typo in Toolchange_Probe_MACH3 preprocessor
+
+23.02.2019
+
+- remade the SolderPaste geometry generation function in ToolSoderPaste to work in certain scenarios where the Gerber pads in the SolderPaste mask Gerber may be just pads outlines
+- updated the Properties Tool to include more information's, also details if a Geometry is of type MultiGeo or SingleGeo
+- remade the Preferences GUI to include the Advanced Options in a separate way so it is obvious which are displayed when App Level is Advanced.
+- added protection, not allowing the user to make a Paint job on a MultiGeo geometry (one that is converted in the Edit -> Conversion menu)) because it is not supported
+
+22.02.2019
+
+- added Repetier preprocessor file
+- removed "added ability to regenerate objects (it's actually deletion followed by recreation)" because of the way Python pass parameters to functions by reference instead of copy
+- added ability to toggle globally the display of ToolTips. Edit -> Preferences -> General -> Enable ToolTips checkbox.
+- added true fullscreen support (for Windows OS)
+- added the ability of context menu inside the GuiElements.FCCombobox() object.
+- remade the UI for ToolSolderPaste. The object comboboxes now have context menu's that allow object deletion. Also the last object created is set as current item in comboboxes.
+- some GUI elements changes
+
+21.02.2019
+
+- added protection against creating CNCJob from an empty Geometry object (with no geometry inside)
+- changed the shortcut key for YouTube channel from F2 to key F4
+- changed the way APP LEVEL is showed both in Edit -> Preferences -> General tab and in each Selected Tab. Changed the ToolTips content for this.
+- added the functions for GCode View and GCode Save in Tool SolderPaste
+- some work in the Gcode generation function in Tool SolderPaste
+- added protection against trying to create a CNCJob from a solder_paste dispenser geometry. This one is different than the default Geometry and can be handled only by SolderPaste Tool.
+- ToolSolderPaste tools (nozzles) now have each it's own settings
+- creating the camlib functions for the ToolSolderPaste gcode generation functions
+- finished work in ToolSolderPaste
+- fixed issue with not updating correctly the plot kind (all, cut, travel) when clicking in the CNC Tools Table plot buttons
+- made the GCode Editor for ToolSolderPaste clear the text before updating the Code Editor tab
+- all the Tabs in Plot Area are closed (except Plot Area itself) on New Project creation
+- added ability to regenerate objects (it's actually deletion followed by recreation)
+
+20.02.2019
+
+- finished added a Tool Table for Tool SolderPaste
+- working on multi tool solder paste dispensing
+- finished the Edit -> Preferences defaults section
+- finished the UI, created the preprocessor file template
+- finished the multi-tool solder paste dispensing: it will start using the biggest nozzle, fill the pads it can, and then go to the next smaller nozzle until there are no pads without solder.
+
+19.02.2019
+
+- added the ability to compress the FlatCAM project on save with LZMA compression. There is a setting in Edit -> Preferences -> Compression Level between 0 and 9. 9 level yields best compression at the price of RAM usage and time spent.
+- made FlatCAM able to load old type (uncompressed) FlatCAM projects
+- fixed issue with not loading old projects that do not have certain information's required by the new versions of FlatCAM
+- compacted a bit more the GUI for Gerber Object
+- removed the Open Gerber with 'follow' menu entry and also the open_gerber Tcl Command attribute 'follow'. This is no longer required because now the follow_geometry is stored by default in a Gerber object attribute gerber_obj.follow_geometry
+- added a new parameter for the Tcl CommandIsolate, named: 'follow'. When follow = 1 (True) the resulting geometry will follow the Gerber paths.
+- added a new setting in Edit -> Preferences -> General that allow to select the type of saving for the FlatCAM project: either compressed or uncompressed. Compression introduce an time overhead to the saving/restoring of a FlatCAM project.
+- started to work on Solder Paste Dispensing Tool
+- fixed a bug in rotate from shortcut function
+- finished generating the solder paste dispense geometry
+
+18.02.2019
+
+- added protections again wrong values for the Buffer and Paint Tool in Geometry Editor
+- the Paint Tool in Geometry Editor will load the default values from Tool Paint in Preferences
+- when the Tools in Geometry Editor are activated, the notebook with the Tool Tab will be unhidden. After execution the notebook will hide again for the Buffer Tool.
+- changed the font in Tool names
+- added in Geometry Editor a new Tool: Transformation Tool.
+- in Geometry Editor by selecting a shape with a selection shape, that object was added multiple times (one per each selection) to the selected list, which is not intended. Bug fixed.
+- finished adding Transform Tool in Geometry Editor - everything is working as intended
+- fixed a bug in Tool Transform that made the user to not be able to capture the click coordinates with SHIFT + LMB click combo
+- added the ability to choose an App QStyle out of the offered choices (different for each OS) to be applied at the next app start (Preferences -> General -> Gui Pref -> Style Combobox)
+- added support for FlatCAM usage with High DPI monitors (4k). It is applied on the next app startup after change in Preferences -> General -> Gui Settings -> HDPI Support Checkbox
+- made the app not remember the window size if the app is maximized and remember in QSettings if it was maximized. This way we can restore the maximized state but restore the windows size unmaximized
+- added a button to clear the GUI preferences in Preferences -> General -> Gui Settings -> Clear GUI Settings
+- added key shortcuts for the shape transformations within Geometry Editor: X, Y keys for Flip(mirror), Shift+X, Shift+Y combo keys for Skew and Alt+X, Alt+Y combo keys for Offset
+- adjusted the plotcanvas.zomm_fit() function so the objects are better fit into view (with a border around) 
+- modified the GUI in Objects Selected Tab to accommodate 2 different modes: basic and Advanced. In Basic mode, some of the functionality's are hidden from the user.
+- added Tool Transform preferences in Edit -> Preferences and used them through out the app
+- made the output of Panelization Tool a choice out of Gerber and Geometry type of objects. Useful for those who want to engrave multiple copies of the same design.
+
+17.02.2019
+
+- changed some status bar messages
+- New feature: added the capability to view the source code of the Gerber/Excellon file that was loaded into the app. The file is also stored as an object attribute for later use. The view option is in the project context menu and in Menu -> Options -> View Source
+- Serialized the source_file of the Objects so it is saved in the FlatCAM project and restored.
+- if there is a single tool in the tool list (Geometry , Excellon) and the user click the Generate GCode, use that tool even if it is not selected
+- fixed issue where after loading a project, if the default kind of CNCjob view is only 'cuts' the plot will revert to the 'all' type
+- in Editors, if the modifier key set in Preferences (CTRL or SHIFT key) is pressed at the end of one tool operation it will automatically continue to that action until the modifier is no longer pressed when Select tool will be automatically selected.
+- in Geometry Editor, on entry the notebook is automatically hidden and restored on Geometry Editor exit.
+- when pressing Escape in Geometry Editor it will automatically deselect any shape not only the currently selected tool.
+- when deselecting an object in Project menu the status bar selection message is deleted
+- added ability to save the Gerber file content that is stored in FlatCAM on Gerber file loading. It's useful to recover from saved FlatCAM projects when the source files are no longer available.
+- fixed an issue where the function handler that changed the layout had a parameter changed accidentally by an index value passed by the 'activate' signal to which was connected
+- fixed bug in paint function in Geometry Editor that didn't allow painting due of overlap value
+
+16.02.2019
+
+- added the 'Save' menu entry to the Project context menu, for CNCJob: it will export the GCode.
+- added messages in info bar when selecting objects in the Project View list
+- fixed DblSided Tool so it correctly creates the Alignment Drills Excellon file using the new structure
+- fixed DblSided Tool so it will not crash the app if the user tries to make a mirror using no coordinates
+- added some relevant status bar messages in DblSided Tool
+- fixed DblSided Tool to correctly use the Box object (until now it used as reference only Gerber object in spite of Excellon or Geometry objects being available)
+- fixed DblSided Tool crash when trying to create Alignment Drills object without a Tool diameter specified
+- fixed DblSided Tool issue when entering Tool diameter values with comma decimal separator instead of decimal dot separator
+- fixed Cutout Tool Freeform to generate cutouts with options: LR, TB. 2LR, 2TB which didn't worked previously
+- fixed Excellon parser to detect correctly the units and zeros for Excellon's generated by Eagle 9.3.0
+- modified the initial size of the canvas on startup
+- modified the build file (make_win.py) to solve the issue with suddenly not accepting the version as Beta
+- changed the initial layout to 'compact'
+- updated the install scripts to uninstall a previously installed FlatCAM Beta (that has the same GUID)
+
+15.02.2019
+
+- rearranged the File and Edit menu's and added some explanatory tooltips on certain menu items that could be seen as cryptic
+- added Excellon Export Options in Edit -> Preferences
+- started to work in using the Excellon Export parameters
+- remade the Excellon export function to work with parameters entered in Edit -> Preferences -> Excellon Export
+- added a new entry in the Project Context Menu named 'Save'. It will actually work for Geometry and it will do Export DXF and for Excellon and it will do Export Excellon
+- reworked the offer to save a project so it is done only if there are objects in the project but those objects are new and/or are modified since last project load (if an old project was loaded.)
+- updated the Excellon plot function so it can plot the Excellon's from old projects
+- removed the message boxes that popup on Excellon Export errors and replaced them with status bar messages
+- small change in tab width so the tabs looks good in Linux, too.
+
+14.02.2019
+
+- 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()
+- 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
+- the notebook is automatically collapsed when there are no objects in the collection and it is showed when adding an object
+- added new options in Edit -> Preferences -> General -> App Preferences to control if the Notebook is showed at startup and if the notebook is closed when there are no objects in the collection and showed when the collection has objects.
+
+13.02.2019
+
+- added new parameter for Excellon Object in Preferences: Fast Retract. If the checkbox is checked then after reaching the drill depth, the drill bit will be raised out of the hole asap.
+- started to work on GUI forms simplification
+- changed the Preferences GUI for Geometry and Excellon Objects to make a difference between parameters that are changed often and those that are not.
+- changed the layout in the Selected Tab UI
+- started to add apertures table support
+- 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()
+
+12.02.2019
+
+- whenever a FlatCAM tool is activated, if the notebook side is hidden it will be unhidden
+- reactivated the Voronoi classes
+- added a new parameter named Offset in the Excellon tool table - work in progress
+- finished work on Offset parameter in Excellon Object (Excellon Editor, camlib, FlatCAMObj updated to take this param in consideration)
+- fixed a bug where in Excellon editor when editing a file, a tool was automatically added. That is supposed to happen only for empty newly created Excellon Objects.
+- starting to work on storing the solid_geometry for each tool in part in Excellon Object
+- stored solid_geometry of Excellon object in the self.tools dictionary
+- finished the solid_geometry restore after edit in Excellon Editor
+- finished plotting selection for each tool in the Excellon Tool Table
+- fixed the camlib.Excellon.bounds() function for the new type of Excellon geometry therefore fixed the canvas selection, too
+
+
+10.02.2019
+
+- the SELECTED type of messages are no longer printed to shell from 2 reasons: first, too much spam and second, issue with displaying html
+- on set_zero function and creation of new geometry or new excellon there is no longer a zoom fit 
+- repurposed shortcut key 'Delete' to delete tools in tooltable when the mouse is over the Seleted tab (with Geometry inside) or in Tools tab (when NCC Tool or Paint Tool is inside). Or in Excellon Editor when mouse is hovering the Selected tab selecting a tool, 'Delete' key will delete that tool, if on canvas 'Delete' key will delete a selected shape (drill). In rest, will delete selected objects.
+- adjusted the preprocessor files so the Spindle Off command (M5) is done before the move to Toolchange Z
+- adjusted the Toolchange Manual preprocessor file to have more descriptive messages on the toolchange event
+- added a strong focus to the object_name entry in the Selected tab
+- the keypad keyPressed are now detected correctly
+- added a pause and message/warning to do a rough zero for the Z axis, in case of Toolchange_Probe_MACH3 preprocessor file
+- changes in Toolchange_Probe_MACH3 preprocessor file
+
+9.02.2019
+
+- added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory
+- added into Preferences the Calculator Tools
+- made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before)
+- fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor
+- changed the messages from status bar on new object creation/selection
+- in Geometry Editor fixed the handler for the Rotate shortcut key ('R')
+
+8.02.2019
+
+- when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be made visible
+- changed the menu entry Toggle Grid name to Toggle Grid Snap
+- fixed errors in Toggle Axis
+- fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View
+- moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI
+- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999
+- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter
+- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected
+- changed the shortcut key for Transform Tool from 'T' to 'Alt+T'
+- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message
+- added Double Validator for the Offset value so only float numbers can be entered.
+- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab
+- if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value.
+
+7.02.2019
+
+- in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object
+- commented some debug messages
+- imported speedups for shapely
+- added a disable menu entry in the canvas contextual menu
+- small changes in Tools layout
+- added some new icons in the help menu and reorganized this menu
+- added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section
+- changed the Shortcut list shortcut key to F3
+- moved some graphical classes out of Tool Shell to GUIElements.py where they belong
+- when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed
+- in Move Tool I've added the type of object that was moved in the status bar message
+- color coded the status bar bullet to blue for selection
+- the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs.
+
+6.02.2019
+
+- fixed the units calculators crash FlatCAM when using comma as decimal separator
+- done a regression on Tool Tab default text. It somehow delete Tools in certain scenarios so I got rid of it
+- fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation
+- fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. 
+- fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it.
+- fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None
+- fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots.
+- changed the messages for Units Conversion
+- all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent()
+- renamed the theme to layout because it is really a layout change
+- added plot kind for CNC Job in the App Preferences
+- combined the geocutout and cutout_any TCL commands - work in progress
+- added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es) 
+- fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences
+- fixed the Tcl Command Geocutout to work with Gerber objects too (besides Geometry objects)
+
+5.02.3019
+
+- added a text in the Selected Tab which is showed whenever the Selected Tab is selected but without having an object selected to display it's properties
+- added an initial text in the Tools tab
+- added possibility to use the shortcut key for shortcut list in the Notebook tabs
+- added a way to set the Probe depth if Toolchange_Probe preprocessors are selected
+- finished the preprocessor file for MACH3 tool probing on toolchange event
+- added a new parameter to set the feedrate of the probing in case the used preprocessor does probing (has toolchange_probe in it's name)
+- fixed bug in Marlin preprocessor for the Excellon files; the header and toolchange event always used the parenthesis witch is not compatible with GCode for Marlin
+- fixed a issue with a move to Z_move before any toolchange
+
+4.02.2019
+
+- modified the Toolchange_Probe_general preprocessor file to remove any Z moves before the actual toolchange event
+- created a prototype preprocessor file for usage with tool probing in MACH3
+- added the default values for Tool Film and Tool Panelize to the Edit -> Preferences
+- added a new parameter in the Tool Film which control the thickness of the stroke width in the resulting SVG. It's a scale parameter.
+- whatever was the visibility of the corresponding toolbar when we enter in the Editor, it will be set after exit from the Editor (either Geometry Editor or Excellon Editor).
+- added ability to be detached for the tabs in the Notebook section (Project, Selected and Tool)
+- added ability for all detachable tabs to be restored to the same position from where they were detached.
+- changed the shortcut keys for Zoom In, Zoom Out and Zoom Fit from 1, 2, 3 to '-', '=' respectively 'V'. Added new shortcut keys '1', '2', '3' for Select Project Tab, Select Selected Tab and Select Tool Tab.
+- formatted the Shortcut List Tab into a HTML table
+
+3.3.2019
+
+- updated the new shortcut list with the shortcuts added lately
+- now the special messages in the Shell are color coded according to the level. Before they all were RED. Now the WARNINGS are yellow, ERRORS are red and SUCCESS is a dark green. Also the level is in CAPS LOCK to make them more obvious
+- some more changes to GUI interface (solved issues)
+- added some status bar messages in the Geometry Editor to guide the user when using the Geometry Tools
+- now the '`' shortcut key that shows the 'shortcut key list' in Editors points to the same window which is created in a tab no longer as a pop-up window. This tab can be detached if needed.
+- added a remove_tools() function before install_tools() in the init_tools() that is called when creating a new project. Should solve the issue with having double menu entry's in the TOOLS menu
+- fixed remove_tools() so the Tcl Shell action is readded to the Tools menu and reconnected to it's slot function
+- added an automatic name on each save operation based on the object name and/or the current date
+- added more information's for the statistics
+
+2.2.2019
+
+- code cleanup in Tools
+- some GUI structure optimization's
+- added protection against entering float numbers with comma separator instead of decimal dot separator in key points of FlatCAM (not everywhere)
+- added a choice of plotting the kind of geometry for the CNC plot (all, travel and cut kind of geometries) in CNCJob Selected Tab
+- added a new preprocessor file named: 'probe_from_zmove' which allow probing to be done from z_move position on toolchange event 
+- fixed the snap magnet button in Geometry Editor, restored the checkable property to True
+- some more changes in the Editors GUI in deactivate() function
+- a fix for saving as empty an edited new and empty Excellon Object
+
+1.02.2019
+
+- fixed preprocessor files so now the bounds values are right aligned (assuming max string length of 9 chars which means 4 digits and 4 decimals)
+- corrected small type in list_sys Tcl command; added a protection of the Plot Area Tab after a successful edit.
+- remade the way FlatCAM saves the GUI position data from a file (previously) to use PyQt QSettings
+- added a 'theme' combo selection in Edit -> Preferences. Two themes are available: standard and compact.
+- some code cleanup
+- fixed a source of possible errors in DetachableTab Widget.
+- fixed gcode conversion/scale (on units change) when multiple values are found on each line
+- replaced the pop-up window for the shortcut list with a new detachable tab
+- removed the pop-up messages from the rotate, skew, flip commands
+
+31.01.2019
+
+- added a parameter ('Fast plunge' in Edit -> Preferences -> Geometry Options and Excellon Options) to control if the fast move to Z_move is done or not
+- added new function to toggle fullscreen status in Menu -> View -> Toggle Full Screen. Shortcut key: Alt+F10
+- added key shortcuts for Enable Plots, Disable Plots and Disable other plots functions (Alt+1, Alt+2, Alt+3)
+- hidden the snap magnet entry and snap magnet toggle from the main view; they are now active only in Editor Mode
+- updated the camlib.CNCJob.scale() function so now the GCode is scaled also (quite a HACK :( it will need to be replaced at some point)). Units change work now on the GCODE also.
+- added the bounds coordinates to the GCODE header
+- FlatCAM saves now to a file in self.data_path the toolbar positions and the position of TCL Shell
+- Plot Area Tab view can now be toggled, added entry in View Menu and shortcut key Ctrl+F10
+- All the tabs in the GUI right side are (Plot Are, Preferences etc) are now detachable to a separate windows which when closed it returns in the previous location in the toolbar. Those detached tabs can be also reattached by drag and drop.
+
+30.01.2019
+
+- added a space before Y coordinate in end_code() function in some of the preprocessor files
+- added in Calculators Tool an Electroplating Calculator.
+- remade the App Menu for Editors: now they will be showed only when the respective Editor is active and hidden when the Editor is closed.
+- added a traceback report in the TCL Shell for the errors that don't allow creation of an object; useful to trace exceptions/errors
+- in case that the Toolchange X,Y parameter in Selected (or in Preferences) are deleted then the app will still do the job using the current coordinates for toolchange
+- fixed an issue in camlib.CNCJob where tha variable self.toolchange_xy was used for 2 different purposes which created loss of information.
+- fixed unit conversion functions in case the toolchange_xy parameter is None
+- more fixes in camlib.CNCJob regarding usage of toolchange (in case it is None)
+- fixed preprocessor files to work with toolchange_xy parameter value = None (no values in Edit - Preferences fields)
+- fixed Tcl commands CncJob and DrillCncJob to work with toolchange
+- added to the preprocessor files the command after toolchange to go with G00 (fastest) to "Z Move" value of Z pozition.
+
+29.01.2019
+
+- fixed issue in Tool Calculators when a float value was entered starting only with the dot.
+- added protection for entering incorrect values in Offset and Scale fields for Gerber and Geometry objects (in Selected Tab)
+- added more shortcut keys in the Geometry Editor and in Excellon Editor; activated also the zoom (fit, in, out) shortcut keys ('1' , '2', '3') for the editors
+- disabled the context menu in tools table on Paint Tool in case that the painting method is single.
+- added protection when trying to do Intersection in Geometry Editor without having selected Geometry items.
+- fixed the scale, mirror, rotate, skew functions to work with Geometry Objects of multi-geometry type.
+- added a GUI for Excellon Search time for OR-TOOLS path optimization in Edit -> Preferences -> Excellon General -> Optimization Time
+- more changes in Edit -> Preferences -> Geometry, Gerber and in CNCJob
+- added new option for Cutout Tool Freeform Gaps in Edit -> Preferences -> Tools
+- fixed Freeform Cutout gaps issue (it was double than the value set)
+- added protection so the Cutout (either Freeform or Rectangular) cannot be done on a multigeo Geometry
+- added 2Sided Tool default values in Edit -> Preferences -> Tools
+- optimized the FlatCAMCNCJob.on_plot_cb_click_table() plot function and solved a bug regarding having tools numbers not in sync with the cnc tool table
+
+28.01.2018
+
+- fixed the FlatCAMGerber.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
+
+27.01.2018
+
+- added more key shortcuts into the application; they are now displayed in the GUI menu's
+- reorganized the Edit -> Preferences -> Global
+- redesigned the messagebox that is showed when quiting ot creating a New Project: now it has an option ('Cancel') to abort the process returning to the app
+- added options for trace segmentation that can be useful for auto-levelling (code snippet from Lei Zheng from a rejected pull request on FlatCAM https://bitbucket.org/realthunder/ )
+- added shortcut key 'L' for creating 'New Excellon' 
+- added shortcut key combo 'Shift+S' for Running a Script.
+- modified GRBL_laser preprocessor file so it includes a Sxxxx command on the line with M03 (laser active) whenever a value is enter in the Spindlespeed entry field
+- remade the EDIT -> PREFERENCES window, the Excellon and Gerber sections. Created a new section named TOOLS
+
+26.01.2019
+
+- fixed grbl_11 preprocessor in linear_code() function
+- added icons to the Project Tab context menu
+- 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  
+
+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
+- 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
+
+24.01.2019
+
+- trying to fix painting single when the actual painted object it's a MultiPolygon
+- fixed the Copy Object function when the object is Gerber
+- added the Copy entry to the Project context menu
+- made the functions behind Disable and Enable project context menu entries, non-threaded to fix a possible issue
+- added multiple object selection on Open ... and Import ... (idea and code snippet came from Travers Carter, BitBucket user https://bitbucket.org/travc/)
+- fixed 'GRBL_laser' preprocessor bugs (missing functions)
+- fixed display geometry for 'GRBL_laser' preprocessor
+- Excellon Editor - added possibility to create an linear drill array rotated at an custom angle
+- added the Edit and Properties entries to the Project context menu
+
+23.01.2019
+
+- added a new preprocessor file named 'line_xyz' which have x, y, z values on the same GCode line
+- fixed calculation of total path for Excellon Gcode file
+- modified the way FlatCAM preferences are saved. Now they can be saved as new files with .FlatConfig extension by the user and shared.
+- added possibility to open the folder where FlatCAM is saving the preferences files
+
+21.01.2019
+
+- changed some tooltips
+- added tooltips in Excellon tool table headers
+- in Excellon Tool Table the columns are now only selectable by clicking on the header (sorting is done automatically)
+- if CNCJob from Excellon then hide the CNC tools table in CNCJob Object
+
+ 
+20.01.2019
+
+- fixed the HPGL code geometry rendering when travel
+- fixed the message box layout when asking to save the current work
+- made sure that whenever the HPGL preprocessor is selected the Toolchange is always ON and the MultiDepth is OFF
+- the HPGL preprocessor entry is not allowed in Excellon Object preprocessor selection combobox as it is only applicable for Geometry
+- when saving HPGL code it will be saved as a file with extension .plt
+- the units mentioned in HPGL format are only METRIC therefore if FlatCAM units are in INCH they will be transform to METRIC
+- the minimum unit in HPGL is 0.025mm therefore the coordinates are rounded to a multiple of 0.025mm
+- removed the raise statement in do_worker_task() function as this is fatal in conjunction with PyQt5
+- added a try - except clause for the situations when for a font can't be determined the family and name
+- moved font parsing to the Geometry Editor: it is done everytime the Text tool is invoked
+- made sure that the HPGL preprocessor is not populated in the Excellon preprocessors in Preferences as it make no sense (HPGL is useful only for Geometries)
+
+19.01.2019
+
+- added initial implementation of HPGL preprocessor
+- fixed display HPGL code geometry on canvas
+
+11.01.2019
+
+- added a status message for font parsing
+
+9.01.2019
+
+- added a fix to allow creating of Excellon geometry even when there are points with no tools by skipping those points and warning the user about this in a Tcl message
+- added a message box asking users if they want to save the project in case that either New Project menu entry is clicked or if Exit menu entry is clicked or if the app is closed from the close button. The message box will be showed only if there are objects in the collection.
+- modified the first line in the Gcode header to show the FlatCAM version and version_date
+
+8.01.2019
+
+- added checkboxes in Preferences -> General -> Global Preferences to switch on/off version check at application startup and also to control if the app will send anonymous statistics about FlatCAM usage to help improve FlatCAM
+
+7.01.2019
+
+- added tooltips in Edit->Convert menu
+- fixed cutting from copper features when doing Gerber isolation with multiple passes
+
+6.01.2019
+
+- fixed the Marlin preprocessor detection in GCode header
+- the version date in GCode header is now the one set in FlatCAMApp.App.version_date
+- fixed bug in preprocessor files: number of drills is now calculated only for the Excellon objects in toolchange function (only Excellon objects have drills) 
+
+5.01.2019
+
+- fixed cncjob TclCommand - it used the default values for parameters
+- fixed the layout in ToolTransform
+- 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.
+
+3.01.2019
+
+- initial merge into FlatCAM regular
+
+28.12.2018
+
+- changed the workspace drawing from 'gl' to 'agg'. 'gl' has better performance but it messes with the overlapping graphics
+- removed the initial obj.build_ui() in App.editor2object()
+
+25.12.2018
+
+- fixed bugs in Excellon Editor due of PyQt5 port
+- fixed bug when loading Gerber with follow
+- fixed bug that when a Gerber was loaded with -follow parameter it could not be isolated external and full
+- changed multiple status bar messages
+- changed some assertions to (status error message + return) combo
+- fixed issues in 32bit installers
+- added protection against using Excellon joining on different kind of objects
+- fixed bug in ToolCutout where the Rectangular Cutout used the Type of Gaps from Freeform Cutout
+- fixed bug that didn't allowed saving SVG file from a Gerber file
+- modified setup_ubuntu.sh file for PyQt5 packages
+
+23.12.2018
+
+- added move (as in Tool Move) capability for CNCJob object and the GCode is updated on each move --> finished both for Gcode loaded and for CNCJob generated in the app
+- fixed some errors related to DialogOpen widget that I've missed in PyQt5 porting
+- added a bounds() method for CNCJob class in camlib (perhaps overdone as it worked well with the one inherited)
+- small changes in Paint Tool - the rest machining is working only partially
+- added more columns in CNCjob Tool Table showing more info about the present tools
+- make the columns in CNCJob Tool Table not editable as it has no sense
+
+22.12.2018
+
+- fixed issues in Transform Tool regarding the message boxes
+- fixed more error in Double Sided Tool and added some more information's in ToolTips
+- added more information's in CutOut Tool ToolTips
+- updated the tooltips in amost all FlatCAM tools; in Tool Tables added column header ToolTips
+- fixed NCC rest machining in NCC Tool; added status message and stop object creation if there is no geometry on any tool
+- fixed version number: now it will made of a number in format main_version.secondary_version/working_version
+- modified the makefile for windows builds to accommodate both 32bit and 64bit executable generation
+
+21.12.2018
+
+- added shortcut "SHIFT + W" for workspace toggle
+- updated the list of shortcuts
+- forbid editing for the MultiGeo type of Geometry because the Geometry Editor is not prepared for this
+- finished a "sort" of rest-machining for Non Copper Clearing Tool but it's time consuming operation
+- reworked the NCC Tool as it was fundamental wrong - still has issues on the rest machining
+- added a parameter reset for each run of Paint Tool and NCC Tool
+
+20.12.2018
+
+- porting application to PyQt5
+- adjusted the level of many status bar messages
+- created new bounds() methods for Excellon and Gerber objects as the one inherited from Geometry failed in conjunction with PyQt5
+- fixed some small bugs where a string was divided by a float finally casting the result to an integer
+- removed the 'raise' conditions everywhere I could and make protections against loading files in the wrong place
+- fixed a "PyCharm stupid paste on the previous tab level even after one free line " in Excellon.bounds()
+- in Geometry object fixed error in tool_delete regarding deletion while iterating a dict
+- started to rework the NCC Tool to generate one file only
+- in Geometry Tool Table added checkboxes for individual plot of tools in case of MultiGeo Geometry
+- rework of NCC Tool UI
+- added a automatic selector: if the system is 32bit the OR-tools imports are not done and the OR-tools drill path optimizations are replaced by a default Travelling Salesman drill path optimization
+- created a Win32 make file to generate a Win32 executable
+- disabled the Plot column in Geometry Tool Table when the geometry is SingleGeo as it is not needed
+- solved a issue when doing isolation, if the solid_geometry is not a list will make it a list
+- added tooltips in the Geometry Tool Table headers explaining each column
+- added a new Tcl Command: clear. It clears the Tcl Shell of all text and restore it to the original state
+- fixed Properties Tool area calculation; added status bar messages if there is no object selected show an error and successful showing properties is confirmed in status bar
+- when Preferences are saved, now the default values are instantly propagated within the application
+- when a geometry is MultiGeo and all the tools are deleted, it will have no geometry at all therefore all that it's plotted on canvas that used to belong to it has to be deleted and because now it is an empty object we demote it to SingleGeo so it can be edited
+
+19.12.2018
+
+- fixed SVG_export for MultiGeo Geometries
+- fixed DXF_export for MultiGeo Geometries
+- fixed SingleGeo to MultiGeo conversion plotting bug
+
+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
+- 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
+- fixed Move Tool to support MultiGeo geometry objects moving
+- added tool edit in Geometry Object Tool Table
+- added Tool Table context menu in Geometry Object and in Paint Tool
+- modified some Status Bar messages in Geometry Object
+
+17.12.2018
+
+- added support for multiple solid_geometry in a geometry object; each tool can now have it's own geometry. Plot, project save/load are OK.
+- added support for single GCode file generation from multi-tool PaintTool job
+- added protection for results of Paint Tool job that do not have geometry at all. An Error will be issued. It can happen if the combination of Paint parameters is not good enough
+- solved a small bug that didn't allow the Paint Job to be done with lines when the results were geometries not iterable 
+- added protection for the case when trying to run the cncjob Tcl Command on a Geometry object that do not have solid geometry or one that is multi-tool
+- Paint Tool Table: now it is possible to edit a tool to a new diameter and then edit another tool to the former diameter of the first edited tool
+- added a new type of warning, [WARNING_NOTCL]
+- fixed conflict with "space" keyboard shortcut for CNC job
+
+16.12.2018
+
+- redone the Options menu; removed the Transfer Options as they were not used
+- deleted some folders in the project structure that were never used
+- Paint polygon Single works only for left mouse click allowing mouse panning
+- added ability to print errors in status bar without raising Tcl Shell
+- fixed small bug: when doing interiors isolation on a Gerber that don't allow it, no object is created now and an error in the status bar is issued
+- fixed bug in Paint All for Geometry made from exteriors Gerber isolation
+- fixed the join geometry: when the geometries has different tools the join will fail with a status bar message (as it should). Allow joining of geometries that have no tool. // Reverted on 18.12.2018
+- changed the error messages that are simple to the kind that do not open the TCl shell
+- fixed some issues in Geometry Object
+- Paint Tool - reworked the UI and made it compatible with the Geometry Object UI
+- Paint Tool - tool edit functional
+- added Clear action in the Context menu of the TCl Shell
+
+14.12.2018
+
+- fixed typo in setup_ubuntu.sh
+- minor changes in Excellon Object UI
+- added Tool Table in Paint Tool
+- now in Paint Tool and Non Copper Clearing Tool a selection of tools can be deleted (not only one by one)
+- minor GUI changes (added/corrected tooltips)
+- optimized vispy startup time from about >6 sec to ~3 seconds
+- removed vispy text collection starting in plotcanvas as it did nothing // RESTORED 18.12.2018 as it messed the graphical presentation
+- fixed cncjob TclCommand for the new type of Geometry
+- make sure that when using the TclCommands, the object names are case insensitive
+- updated the TCL Shell auto-complete function; now it will index also the names of objects created or loaded in the application
+- on object removal the name is removed from the Shell auto-complete model
+
+13.12.2018
+
+NEW Geometry Object and CNC Object architecture (3rd attempt) which allow multiple tools for one geometry
+
+- fixed issue with cumulative G-code after successive delete/create of a CNCJob on the same geometry (some references were kept after deletion of CNCJob object which kept the deleted tools data and added it to a new one)
+- fixed plot and export G-code in new format
+- fixed project save/load in the new format for geometry
+- added new feature in CNCJob Object UI: since we may have multiple tools per CNCJob object due of having multiple tool in Geometry Object,
+now there is a Tool Table in CNC Object UI and each tool GCode can be enabled or disabled
+
+12.12.2018
+
+- Geometry Tool Table: when the Offset type is 'custom' each tool it's storing the value and it is updated on UI when that tool is selected in UI table
+- Geometry Tool Table: fixed tool offset conversion when the Offset in Tool Table UI is set to Custom
+
+11.12.2018
+
+- cleaned up the generatecncjob() function in FlatCAMObj
+- created a new function for generating cncjob out of multitool geometry, mtool_generate_cncjob()
+- cleaned up the generate_from_geometry_2() method in camlib
+- Geometry Tool Table: new tool added copy all the form fields (data) from the last tool
+- finished work on generation of a single CNC Job file (therefore a single GCODE file) even for multiple tools in Geo Tool Table
+- GCode header is added only on saving the file therefore the time generation will be reflected in the file
+- modified preprocessors to accommodate the new CNC Job file with multiple tools
+- modified preprocessors so the last X,Y move will be to the toolchange X,Y pos (set in Preferences)
+- save_project and load_project now work with the new type of multitool geometry and cncjob objects
+
+10.12.2018
+
+- added new feature in Geometry Tool Table: if the Offset type in tool table is 'Offset' then a new entry is unhidden and the user can use custom offset
+- Geometry Tool Table: fixed add new tool with diameter with many decimals
+- Geometry Tool Table: when editing the tip dia or tip angle for the V Shape tool, the CutZ is automatically calculated
+
+9.12.2018
+
+- new Geometry Tool Table has functional unit conversion
+- when entering a float number in Spindle Speed now there is no error and only the integer part is used, the decimals are discarded
+- finished the Geometry Tool Table in the form that generates only multiple files
+- if tool type is V-Shape ('V') then the Cut Z entry is disabled and new 'Tip Dia' and 'Tip Angle' fields are showed. The values entered will calculate the Cut Z parameter
+
+5.12.2018
+
+- remade the Geometry Tool Table, before this change each tool could not store it's own set of data in case of multiple tools with same diameter
+- added a new column in Geo Tool Table where to specify which type of tool to use: C for circular, B for Ball and V for V-shape
+
+4.12.2018
+
+- new geometry/excellon object name is now only "new_g"/"new_e" as the type is clear from the category is into (and the associated icon)
+- always autoselect the first tool in the Geometry Tool table
+- issue error message if the user is trying to generate CNCJob without a tool selected in Geometry Tool Table
+- add the whole data from Geometry Object GUI as dict in the geometry tool dict so each tool (file) will have it's own set of data
+
+3.12.2018
+
+- Geometry Tool table: delete multiple tools with same diameter = DONE
+- Geometry Tool table: possibility to cut a path inside or outside or on path = DONE
+- Geometry Tool table: fixed situation when user tries to add a tool but there is no tool diameter entered
+- if a geometry is a closed shape then create a Polygon out of it
+- some fixes in Non Copper Clearing Tool
+- Geometry Tool table: added option to delete_tool function for delete_all
+- Geometry Tool table: added ability to delete even the last tool in tool_table and added an warning if the user try to generate a CNC Job without a tool in tool table
+- if a geometry is painted inside the Geometry Editor then it will store the tool diameter used for this painting. Only one tool cn be stored (the last one) so if multiple paintings are done with different tools in the same geometry it will store only the last used tool.
+- if multiple geometries have different tool diameters associated (contain a paint geometry) they aren't allowed to be joined and a message is displayed letting the user know
+
+2.12.2018
+
+- started to work on a geometry Tool Table
+- renamed FlatCAMShell as ToolShell and moved it (and termwidget) to flatcamTools folder
+- cleaned up the ToolShell by removing the termwidget separate file and added those classes to ToolShell
+- added autocomplete for TCL Shell - the autocomplete key is 'TAB'
+- covered some possible exceptions in rotate/skew/mirror functions
+- Geometry Tool table: add/delete tools = DONE
+- Geometry Tool table: add multiple tools with same diameter = DONE
+
+1.12.2018
+
+- fixed Gerber parser so now the Gerber regions that have D02 operation code just before the end of the region will be processed correctly. Autotrax Dex Gerbers are now loaded
+- fixed an issue with temporary geo storage "geo" being referenced before assignment
+- moved all FlatCAM Tools into a single directory
+
+30.11.2018
+
+- remade the CutOut Tool. I've put together the former Freeform Cutout tool and the Cutout Object fount in Gerber Object GUI and left only a link in the Gerber Object GUI. This tidy the GUI a bit.
+- created a Paint Tool and replaced the Paint Area section in Geometry Object GUI with a link to this tool.
+- fixed bug in former Paint Area and in the new Paint Tool that made the paint method not to be saved in App preferences
+- solved a bug in Gerber parser: in case that an operation code D? was encountered alone it was not remembered - fixed
+- fixed bug related to the newly entered toolchange feature for Geometry: it was trying to evaluate toolchange_z as a comma separated value like for toolchange x,y
+- fixed bug in scaling units in CNC Job which made the unit change between INCH and MM not possible if a CNC Job was present in the project objects
+
+29.11.2018
+
+- added checks for using a Z Cut with positive value. The Z Cut parameter has to be negative so if the app will detect a positive value it will automatically convert it to negative
+- started to implement rest-machining for Non Copper clearing Tool - for now the results are not great
+- added Toolchange X,Y position parameters and modified the default and manual_toolchange preprocessor file to use them
+For now they are used only for Excellon objects who do have toolchange events
+- added Toolchange event selection for Geometry objects; for now it is as before, single tool on each file
+- remade the GUI for objects and in Preferences to have uniformity
+- fixed bug: after editing a newly created excellon/geometry object the object UI used to not keep the original settings
+- fixed some bugs in Tool Add feature of the new Non Copper Clear Tool
+- added some messages in the Non Copper Clear Tool
+- added parameters for coordinates no of decimals and for feedrate no of decimals used in the resulting GCODE. They are in EDIT -> Preferences -> CNC Job Options
+- modified the preprocessors to use the "decimals" parameters
+
+28.11.2018
+
+- added different methods of copper clearing (standard, seed, line_based) and "connect", "contour" options found in Paint function
+- remake of the non-copper clearing tool as a separate tool
+- modified the "About" menu entry to mention the main contributors to FlatCAM 3000 
+- modified Marlin preprocessor according to modifications made by @redbull0174 user from FlatCAM.org forum
+- modified Move Tool so it will detect if there is no object to move and issue a message
+
+27.11.2018
+
+- fixed bug in isolation with multiple passes
+- cosmetic changes in Buffer and Paint tool from Geometry Editor
+- changed the way selection box is working in Geometry Editor; now cumulative selection is done with modifier key (SHIFT or CONTROL) - before it was done by default
+- changed the default value for CNCJob tooldia to 1mm
+
+25.11.2018
+
+- each Tool change the name of the Tools tab to it's name
+- all open objects are no longer autoselected upon creation. Only on new Geometry/Excellon object creation it will be autoselected
+
+24.11.2018
+
+- restored the selection method in Geometry Editor to the original one found in FlatCAM 8.5
+- minor changes in Clear Copper function
+- minor changes in some preprocessors
+- change Join Geometry menu entry to Join Geo/Gerber
+- added menu entry for Toggle Axis in Menu -> View
+- added menu entry for Toggle Workspace in Menu -> View
+- added Bounding box area to the Properties (when metric units, in cm2)
+- non-copper clearing function optimization
+- fixed Z_toolchange value in the GCODE header
+
+21.11.2018
+
+- not very precise jump to location function
+- added shortcut key for jump to coordinates (J) and for Tool Transform (T)
+- some work in shortcut key
+
+19.11.2018
+
+- fixed issue with nested comment in preprocessors
+- fixed issue in Paint All; reverted changes
+
+18.11.2018
+
+- renamed FlatCAM 2018 to FlatCAM 3000
+- added new entries in the Help menu; one will show shortcut list and the other will start a YouTube webpage with a playlist where I will publish future training videos for this version of FlatCAM
+- if a Gerber region has issues the file will be loaded bypassing the error but there will be a TCL message letting the user know that there are parser errors. 
+
+17.11.2018
+
+- added Excellon parser support for units defined outside header
+
+
+12.11.2018
+
+- fixed bug in Paint Single Polygon
+- added spindle speed in laser preprocessor
+- added Z start move parameter. It controls the height at which the tool travel on the fist move in the job. Leave it blank if you don't need it.
+
+9.11.2018
+
+- fixed a reported bug generated by a typo for feedrate_z object in camlib.py. Because of that, the project could not be saved.
+- fixed a G01 usage (should be G1) in Marlin preprocessor.
+- changed the position of the Tool Dia entry in the Object UI and in FlatCAMGUI
+- fixed issues in the installer
+
+30.10.2018
+
+- fixed a bug in Freeform Cutout Tool - it was missing a change in the name of an object
+
+29.10.2018
+
+- added Excellon export menu entry and functionality that can export in fixed format 2:4 LZ INCH (format that Altium can load and it is a more generic format).
+It will be usefull for those who need FlatCAM to only convert the Excellon to a more useful format and visualize Gerbers.
+The other Excellon Export menu entry is exporting in units either Metric or INCH depending on the current units in FlatCAM, but it will always use the decimal format which may not be loaded in all cases.
+- disabled the Selected Tab while in Geometry Editor; the user is not supposed to have access to those functions while in Geometry Editor
+- added an menu entry in Menu -> File -> Recent Files named Clear Recent files which does exactly that
+- fixed issue: when a New Project is created but there is a Geometry still in Geometry Editor (or Excellon Editor) not saved, now that geometry is deleted
+- fixed problem when doing Clear Copper with Cut over 1st point option active. When the shape is not closed then it may cut over copper features. Originally the feature was meant to be used only with isolation geometry which is closed. Fixed
+
+28.10.2018
+
+- fixed Excellon Editor shortcut messages; also fixed differences in messages between usage by shortcuts and usage by menu toolbar actions
+- fixed Excellon Editor bug: it was triggering exceptions when the user selected a tool in tooltable and then tried to add a drill (or array) by clicking on canvas
+Clicking on canvas by default clear all the used tools, therefore the action could not be done. Fixed.
+- fixed bug Excellon Editor: when all the drills from a tool are resized, after resize they can't be selected.
+- Excellon Editor: added ability to delete multiple tools at once by doing multiple selection on the tooltable
+- Excellon Editor: if there are no more drills to a tool after doing drills resize then delete that tool from the tooltable
+- Excellon Editor: always select the last tool added to the tooltable
+- Excellon Editor: added a small canvas context menu for Excellon Editor
+
+27.10.2018
+
+- added a Paint tool toolbar icon and added shortcut key 'I' for Paint Tool
+- fixed unreliable multiple selection in Geometry Editor; some clicks were not registered
+- added utility geometry for Add Drill Array in Excellon Editor
+- fixed bug Excellon Editor: drills in drill array start now from the array start point (x, y); previously array start point was used only for calculating the radius
+- fixed bug Excellon Editor: Measurement Tool was not acting correctly in Exc Editor regarding connect/disconnect of events
+- in Excellon Editor every time a tool is clicked (except Select which is the default) the focus will return to Selected tab
+- added protection in Excellon Editor: if there is no tool/drill selected no operation over drills can be performed and a status bar message will be displayed
+- Excellon Editor: added relevant messages for all actions
+- fixed bug Excellon Editor: multiple selection with key modifier pressed (CTRL/SHIFT) either by simple click or through selection box is now working
+- fixed dwell parameter for Excellon in Preferences to be default Off
+
+26.10.2018
+
+- when objects are disabled they can't be selected
+- added Feedrate_z (Plunge) parameter for Geometry Object
+- fixed bug in units convert for Geometry Tab; added some missing parameters to the conversion list
+- fixed bug in isolation Geometry when the isolated Gerber was a single Polygon
+- updated the Paint function in Geometry Editor
+
+25.10.2018
+
+- added a verification on project saving to make sure that the project was saved successfully. If not, a message will be displayed in the status bar saying so.
+
+20.10.2018
+
+- fixed the SVG import as Gerber. But unfortunately, when there is a ground pour in a imported PCB SVG, the ground pour will be isolated inside
+instead to be isolated outside like every other feature. That's no way around this. The end result will be thinner features
+for the ground pour and if one is relying on those thin connections as GND links then it will not work as intended ,they may be broken.
+Of course one can edit the isolation geometry and delete the isolation for the ground pour.
+- delete selection shapes on double clicking on object as we may not want to have selection shape while Selected tab is active
+
+19.10.2018
+
+- solved some value update bugs in tool_table in Excellon Editor when editing tools followed by deleting another tool,
+and then re-adding the just-deleted tool.
+- added support for chaining blocks in DXF Import
+- fixed the DXF arc import
+- added support for a type of Gerber files generated by OrCAD where the Number format is combined with G74 on the same line
+- in Geometry Editor added the possibility for buffer to use different kinds of corners
+- added protection against loading an GCODE file as Excellon through drag & drop on canvas or file open dialog
+- added shortcut key 'B' for buffer operation inside Geometry Editor
+- added shell message in case the Font used in Text Tool in Geometry editor is not supported. Only Regular, Bold, Italic adn BoldItalic are supported as of yet.
+- added shortcut key 'T' for Text Tool inside Geometry Editor
+- added possibility for Drag & Drop on FlatCAM GUI with multiple files at once 
+
+18.10.2018
+
+- fixed DXF arc import in case of extrusion enabled
+- added on Geo Editor Toolbar the button for Buffer Geometry; added the possibility to create exterior and interior buffer
+- fixed a numpy import error
+
+17.10.2018
+
+- added Spline support and Ellipse (chord) support in DXF Import: chord might have issues
+(borrowed from the work of Vasilis Vlachoudis, https://github.com/vlachoudis/bCNC)
+- added Block support in DXF Import - no support yet for chained blocks (INSERT in block)
+- support for repasted block insertions
+
+16.10.2018
+
+- added persistent toolbar view: the enabled toolbars will be active at the next app startup while those that are not enabled will not be
+enabled at the next app startup. To enable/disable toolbars right click on the toolbar.
+
+15.10.2018
+
+- DXF Export works now also for Exteriors only and Interiors only geometry generated from Gerber Object
+- when a Geometry is edited, now the interiors and exterior of a Polygon that is part of the Geometry can be selected individually. In practice, if
+doing full isolation geometry, now both external and internal trace can be selected individually.
+
+13.10.2018
+
+- solved issue in CNC Code Editor: it appended text to the previous one even if the CNC Code Editor was closed
+- added .GBD Gerber extension to the lists
+- added support for closed polylines/lwpolylines in Import DXF; now PCB patterns found in PDF format can be imported in INKSCAPE
+and saved as DXF. FlatCAM can import DXF as Gerber and the user now can do isolation on it.
+
+12.10.2018
+
+- added zoom in, zoom out and zoom fit buttons on the View toolbar
+- fixed bug that on Double Sided Tool when a Excellon Alignment is created does not reset the list of Alignment drills
+- added a message warning the user to add Point coordinates in case the reference used in Double Sided Tool is Point
+- added new feature: DXF Export for Geometry
+
+10.10.2018
+
+- fixed a small bug in Setup Recent Files
+- small fix in Freeform Cutout Tool regarding objects populating the combo boxes
+- Excellon object name will reflect the number of edits performed on it
+
+9.10.2018
+
+- In Geometry Editor, now Path and Polygon draw mode can be finished not only with shortcut key Enter but also with right click on canvas
+- fixes regarding of circle linear approximation - final touch
+- fix for interference between Geo Editor and Excellon Editor
+- fixed Cut action in Geometry Editor so it can now be done multiple times on the target geometry without need for saving in between.
+- initial work on DXF import; made the GUI interface and functional structure
+- added import functions for DXF import
+- finished DXF Import (no blocks support, no SPLINE support for now)
+
+8.10.2018
+
+- completed toggle canvas selection when there is only one object under click position for the case when clicking the object is done
+while other object is already selected.
+- added static utility geometry just upon activating an Editor function
+- changed the way the canvas is showed on FlatCAM startup
+
+7.10.2018
+
+- solved mouse click not setting relative measurement origin to zero
+- solved bug that always added one drill when copying a selection of drills in the EXCELLON EDITOR
+- solved bug that the number of copied drills in Excellon Editor was not updated in the tool table
+- work in the Excellon Editor: found useful to change the diameter of one tool to another already in the list;
+could help for all those tools that are a fraction difference that comes from imperial to mm (or reverse) conversion,
+to reduce the tool changes - Done
+- in Excellon Editor, always auto-select the last tool added
+- in Excellon Editor fixed shortcuts for drill add and drill_array add: they were reversed. Now key 'A' is for array add
+and key 'D' is for drill add
+- solved a small bug in Excellon export: even when there were no slots in the file, it always added the tools list that
+acted as unnecessary toolchanges
+- after Move action, all objects are deselected
+
+
+6.10.2018
+
+- Added basic support for SVG text in SVG import. Will not work if some letters in a word have different style (italic bold or both)
+- added toggle selection to the canvas selection if there is only one object under the click position
+- added support for "repeat" command in Excellon file
+- added support for Allegro Gerber and Excellon files
+- Python 3.7 is used again; solved bug where the activity icon was not playing when FlatCAM active
+
+5.10.2018
+
+- fixed undesired setting focus to Project Tab when doing the SHIFT + LMB combo (to capture the click coordinates)
+
+4.10.2018
+
+- Excellon Editor: finished Add Drill Array - Linear type action
+- Excellon Editor: finished Add Drill Array - Circular type action
+- detected bug in shortcuts: Fixed
+- Excellon Editor: added constrain for adding circular array, if the number of drills multiplied by angle is more than 360
+the app will return with an message
+- solved sorting bug in the Excellon Editor tool table
+- solved bug in Menu -> Edit -> Sort Origin ; the selection box was not updated after offset
+- added Excellon Export in Menu -> File -> Export -> Export Excellon
+- added support to save the slots in the Excellon file in case there were some in the original file
+- fixed Double Sided Tool for the case of using the box as mirroring reference.
+
+2.10.2018
+
+- made slots persistent after edit
+- bug detected: in Excellon Editor if new tool added diameter is bigger than 10 it mess things up: SOLVED
+- Excellon Editor: finished Drill Resize action
+- after an object is deleted from the Project list, if the current tab in notebook is not Project,
+always focus in the Project Tab (deletion can be done by shortcut key also)
+- changed the initial view to include the possible enabled workspace guides
+
+1.10.2018
+
+- added GUI for Excellon Editor in the Tool Tab
+- Excellon Editor: created and populated the tool list
+- Excellon Editor: added possibility to add new tools in the list
+- Excellon Editor: added possibility to delete a tool (and the drills that it contain) by selecting a row in the tool table and 
+clicking the Delete Tool button
+- Excellon Editor: added possibility to change the tool diameter in the tool list for existing tool diameters.
+- Excellon Editor: when selecting a drill, it will highlight the tool in the Tool table
+- Excellon Editor: optimized single click selection
+- Excellon Editor: added selection for all drills with same diameter upon tool selection in tool table; fix in tool_edit
+- Excellon Editor: added constrain to selection by single click, it will select if within a certain area around the drill
+- Excellon Editor: finished Add Drill action
+- Excellon Editor: finished Move Drill action
+- Excellon Editor: finished Copy Drill action
+
+- fixed issue: when an object is selected before entering the Editor mode, now the selecting shape is deleted before entry 
+in the Editor (be it Geometry or Excellon).
+- fixed a few glitches regarding the units change
+- when an object is deselected on the Plot Area, the notebook will switch to Project Tab
+- changed the selection behavior for the dragging rectangle selection box in Editor (Geometry, Excellon): by dragging a
+selection box and selecting is cumulative: it just adds. To remove from selection press key Ctrl (or Shift depending of 
+the setting in the Preferences) and drag the rectangle across the objects you want to deselect.
+
+29.09.2018
+
+- optimized the combobox item population in Panelization Tool and in Film Tool
+- FlatCAM now remember the last path for saving files not only for opening
+- small fix in GUI
+- work on Excellon Editor. Excellon editor working functions are: loading an Excellon object into Editor, 
+saving an Excellon object from editor to FlatCAM, selecting drills by left click, selection of drills by dragging rectangle, deletion of drills.
+- fixed Excellon merge
+- added more Gcode details (depthperpass parameter in Gcode header) in preprocessors
+- deleted the Tool informations from header in preprocessors due to Mach3 not liking the lot of square brackets
+- more corrections in preprocessors
+
+
+28.09.2018
+
+- added a save_defaults() call on App exit from action on Menu -> File -> Exit
+- solved a small bug in Measurement Tool
+- disabled right mouse click functions when Measurement Tools is active so the user can do panning and find the destination point easily
+- added a new button named "Measure" in Measurement Tool that allow easy access to Measurement Tool from within the tool
+- fixed a bug in Gerber parser that when there was a rectangular aperture used within a region, some artifacts were generated.
+- some more work on Excellon Editor
+
+27.09.2018
+
+- fixed bug when creating a new project, if a previous object was selected on screen, the selection shape survived the creation of a new project
+- added compatibility with old type of FlatCAM projects
+- reverted modifications to the way that Excellon geometry was stored to the old way.
+- added exceptions for Paint functions so the user can know if something failed.
+- modified confirmation messages to use the color coded messages (error = red, success = green, warning = yellow)
+- restored activity icon
+
+26.09.2018
+
+- disabled selection of objects in Project Tab when in Editor
+- the Editor Toolbar is hidden in normal mode and it is showed when Editor is activated. I may change this behaviour back.
+- changed names in classes, functions to prepare for the Excellon editor
+
+- fixed bugs in Paint All function
+- fixed a bug in ParseSVG module in parse_svg_transform(), related to 'scale'
+
+- moved all the Editor menu/toolbar creation to FlatCAMUI where they belong
+- fixed a Gerber parse number issue when Gerber zeros are TZ (keep trailing zeros)
+
+- changed the way of how the solid_geometry for Excellon files is stored and plotted. Before everything was put in the same "container". Now, the geometries of drills and slots are organized into dictionaries having as keys the tool diameters and as values list of Shapely objects (polygons)
+- fix for Excellon plotting for newly created empty Excellon Object
+- fixed geometry.bounds() in camlib to work with the new format of the Excellon geometry (list of dicts)
+
+24.09.2018
+
+- added packages in the Requirements and setup_ubuntu.sh. Tested in Ubuntu and it's OK
+- added Replace (All) feature in the CNC Code Editor
+- made CNC Code generation for Excellon to show progress
+- added information about transforms in the object properties (like skew and how much, if it was mirrored and so on)
+- made all the transforms threaded and make them show progress in the progress bar
+- made FlatCAM project saving, threaded.
+ 
+23.09.2018
+
+- added support for "header-less" Excellon files. It seems that Mentor PADS does generate such non-standard Excellon files. The user will have to guess: units (IN/MM), type of zero suppression LZ/TZ  (leading zeros or trailing zeros are kept) and Excellon number format(digits and decimals).  All of those can be adjusted in Menu -> Edit -> Preferences -> Excellon Object -> Excellon format
+- fixed svgparse for Path. Now PCB rasted images can traced in Inkscape or PDF's can be converted and then saved as SVG files which can be imported into FlatCAM. This is a convolute way to convert a PDF to Gerber file.
+
+22.09.2018
+
+- added Drag & Drop capability. Now the user can drag and drop to FlatCAM GUI interface a file (with the right extension) that can be a FlatCAM project file (.FlatPrj) a Gerber file, an Excellon file, a G-Code file or a SVG file.
+- made the Move Tool command threaded
+- added Image import into FlatCAM
+
+21.09.2018
+
+- added new information's in the object properties: all used Tool-Table items are included in a new entry in self.options dictionary
+- modified the preprocessor files so they now include information's about how many drills (or slots) are for each tool. The Gcode will have this information displayed on the message from ToolChange.
+- removed some log.debug and add new log.debug especially for moments when some process is finished
+- fixed the utility geometry for Font geometry in Geometry Editor
+- work on selection in Geometry Editor
+- added multiple selection key as a Preference in Menu -> Edit -> Preferences It can be either Shift or Ctrl.
+- fixed bug in Gerber Object -> Copper Clearing.
+- added more comprehensive tooltips in Non-copper Clearing as advice on how to proceed.
+- adjusted make_win32.py file so it will work with Python 3.7 (cx_freeze can't copy OpenGL files, so it has to be done manually)
+
+19.09.2018
+
+- optimized loading FlatCAM project by double clicking on project file; there is no need to clean up everything by using the function not Thread Safe: on_file_new() because there is nothing to clean since FlatCAM just started.
+
+- added a workspace delimitation with sizes A3, A4 and landscape or portrait format
+- The Workspace checkbox in Preferences GUI is doing toggle on the workspace
+- made the workspace app default state = False
+- made the workspace to resize when units are changed
+- disabled automatic defaults save (might create SSD wear)
+- added an automatic defaults save on FlatCAM application close
+- made the draw method for the Workspace lines 'agg' so the quality of the FC objects will not be affected
+
+- added Area constrain to the Panelization Tool: if the resulting area is too big to fit within constrains, the number of columns and/or rows will be reduced to the maximum that still fits is.
+- removed the Flip command from Panelization Tools because Flipping (Mirroring) should be done properly with the Transform Tool or using the provided shortcut keys.
+
+- made Font parsing threaded so the application will not wait for the font parsing to complete therefore the app start is faster
+
+
+17.09.2018
+
+- fixed Measuring Tool not working when grid is turned OFF
+- fixed Roland MDX20 preprocessor
+- added a .GBR extension in the open_gerber filter
+- added ability to Scale and Offset (for all types of objects) to just press Enter after entering a value in the Entry just like in Tool Transform
+- added capability in Tool Transform to mirror(flip) around a certain Point. The point coordinates can either be entered by hand or they can be captured by left clicking while pressing key "SHIFT" and then clicking the Add button
+- added the .ROL extension when saving Machine Code
+- replaced strings that reference to G-Code from G-Code to CNC Code
+- added capability to open a project by serving the path/project_name.FlatPrj as a parameter to FlatCAM.py
+
+15.09.2018
+
+- removed dwell line generator and included dwell generation in the preprocessor files
+- added a proposed RML1 Roland_MDX20 preprocessor file.
+- added a limit of 15mm/sec (900mm/min) to the feedrate and to the feedrate_rapid. Anything faster than this will be capped to 900mm/min regardless what is entered in the program GUI. This is because Roland MDX-20 has a mechanical limit of the speed to 15mm/sec (900mm/min in GUI)
+
+14.09.2018
+- remade the Double Sided Tool so it now include mirroring of Excellon and Geometry Objects along Gerber. Made adding points easier by adding buttons to GUI that allow adding the coordinates captured by left mouse click + SHIFT key
+- added a few fixes in code to the other FlatCAM tools regarding reset_fields() function. The issue was present when clicking New Project entry in Menu -> File.
+- FIXED: fix adding/updating bounding box coords for the mirrored objects in Double side Tool.
+- FIXED: fix the bounding box values from within FlatCAM objects, upon units change.
+- fixed issue with running again the constructor of the drawing tools after the tool action was complete, in Geometry Editor
+- fixed issue with Tool tab not closed after Text Input tool is finished.
+- fixed issue with TEXT to GEOMETRY tool, the resulting geometry was not scaled depending of current units
+- fixed case when user is clicking on the canvas to place a Font Geometry without clicking apply button first or the Font Geometry is empty, in Geometry Editor - > Text Input tool
+- reworked Measuring Tool by adding more information's (START, STOP point coordinates) and remade the strings
+- added to Double Sided Tool the ability to use as reference box Excellon and Geometry Objects
+
+12.09.2018
+
+- fixed Excellon Object class such that Excellon files that have both drills and slots are supported
+- remade the GUI interface for the Excellon Object in a more compact way; added a column with slots numbers (if any) along the drills numbers so now there is only one tool table for drills and slots.
+- remade the GUI in Preferences and removed unwanted stretch that was broken the layout.
+- if for a certain tool, the slots number is zero it will not be displayed
+- reworked Text to Geometry feature to work in Linux and MacOS
+- remade the Text to Geometry so font collection process is done once at app start-up improving the performance
+
+
+09.09.2018
+
+- added TEXT ENTRY SUPPORT in Geometry Editor. It will convert strings of True Type Fonts to geometry. The actual dimensions are approximations because font size is in points and not in metric or inch units. For now full support is limited to Windows. In Linux/MacOS only the fonts for which the font name is the same as the font filename are supported. Italic and Bold functions may not work in Linux/MacOS.
+- solved bug: some Drawing menu entries not having connected functions
+
+28.08.2018
+
+- fixed Gerber parser so now G01 "moving" rectangular aperture is supported.
+- fixed import_svg function; it can import SVG as geometry (solved bug)
+- fixed import_svg function; it can import SVG as Gerber (it did not work previously)
+- added menu entry's for SVG import as Gerber and separated import as Geometry
+
+27.08.2018
+
+- fixed Gerber parser so now FlatCAM can load Gerber files generated by Mentor Graphics EDA programs.
+
+26.08.2018
+
+- added awareness for missing coordinates in Gerber parsing. It will try to use the previous coordinates but if there are not any those lines will be ignored and an Warning will be printed in Tcl Shell.
+- fixed TCL commands AlignDrillGrid and DrilCncJob
+- added TCL script file load_and_run support in GUI
+- made the tool_table in Excellon to automatically adjust the table height depending on the number of rows such that all the rows will be displayed.
+- structural changes in the Excellon build_ui()
+- icon changes and menu compress
+
+23.08.2018
+
+- added Excellon routing support
+- solved a small bug that crippled Excellon slot G85 support when the coordinates are with period.
+- changed the way selection is done in Geometry Editor; now it should work in all cases (although the method used may be computationally intensive, because sometimes you have to click twice to make selection if you do it too fast)
+
+21.08.2018
+
+- added Excellon slots support when using G85 command for generation of the slots file. Inspired from the work of @mgix. Thanks. Routing format support for slots will follow. 
+- minor bug solved: option "Cut over 1st pt" now has same name both in Preferences -> Geometry Options and in Selected tab -> Geomety Object. Solves #3
+- added option to select Climb or Conventional Milling in Gerber Object options Solves #4
+- made "Combine passes" option to be saved as an app preference
+- added Generate Exteriors Geo and Generate Interiors Geo buttons in the Gerber Object properties
+- added configuration for the number of steps used for Gerber circular aperture linear approximation. The option is in Preferences -> Gerber Options
+- added configuration for the number of steps used for Gcode circular aperture linear approximation. The option is in Preferences -> CNCjob Options
+- added configuration for the number of steps used for Geometry circular aperture linear approximation. The option is in Preferences -> Geometry Options. It is used on circles/arcs made in Geometry Editor and for other types of geometries generated in the app.
+
+17.07.2018
+
+- added the required packages in Requirements.txt file
+- added required packages in setup_ubuntu.sh file
+- added color control over almost all the colors in the application; those settings are in Menu -> Edit -> Preferences -> General Tab
+- added configuration of which mouse button to be used when panning (MMB or RMB)
+- fixed bug with missing 'drillz' parameter in function generate_from_excellon_by_tool() (credits for finding it goes to Stefan Smith https://bitbucket.org/stefan064/)
+- load Factory defaults in Preferences will load the defaults that are used just after first install. Load Defaults option in Preferences will load the User saved Defaults.
+
+03.07.2018
+
+- fixed bug in rotate function that didn't update the bounding box of the modified object (rotated) due of not emitting the right signal parameter.
+- removed the Options tab from the Notebook (the left area where is located also the Project tab). Replaced it with the Preferences Tab launched with Menu -> Edit -> Preferences
+- when FlatCAM is used under MacOS, multiple selection of shapes in Editor mode is done using SHIFT key instead of CTRL key due of MacOS interpreting Ctrl+LMB_click as a RMB click
+- when in Editor, clicking not on a shape, reset the index of selected shapes to zero
+- added a new Tab in the Plot Area named Gcode Editor. It allow the user to edit the Gcode and then Save it or Print it.
+- added a fix so the 'preamble' Gcode is correctly inserted between the comments header and the actual GCODE
+- added Find function in G-Code Editor
+
+27.06.2018
+
+- the Plot Area tab is changing name to "Editor Area" when the Editor is activated and returns to the "Plot Area" name upon exiting the Editor
+- made the labels shorter in Transform Tool in anticipation of Options Tab removal from Notebook and replacing it with Preferences
+- the Excellon Editor is not finished (not even started yet) so the Plot Area title should stay "Plot Area" not change to "Editor Area" when attempting to edit an Excellon file. Solved.
+- added a header comment block in the generated Gcode with useful information's
+- fixed issue that did not allow the Nightly's to be run in Windows 7 x64. The reason was an outdated DLL file (freetype.dll) used by Vispy python module.
+
+25.06.2018
+
+- "New" menu entry in Menu -> File is renamed to "New Project"
+- on "New Project" action, all the Tools are reinitialized so the Tools tab will work as expected
+- fixed issue in Film Tool when generating black film
+- fixed Measurement Tool acquiring and releasing the mouse/key events
+- fixed cursor shape is updated on grid_toggle
+- added some infobar messages to show the user when the Editor was activated and when it was closed (control returned to App).
+- added thread usage for Film tool; now the App is no longer blocked on film generation and there is a visual clue that the App is working
+
+22.06.2018
+
+- added export PNG image functionality and menu entry in Menu -> File -> Export PNG ...
+- added a command to set focus on canvas inside the mouve move event handler; once the mouse is moved the focus is moved to canvas so the shortcuts work immediatly.
+- solved a small bug when using the 'C' key to copy name of the selected object to clipboard
+- fixed millholes() function and isolate() so now it works even when the tool diameter is the same as the hole diameter.
+
+Actually if the passed value to  the buffer() function is zero, I
+artificially add a value of 0.0000001 (FlatCAM has a precision of
+6 decimals so I use a tenth of that value as a pseudo "zero")
+because the value has to be positive. This may have solved for some use
+cases the user complaints that on clearing the areas of copper there is
+still copper leftovers.
+
+- added shortcut "Shift+G" to toggle the axis presence. Useful when one wants to save a PNG file.
+- changed color of the grid from 'gray' to 'dimgray'
+- the selection shape is deleted when the object is deleted
+- the plot area is now in a TAB.
+- solved bug that allowed middle button click to create selection
+- fixed issue with main window geometry restore (hopefully).
+- made view toolbar to be hidden by default as it is not really needed (we have the functions in menu, zoom is done with mouse wheel, and there is also the canvas context menu that holds the functionality)
+- remade the GUIElements.FCInput() and made a GUIElements.FCTab()
+- on visibility plot toogle the selection shape is deleted
+- made sure that on panning in Geometry editor, the context menu is not displayed
+- disabled App shortcut keys on entry in Geometry Editor so only the local shortcut keys are working
+- deleted metric units in canvas context menu
+- added protection so object deletion can't be done until Geometry Editor session is finished. Solved bug when the shapes on Geometry Editor were not transferred to the New_geometry object yet and the New_Geometry object is deleted. In this case the drawn shapes are left in a intermediary state on canvas.
+- added selection shape drawing in Geometry Editor preserving the current behavior: click to select, click on canvas clear selection, Ctrl+click add to selection new shape but remove from selection if already selected. Drag LMB from left to right select enclosed shapes, drag LMB from right to left select touching shapes. Now the selection is made based on
+- added info message to be displayed in infobar, when a object is renamed
+
+20.06.2018
+
+- there are two types of mouse drag selection (rectangle selection). If there is a rectangle selection from left to right, the color of the selection rectangle is blue and the selection is "enclosing" - this means that the object to be selected has to be enclosed by the selecting blue rectangle shape. If there is a rectangle selection fro right to left, the color of the selection rectangle is green and the selection is "touching" - this means that it's enough to touch with the selecting green rectangle the object(s) to be selected so they become selected
+- changed the modifier key required to be pressed when LMB is ckicked over canvas in order to copy to clipboard the coordinates of the click, from CTRL to SHIFT. CTRL will be used for multiple selection.
+- change the entry names in the canvas context menu
+- disconnected the app mouse event functions while in geometry editor since the geometry editor has it's own mouse event functions and there was interference between object and geometry items. Exception for the mouse release event so the canvas context menu still work.
+- solved a bug that did not update the obj.options after a geometry object was edited in geometry editor
+- solved a bug in the signal that saved the position and dimensions of the application window.
+- solved a bug in app.on_preferences() that created an error when run in Linux
+
+18.06.2018 Update 1
+
+- reverted the 'units' parameter change to 'global_units' due of a bug that did not allow saving of the project
+- modified the camlib transform (rotate, mirror, scale etc) functions so now they work with Gerber file loaded with 'follow' parameter
+
+18.06.2018
+
+- reworked the Properties context menu option to a Tool that displays more informations on the selected object(s)
+- remade the FlatCAM project extension as .FlatPrj
+- rearranged the toolbar menu entries to a more properly order
+- objects can now be selected on canvas, a blue polygon is drawn around when selected
+- reworked the Tool Move so it will work with the new canvas selection
+- reworked the Measurement Tool so it will work with the new canvas selection
+- canvas selection can now be done by dragging left mouse boutton and creating a selection box over the objects
+- when the objects are overlapped on canvas, the mouse click selection works in a circular way, selecting the first, then the second, then ..., then the last and then again the first and so on.
+- double click on a object on canvas will open the Selected Tab
+- each object store the bounding box coordinates in the options dict
+- the bbox coordinates are updated on the obj options when the object is modified by a transform function (rotate, scale etc)
+
+
+15.06.2018
+
+- the selection marker when moving is now a semitransparent Polygon with a blue border
+- rectified a small typo in the ToolTip for Excellon Format for Diptrace excellon format; from 4:2 to 5:2
+- corrected an error that cause no Gcode could be saved
+
+14.06.2018
+
+- more work on the contextual menu
+- added Draw context menu
+- added a new tool that bring together all the transformations, named Transformation Tool (Rotate, Skew, Scale, Offset, Flip)
+- added shorcut key 'Q' which toggle the units between IN and MM
+- remade the Move tool, there is now a selection box to show where the move is done
+- remade the Measurement tool, there is now a line between the start point of measurement and the end point of the measurement.
+- renamed most of the system variables that have a global app effect to global_name where name is the parameter (variable)
+
+9.06.2018
+
+- reverted to PyQt4. PyQt5 require too much software rewrite
+- added calculators: units_calculator and V-shape Tool calculator
+- solved bug in Join Excellon
+- added right click menu over canvas
+
+6.06.2018 Update
+
+- fixed bug: G-Code could not be saved
+- fixed bug: double clicking a category in Project Tab made the app to crash
+- remade the bounds() function to work with nested lists of objects as per advice from JP which made the operation less performance taxing.
+- added shortcut Shift+R that is complement to 'R'
+- shorcuts 'R' and 'Shift+R' are working now in steps of 90 degrees instead of previous 45 degrees.
+- added filters in the open ... FlatCAM projects are saved automatically as *.flat, the Gerber files have few categories. So the Excellons and G-Code and SVG.
+
+6.06.2018
+
+- remade the transform functions (rotate, flip, skew) so they are now working for joined objects, too
+- modified the Skew and Rotate comamands: if they are applied over a selection of objects than the origin point will be the center of the biggest bounding box. That allow for perfect sync between the selected objects
+- started to modify the program so the exceptions are handled correctly
+- solved bug where a crash occur when ObjCollection.setData didn't return a bool value
+- work in progress for handling situations when a different file is loaded as another (like loading a Gerber file using Open Excellon commands.
+- added filters on open_gerber and open_excellon Dialogs. There is still the ability to select All Files but this should reduce the cases when the user is trying to oprn a file from a wrong place.
+
+4.06.2018
+
+- finished PyQt4 to PyQt4 port on the Vispy variant (there were some changes compared with the Matplotlib version for which the port was finished some time ago)
+- added Ctrl+S shortcut for the Geometry Editor. When is activated it will save de geometry ("update") and return to the main App.
+- modified the Mirror command for the case when multiple objects are selected and we want to mirror all together. In this case they should mirror around a bounding box to fill all.
+
+3.06.2018
+
+- removed the current drill path optimizations as they are inefficient
+- implemented Google OR-tools drill path optimization in 2 flavors; Basic OR-tools TSP algorithm and OR-Tools Metaheuristics Guided Local Path
+- Move tool is moved to Menu -> Edit under the name Move Object
+
+- solved some internal bugs (info command was creating an non-fatal error in PyQt, regarding using QPixMaps outside GUI thread
+- reworked camlib number parsing (still had some bugs)
+- working in porting the application from usage of PyQt4 to PyQt4
+- added TclCommands save_sys and list_sys. save_sys is saving all the system default parameters and list_sys is listing them by the first letters. listsys with no arguments will list all the system parameters.
+
+29.05.2018
+
+- modified the labels for the X,Y and Dx,Dy coordinates
+- modified the menu entries, added more icons
+- added initial work on a Excellon Editor
+- modified the behavior of when clicking on canvas the coordinates were copied to cliboard: now it is required to press CTRL key for this to happen, and it will only happen just for left mouse button click
+- removed the autocopy of the object name on new object creation
+- remade the Tcl commands drillcncjob and cncjob
+- added fix so the canvas is focused on the start of the program, therefore the shortcuts work without the need for doing first a click on canvas.
+
+28.05.2018
+
+- added total drill count column in Excellon Tool Table which displays the total number of drills
+- added aliases in panelize Tool (pan and panel should work)
+- modified generate_milling method which had issues from the Python3 port (it could not sort the tools due of dict to dict comparison no longer possible).
+- modified the 'default' preprocessor in order to include a space between the value of Xcoord and the following Y
+- made optional the using of threads for the milling command; by default it is OFF (False) because in the current configuration it creates issues when it is using threads
+- modified the Panelize function and Tcl command Panelize. It was having issues due to multithreading (kept trying to modify a dictionary in redraw() method)and automatically selecting the last created object (feature introduced by me). I've added a parameter to the new_object method, named autoselected (by default it is True) and in the panelize method I initialized it with False.
+By initializing the plot parameter with False for the temporary objects, I have increased dramatically the  generation speed of the panel because now the temporary object are no longer ploted which consumed time.
+- replaced log.warn() with log.warning() in camlib.py. Reason: deprecated
+- fixed the issue that the "Defaults" button was having no effect when clicked and Options Combo was in Project Options
+- fixed issue with Tcl Shell loosing focus after each command, therefore needing to click in the edit line before we type a new command (borrowed from @brainstorm
+- added a header in the preprocessor files mentioning that the GCODE files were generated by FlatCAM.
+- modified the number of decimals in some of the line entries to 4.
+- added an alias for the millholes Tcl Command: 'mill'
+
+27.04.2018
+
+- modified the Gerber.scale() function from camlib.py in order to allow loading Gerber files with 'follow' parameter in other units than the current ones
+- snap_max_entry is disabled when the DRAW toolbar is disabled (previous fix didn't work)
+- added drill count column in Excellon Tool Table which displays the total number of drills for each tool
+- added a new menu entry in Menu -> EDIT named "Join Excellon". It will merge a selection of Excellon files into a new Excellon file
+- added menu stubs for other Excellon based actions
+- solved bug that was not possible to generate film from joined geometry
+- improved toggle active/inactive of the object through SPACE key. Now the command works not only for one object but also for a selection
+
+26.05.2018
+
+- made conversion to Python3
+- added Rtree Indexing drill path optimization
+- added a checkbox in Options Tab -> App Defaults -> Excellon Group named Excellon Optim. Type from which it can be selected the default optimization type: TS stands for Travelling Salesman algorithm and Rtree stands for Rtree Indexing
+- added a checkbox on the Grid Toolbar that when checked (default status is checked) whatever value entered in the GridX entry will be used instead of the now disabled GridY entry
+- modified the default behavior on when a line_entry is clicked. Now, on each click on a line_entry, the content is automatically selected.
+- snap_max_entry is disabled when the DRAW toolbar is disabled
+
+24.05.2015
+
+- in Geometry Editor added a initial form of Rotate Geometry command in toolbar
+- changed the way the geometry is finished if it requires a key: before it was using key 'Space' now it uses 'Enter'
+- added Shortcut for Rotate Geometry to key 'Space'
+- after using a tool in Geometry Editor it automatically defaults to 'Select Tool'
+
+23.05.2018
+
+Added key shortcut's in FlatCAMApp and in Geometry Editor.
+
+FlatCAMApp shortcut list:
+1      Zoom Fit
+2      Zoom Out
+3      Zoom In
+C      Copy Obj_Name
+E      Edit Geometry (if selected)
+G      Grid On/Off
+M      Move Obj
+
+N      New Geometry
+R      Rotate
+S      Shell Toggle
+V      View Fit
+X      Flip on X_axis
+Y      Flip on Y_axis
+~      Show Shortcut List
+
+Space:   En(Dis)able Obj Plot
+Ctrl+A   Select All
+Ctrl+C   Copy Obj
+Ctrl+E   Open Excellon File
+Ctrl+G   Open Gerber File
+Ctrl+M   Measurement Tool
+Ctrl+O   Open Project
+Ctrl+S   Save Project As
+Delete   Delete Obj'''
+
+
+Geometry Editor Key shortcut list:
+A       Add an 'Arc'
+C       Copy Geo Item
+G       Grid Snap On/Off
+K       Corner Snap On/Off
+M       Move Geo Item
+
+N       Add an 'Polygon'
+O       Add a 'Circle'
+P       Add a 'Path'
+R       Add an 'Rectangle'
+S       Select Tool Active
+
+
+~        Show Shortcut List
+Space:   Rotate Geometry
+Enter:   Finish Current Action
+Escape:  Abort Current Action
+Delete:  Delete Obj
+
+22.05.2018
+
+- Added Marlin preprocessor
+- Added a new entry into the Geometry and Excellon Object's UI: Feedrate rapid: the purpose is to set a feedrate for the G0 command that some firmwares like Marlin don't intepret as 'move with highest speed'
+- FlatCAM was not making the conversion from one type of units to another for a lot of parameters. Corrected that.
+- Modified the Marlin preprocessor so it will generate the required GCODE.
+
+21.05.2018
+
+- added new icons for menu entries
+- added shortcuts that work on the Project tab but also over Plot. Shorcut list is accesed with shortcut key '~' sau '`'
+- small GUI modification: on each "New File" command it will switch to the Project Tab regardless on which tab we were.
+- removed the global shear entries and checkbox as they can be damaging and it will build effect upon effect, which is not good
+- solved bug in that the Edit -> Shear on X (Y)axis could adjust only in integers. Now the angle can be adjusted in float with 3 decimals.
+- changed the tile of QInputDialog to a more general one
+- changed the "follow" Tcl command to the new format
+- added a new entry in the Menu -> File, to open a Gerber with the follow parameter = True
+- added a new checkbox in the Gerber Object Selection Tab that when checked it will create a "follow" geometry
+- added a few lines in Mill Holes Tcl command to check if there are promises and raise an Tcl error if there are any.
+- started to modify the Export_Svg Tcl command
+
+20.05.2018
+
+- changed the interpretation of the axis for the rotate and skew commands. Actually I reversed them to reflect reality.
+- for the rotate command a positive angle now rotates CW. It was reversed.
+- added shortcuts (for outside CANVAS; the CANVAS has it's own set of shortcuts) Ctrl+C will copy to clipboard the name of the selected object Ctrl+A will Select All objects
+"X" key will flip the selected objects on X axis
+"Y" key will flip the selected objects on Y axis
+"R" key will rotate CW with a 45 degrees step
+
+- changed the layout for the top of th Options page. Added a checkbox and entries for parameters for skew command. When the checkbox is checked it will save (and load at the next startup of the program) the option that at each CNCJob generation (be it from Excellon or Geometry) it will perform the Skew command with the parametrs set in the nearby field boxes (Skew X and Skey Y angles). It is useful in case the CNC router is not perfectly alligned between the X and Y axis
+- added some protection in case the skew command receive a None parameter
+- BUG solved: made an UGLY (really UGLY) HACK so now, when there is a panel geometry generated from GUI, the project WILL save. I had to create a copy of the generated panel geometry and delete the original panel geometry. This way there is no complain from JSON module about circular reference.
+- removed the Save buttons previously added on each Group in Application Defaults. Replaced them with a single Save button that stays always on top of the Options TAB
+- added settings for defaults for the Grid that are persistent
+- changed the default view at FlatCAM startup: now the origin is in the center of the screen
+
+19.05.2018
+
+- last object that is opened (created) is always automatically selected and the name of the object is automatically copied to clipboard; useful when using the TCL command :)
+- added new commands in MENU -> EDIT named: "Copy Object" and "Copy Obj as Geom". The first command will duplicate any object (Geometry, Gerber, Excellon). The second command will duplicate the object as a geometry. For example, holes in Excello now are just circles that can be "painted" if one wants it.
+- added new Tool named ToolFreeformCutout. It does what it says, it will make a board cutout from a "any shape" Gerber or Geometry file
+- solved bug in the TCL command "drillcncjob" that always used the endz parameter value as the toolchangez parameter value and for the endz value used a default value = 1
+- added preprocessor name into the TCL command "drillcncjob" parameters
+- when adding a new geometry the default name is now: "New_Geometry" instead of "New Geometry". TCL commands don't handle the spaces inside the name and require adding quotes.
+- solved bug in "cncjob" TCL command in which it used multidepth parameter as always True regardless of the argument provided
+- added a checkbox for Multidepth in the Options Tab -> Application Defaults
+
+18.05.2018
+
+- added an "Defaults" button in Excellon Defaults Group; it loads the following configuration (Excellon_format_in 2:4, Excellon_format_mm 3:3, Excellon_zeros LZ)
+- added Save buttons for each Defaults Group; in the future more parameters will be propagated in the app, for now they are a few
+- added functions for Skew on X axis and for Skew on Y menu stubs. Now, clicking on those Menu -> Options -> Transform Object menu entries will trigger those functions
+- added a CheckBox button in the Options Tab -> Application Defaults that control the behaviour of the TCL shell: checking it will make the TCL shell window visible at each start-up, unchecking it the TCL shell window will be hidden until needed
+- Depth/pass parameter from Geometry Object CNC Job is now in the defaults and it will keep it's value until changed in the Application Defaults.
+
+17.05.2018
+
+- added messages box for the Flip commands to show error in case there is no object selected when the command is executed
+- added field entries in the Options TAB - > Application Defaults for the following newly introduced parameters: 
+excellon_format_upper_in
+excellon_format_lower_in
+excellon_format_upper_mm
+excellon_format_lower_mm
+
+The ones with upper indicate how many digits are allocated for the units and the ones with lower indicate how many digits from coordinates are alocated for the decimals.
+
+[  Eg: Excellon format 2:4 in INCH
+   excellon_format_upper_in = 2
+   excellon_format_lower_in = 4
+where the first 2 digits are for units and the last 4 digits are
+decimals so from a number like 235589 we will get a coordinate 23.5589
+]
+
+- added Radio button in the Options TAB - > Application Defaults for the Excellon_zeros parameter
+After each change of those parameters the user will have to press "Save defaults" from File menu in order to propagate the new values, or wait for the autosave to kick in (each 20sec).
+Those parameters can be set in the set_sys TCL command.
+
+15.05.2018
+- modified SetSys TCL command: now it can change units
+- modified SetSys TCL command: now it can set new parameters: excellon_format_mm and excellon_format_in. the first one is when the excellon units are MM and the second is for when the excellon units are in INCH. Those parameters can be set with a number between 1 and 5 and it signify how many digits are before coma.
+- added new GUI command in EDIT -> Select All. It will select all objects on the first mouse click and on the second will deselect all (toggle action)
+- added new GUI commands in Options -> Transform object. Added Rotate selection, Flip on X axis of the selection and Flip on Y axis of the selection For the Rotate selection command, negative numbers means rotation CCW and positive numbers means rotation CW.
+- cleaned up a bit the module imports
+- worked on the excellon parsing for the case of trailing zeros. If there are more than 6digits in the coordinates, in case that there is no period, now the software will identify the issue and attempt to correct it by dividing the coordinate  further by 10 for each additional digit over 6. If the number of digits is less than 6 then the software will multiply by 10 the coordinates
+
+14.05.2018
+
+- fixed bug in Geometry CNCJob generation that prevented generating the object
+- added GRBL 1.1 preprocessor and Laser preprocessor (adapted from the work of MARCO A QUEZADA)
+
+13.05.2018
+
+- added postprocessing in correct form
+- added the possibility to select an preprocessor for Excellon Object
+- added a new preprocessor, manual_toolchange.py. It allows to change the tools and adjust the drill tip to touch the surface manually, always in the X=0, Y=0, Z = toolchangeZ coordinates.
+- fixed drillcncjob TCL command by adding toolchangeZ parameter
+- fixed the preprocessor file template 'default.py' in the toolchange command section
+- after I created a feature that the message in infobar is cleared by moving mouse on canvas, it generated a bug in TCL shell: everytime  mouse was moved it will add a space into the TCL read only section. Now this bug is fixed.
+- added an EndZ parameter for the drillcncjob and cncjob TCL commands: it will specify at what Z value to park the CNC when job ends
+- the spindle will be turned on after the toolchange and it will be turned off just before the final end move.
+
+Previously:
+- added GRID based working of FLATCAM
+- added Set Origin command
+- added FilmTool, PanelizeTool GUI, MoveTool
+- and others
+
+24.04.2018
+
+- Remade the Measurement Tool: it now ask for the Start point of the measurement and then for the Stop point. After it will display the measurement until we left click again on the canvas and so on. Previously you clicked the start point and reset the X and Y coords displayed and then you moved the mouse pointer wherever you wanted to measure, but moving the mouse from there you lost the measurement.
+- Added Relative measurement on the main plot
+- Now both the measuring tool and the relative measurement will work only with the left click of the mouse button because middle mouse click and right mouse click are used for panning
+- Renamed the tools files starting with Tool so they are grouped (in the future they may have their own folder like for TCL Commands)
+
+- Commented some shortcut keys and functions for features that are not present anymore or they are planned to be in the future but unfinished (like buffer tool, paint tool)
+- minor corrections regarding PEP8 (Pycharm complains about the m)
+- solved bug in TclCommandsSetSys.py Everytime that the command was executed it complain about the parameter not being in the list (something like this). There was a missing “else:”
+- when using the command “set_sys excellon_zeros” with parameter in lower case (either ‘l’ or ‘t’) now it is always written in the defaults file as capital letter
+
+- solved a bug introduced by me: when apertures macros were detected in Excellon file, FlatCam will complain about missing dictionary key “size”. Now it first check if the aperture is a macro and perform the check for zero value only for apertures with “size” key
+- solved a bug that didn't allowed FC to detect if Excellon file has leading zeros or trailing zeros
+- solved a bug that FC was searching for char ‘%’ that signal end of Excellon header even in commented lines (latest versions of Eagle end the commented line with a ‘%’)
+
+
+============================================
+This fork features:
+
+- Added buttons in the menu bar for opening of Gerber and Excellon files;
+- Reduced number of decimals for drill bits to two decimals;
+- Updated make_win32.py so it will work with cx_freeze 5.0.1 
+- Added capability so FlatCAM can now read Gerber files with traces having zero value (aperture size is zero);
+- Added Paint All / Seed based Paint functions from the JP's FlatCAM;
+- Added Excellon move optimization (travelling salesman algorithm) cherry-picked from David Kahler: https://bitbucket.org/dakahler/flatcam
+- Updated make_win32.py so it will work with cx_freeze 5.0.1
+- Corrected small typo in DblSidedTool.py
+- Added the TCL commands in the new format. Picked from FLATCAM master.
+- Hack to fix the issue with geometry not being updated after a TCL command was executed. Now after each TCL command the plot_all() function is executed and the canvas is refreshed.
+- Added GUI for panelization TCL command
+- Added GUI tool for the panelization TCL command: Changed some ToolTips.
+============================================
+
+Previously added features by Dennis
+
+- "Clear non-copper" feature, supporting multi-tool work.
+- Groups in Project view.
+- Pan view by dragging in visualizer window with pressed MMB.
+- OpenGL-based visualizer.
+

+ 21 - 8
FlatCAM.py

@@ -1,7 +1,7 @@
 import sys
 import os
 
-from PyQt5 import QtWidgets, QtGui
+from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings, Qt
 from FlatCAMApp import App
 from flatcamGUI import VisPyPatches
@@ -32,6 +32,19 @@ if __name__ == '__main__':
     # NOTE: Never talk to the GUI from threads! This is why I commented the above.
     freeze_support()
 
+    # Supported Python version is >= 3.5
+    if sys.version_info.major >= 3:
+        if sys.version_info.minor >= 5:
+            pass
+        else:
+            print("FlatCAM BETA uses PYTHON 3. The version minimum is 3.5\n"
+                  "Your Python version is: %s" % str(sys.version_info))
+            os._exit(0)
+    else:
+        print("FlatCAM BETA uses PYTHON 3. The version minimum is 3.5\n"
+              "Your Python version is: %s" % str(sys.version_info))
+        os._exit(0)
+
     debug_trace()
     VisPyPatches.apply_patches()
 
@@ -55,6 +68,11 @@ if __name__ == '__main__':
     # else:
     #     QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling, False)
 
+    if hdpi_support == 2:
+        QtWidgets.QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
+    else:
+        QtWidgets.QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, False)
+
     app = QtWidgets.QApplication(sys.argv)
 
     # apply style
@@ -63,11 +81,6 @@ if __name__ == '__main__':
         style = settings.value('style', type=str)
         app.setStyle(style)
 
-    if hdpi_support == 2:
-        app.setAttribute(Qt.AA_EnableHighDpiScaling, True)
-    else:
-        app.setAttribute(Qt.AA_EnableHighDpiScaling, False)
-
     fc = App()
-
-    sys.exit(app.exec_())
+    # sys.exit(app.exec_())
+    app.exec_()

Plik diff jest za duży
+ 313 - 162
FlatCAMApp.py


Plik diff jest za duży
+ 1480 - 16
FlatCAMCommon.py


Plik diff jest za duży
+ 533 - 179
FlatCAMObj.py


+ 123 - 1
FlatCAMTool.py

@@ -9,7 +9,15 @@
 from PyQt5 import QtGui, QtCore, QtWidgets, QtWidgets
 from PyQt5.QtCore import Qt
 
-from shapely.geometry import Polygon
+from shapely.geometry import Polygon, LineString
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
 
 
 class FlatCAMTool(QtWidgets.QWidget):
@@ -98,6 +106,7 @@ class FlatCAMTool(QtWidgets.QWidget):
 
         :param old_coords: old coordinates
         :param coords: new coordinates
+        :param kwargs:
         :return:
         """
 
@@ -135,6 +144,119 @@ class FlatCAMTool(QtWidgets.QWidget):
         if self.app.is_legacy is True:
             self.app.tool_shapes.redraw()
 
+    def draw_selection_shape_polygon(self, points, **kwargs):
+        """
+
+        :param points: a list of points from which to create a Polygon
+        :param kwargs:
+        :return:
+        """
+        if 'color' in kwargs:
+            color = kwargs['color']
+        else:
+            color = self.app.defaults['global_sel_line']
+
+        if 'face_color' in kwargs:
+            face_color = kwargs['face_color']
+        else:
+            face_color = self.app.defaults['global_sel_fill']
+
+        if 'face_alpha' in kwargs:
+            face_alpha = kwargs['face_alpha']
+        else:
+            face_alpha = 0.3
+        if len(points) < 3:
+            sel_rect = LineString(points)
+        else:
+            sel_rect = Polygon(points)
+
+        # color_t = Color(face_color)
+        # color_t.alpha = face_alpha
+
+        color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
+
+        self.app.tool_shapes.add(sel_rect, color=color, face_color=color_t, update=True,
+                                 layer=0, tolerance=None)
+        if self.app.is_legacy is True:
+            self.app.tool_shapes.redraw()
+
     def delete_tool_selection_shape(self):
         self.app.tool_shapes.clear()
         self.app.tool_shapes.redraw()
+
+    def draw_moving_selection_shape_poly(self, points, data, **kwargs):
+        """
+
+        :param points:
+        :param data:
+        :param kwargs:
+        :return:
+        """
+        if 'color' in kwargs:
+            color = kwargs['color']
+        else:
+            color = self.app.defaults['global_sel_line']
+
+        if 'face_color' in kwargs:
+            face_color = kwargs['face_color']
+        else:
+            face_color = self.app.defaults['global_sel_fill']
+
+        if 'face_alpha' in kwargs:
+            face_alpha = kwargs['face_alpha']
+        else:
+            face_alpha = 0.3
+
+        temp_points = [x for x in points]
+        try:
+            if data != temp_points[-1]:
+                temp_points.append(data)
+        except IndexError:
+            return
+
+        l_points = len(temp_points)
+        if l_points == 2:
+            geo = LineString(temp_points)
+        elif l_points > 2:
+            geo = Polygon(temp_points)
+        else:
+            return
+
+        color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
+        color_t_error = "#00000000"
+
+        if geo.is_valid and not geo.is_empty:
+            self.app.move_tool.sel_shapes.add(geo, color=color, face_color=color_t, update=True,
+                                              layer=0, tolerance=None)
+        elif not geo.is_valid:
+            self.app.move_tool.sel_shapes.add(geo, color="red", face_color=color_t_error, update=True,
+                                              layer=0, tolerance=None)
+
+        if self.app.is_legacy is True:
+            self.app.move_tool.sel_shapes.redraw()
+
+    def delete_moving_selection_shape(self):
+        self.app.move_tool.sel_shapes.clear()
+        self.app.move_tool.sel_shapes.redraw()
+
+    def confirmation_message(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform.emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' %
+                                 (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval))
+        else:
+            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+
+    def confirmation_message_int(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform.emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                 (_("Edited value is out of range"), minval, maxval))
+        else:
+            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+
+    def sizeHint(self):
+        """
+        I've overloaded this just in case I will need to make changes in the future to enforce dimensions
+        :return:
+        """
+        default_hint_size = super(FlatCAMTool, self).sizeHint()
+        return QtCore.QSize(default_hint_size.width(), default_hint_size.height())

+ 6 - 0
FlatCAMTranslation.py

@@ -183,6 +183,12 @@ def restart_program(app, ask=None):
     else:
         resource_loc = 'share'
 
+    # close the Socket in ArgsThread class
+    app.new_launch.listener.close()
+
+    # close the QThread that runs ArgsThread class
+    app.th.quit()
+
     if app.should_we_save and app.collection.get_list() or ask is True:
         msgbox = QtWidgets.QMessageBox()
         msgbox.setText(_("There are files/objects modified in FlatCAM. "

+ 175 - 56
ObjectCollection.py

@@ -16,7 +16,8 @@ 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
+from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon, FlatCAMCNCjob, FlatCAMDocument, FlatCAMScript, \
+    FlatCAMObj
 import inspect  # TODO: Remove
 import FlatCAMApp
 
@@ -55,6 +56,17 @@ class KeySensitiveListView(QtWidgets.QTreeView):
         self.filename = ""
         self.app = app
 
+        # Enabling Drag and Drop for the items in the Project Tab
+        # Example: https://github.com/d1vanov/PyQt5-reorderable-list-model/blob/master/reorderable_list_model.py
+        # https://github.com/jimmykuu/PyQt-PySide-Cookbook/blob/master/tree/drop_indicator.md
+        # self.setDragEnabled(True)
+        # self.viewport().setAcceptDrops(True)
+        # self.setDropIndicatorShown(True)
+        # self.DragDropMode(QtWidgets.QAbstractItemView.InternalMove)
+        # self.current_idx = None
+        # self.current_group = None
+        # self.dropped_obj = None
+
     keyPressed = QtCore.pyqtSignal(int)
 
     def keyPressEvent(self, event):
@@ -62,6 +74,11 @@ class KeySensitiveListView(QtWidgets.QTreeView):
         self.keyPressed.emit(event.key())
 
     def dragEnterEvent(self, event):
+        # if event.source():
+        #     self.current_idx = self.currentIndex()
+        #     self.current_group = self.model().group_items[self.current_idx.internalPointer().obj.kind]
+        #     self.dropped_obj = self.current_idx.internalPointer().obj
+
         if event.mimeData().hasUrls:
             event.accept()
         else:
@@ -69,6 +86,7 @@ class KeySensitiveListView(QtWidgets.QTreeView):
 
     def dragMoveEvent(self, event):
         self.setDropIndicatorShown(True)
+
         if event.mimeData().hasUrls:
             event.accept()
         else:
@@ -77,6 +95,20 @@ class KeySensitiveListView(QtWidgets.QTreeView):
     def dropEvent(self, event):
         drop_indicator = self.dropIndicatorPosition()
 
+        # if event.source():
+        #     new_index = self.indexAt(event.pos())
+        #     new_group = self.model().group_items[new_index.internalPointer().obj.kind]
+        #     if self.current_group == new_group:
+        #
+        #         # delete it from the model
+        #         deleted_obj_name = self.dropped_obj.options['name']
+        #         self.model().delete_by_name(deleted_obj_name)
+        #
+        #         # add the object to the new index
+        #         self.model().append(self.dropped_obj, to_index=new_index)
+        #
+        #         return
+
         m = event.mimeData()
         if m.hasUrls:
             event.accept()
@@ -84,46 +116,46 @@ class KeySensitiveListView(QtWidgets.QTreeView):
             for url in m.urls():
                 self.filename = str(url.toLocalFile())
 
-            # file drop from outside application
-            if drop_indicator == QtWidgets.QAbstractItemView.OnItem:
-                if self.filename == "":
-                    self.app.inform.emit(_("Open cancelled."))
-                else:
-                    if self.filename.lower().rpartition('.')[-1] in self.app.grb_list:
-                        self.app.worker_task.emit({'fcn': self.app.open_gerber,
-                                                   'params': [self.filename]})
-                    else:
-                        event.ignore()
-
-                    if self.filename.lower().rpartition('.')[-1] in self.app.exc_list:
-                        self.app.worker_task.emit({'fcn': self.app.open_excellon,
-                                                   'params': [self.filename]})
+                # file drop from outside application
+                if drop_indicator == QtWidgets.QAbstractItemView.OnItem:
+                    if self.filename == "":
+                        self.app.inform.emit(_("Cancelled."))
                     else:
-                        event.ignore()
-
-                    if self.filename.lower().rpartition('.')[-1] in self.app.gcode_list:
-                        self.app.worker_task.emit({'fcn': self.app.open_gcode,
-                                                   'params': [self.filename]})
-                    else:
-                        event.ignore()
-
-                    if self.filename.lower().rpartition('.')[-1] in self.app.svg_list:
-                        object_type = 'geometry'
-                        self.app.worker_task.emit({'fcn': self.app.import_svg,
-                                                   'params': [self.filename, object_type, None]})
-
-                    if self.filename.lower().rpartition('.')[-1] in self.app.dxf_list:
-                        object_type = 'geometry'
-                        self.app.worker_task.emit({'fcn': self.app.import_dxf,
-                                                   'params': [self.filename, object_type, None]})
-
-                    if self.filename.lower().rpartition('.')[-1] in self.app.prj_list:
-                        # self.app.open_project() is not Thread Safe
-                        self.app.open_project(self.filename)
-                    else:
-                        event.ignore()
-            else:
-                pass
+                        if self.filename.lower().rpartition('.')[-1] in self.app.grb_list:
+                            self.app.worker_task.emit({'fcn': self.app.open_gerber,
+                                                       'params': [self.filename]})
+                        else:
+                            event.ignore()
+
+                        if self.filename.lower().rpartition('.')[-1] in self.app.exc_list:
+                            self.app.worker_task.emit({'fcn': self.app.open_excellon,
+                                                       'params': [self.filename]})
+                        else:
+                            event.ignore()
+
+                        if self.filename.lower().rpartition('.')[-1] in self.app.gcode_list:
+                            self.app.worker_task.emit({'fcn': self.app.open_gcode,
+                                                       'params': [self.filename]})
+                        else:
+                            event.ignore()
+
+                        if self.filename.lower().rpartition('.')[-1] in self.app.svg_list:
+                            object_type = 'geometry'
+                            self.app.worker_task.emit({'fcn': self.app.import_svg,
+                                                       'params': [self.filename, object_type, None]})
+
+                        if self.filename.lower().rpartition('.')[-1] in self.app.dxf_list:
+                            object_type = 'geometry'
+                            self.app.worker_task.emit({'fcn': self.app.import_dxf,
+                                                       'params': [self.filename, object_type, None]})
+
+                        if self.filename.lower().rpartition('.')[-1] in self.app.prj_list:
+                            # self.app.open_project() is not Thread Safe
+                            self.app.open_project(self.filename)
+                        else:
+                            event.ignore()
+                else:
+                    pass
         else:
             event.ignore()
 
@@ -221,6 +253,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
     # will emit the name of the object that was just selected
     item_selected = QtCore.pyqtSignal(str)
+    update_list_signal = QtCore.pyqtSignal()
 
     root_item = None
     # app = None
@@ -296,6 +329,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
         self.click_modifier = None
 
+        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)
         self.promises.add(obj_name)
@@ -338,7 +373,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             self.app.ui.menuprojectcolor.setEnabled(False)
 
             for obj in self.get_selected():
-                if type(obj) == FlatCAMGerber:
+                if type(obj) == FlatCAMGerber or type(obj) == FlatCAMExcellon:
                     self.app.ui.menuprojectcolor.setEnabled(True)
 
                 if type(obj) != FlatCAMGeometry:
@@ -430,6 +465,18 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 return icon
             else:
                 return QtGui.QPixmap()
+        elif role == Qt.ToolTipRole:
+            try:
+                obj = index.internalPointer().obj
+            except AttributeError:
+                return None
+
+            if obj:
+                text = obj.options['name']
+                return text
+            else:
+                QtWidgets.QToolTip.hideText()
+                return None
         else:
             return None
 
@@ -459,7 +506,10 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                     self.app.inform.emit(_("Object renamed from <b>{old}</b> to <b>{new}</b>").format(old=old_name,
                                                                                                       new=new_name))
 
-        return True
+            self.dataChanged.emit(index, index)
+            return True
+        else:
+            return False
 
     def supportedDropActions(self):
         return Qt.MoveAction
@@ -471,15 +521,17 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             return Qt.ItemIsEnabled | default_flags
 
         # Prevent groups from selection
-        if not index.internalPointer().obj:
+        try:
+            if not index.internalPointer().obj:
+                return Qt.ItemIsEnabled
+            else:
+                return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable | \
+                       Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
+        except AttributeError:
             return Qt.ItemIsEnabled
-        else:
-            return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable | \
-                   Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
-
         # return QtWidgets.QAbstractItemModel.flags(self, index)
 
-    def append(self, obj, active=False):
+    def append(self, obj, active=False, to_index=None):
         FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
 
         name = obj.options["name"]
@@ -504,21 +556,27 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 name += "_1"
         obj.options["name"] = name
 
-        obj.set_ui(obj.ui_type(decimals=self.app.decimals))
+        obj.set_ui(obj.ui_type(app=self.app))
 
         # Required before appending (Qt MVC)
         group = self.group_items[obj.kind]
         group_index = self.index(group.row(), 0, QtCore.QModelIndex())
-        self.beginInsertRows(group_index, group.child_count(), group.child_count())
-
-        # Append new item
-        obj.item = TreeItem(None, self.icons[obj.kind], obj, group)
 
-        # Required after appending (Qt MVC)
-        self.endInsertRows()
+        if to_index is None:
+            self.beginInsertRows(group_index, group.child_count(), group.child_count())
+            # Append new item
+            obj.item = TreeItem(None, self.icons[obj.kind], obj, group)
+            # Required after appending (Qt MVC)
+            self.endInsertRows()
+        else:
+            self.beginInsertRows(group_index, to_index.row()-1, to_index.row()-1)
+            # Append new item
+            obj.item = TreeItem(None, self.icons[obj.kind], obj, group)
+            # Required after appending (Qt MVC)
+            self.endInsertRows()
 
         # Expand group
-        if group.child_count() is 1:
+        if group.child_count() == 1:
             self.view.setExpanded(group_index, True)
 
         self.app.should_we_save = True
@@ -645,6 +703,62 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             if not self.get_list():
                 self.app.ui.splitter.setSizes([0, 1])
 
+    def delete_by_name(self, name, select_project=True):
+        obj = self.get_by_name(name=name)
+        item = obj.item
+        group = self.group_items[obj.kind]
+
+        group_index = self.index(group.row(), 0, QtCore.QModelIndex())
+        item_index = self.index(item.row(), 0, group_index)
+
+        deleted = item_index.internalPointer()
+        group = deleted.parent_item
+
+        # some objects add a Tab on creation, close it here
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.widget(idx).objectName() == deleted.obj.options['name']:
+                self.app.ui.plot_tab_area.removeTab(idx)
+                break
+
+        # update the SHELL auto-completer model data
+        name = deleted.obj.options['name']
+        try:
+            self.app.myKeywords.remove(name)
+            self.app.shell._edit.set_model_data(self.app.myKeywords)
+            # this is not needed any more because now the code editor is created on demand
+            # self.app.ui.code_editor.set_model_data(self.app.myKeywords)
+        except Exception as e:
+            log.debug(
+                "delete_by_name() --> Could not remove the old object name from auto-completer model list. %s" % str(e))
+
+        self.app.object_status_changed.emit(deleted.obj, 'delete', name)
+
+        # ############ OBJECT DELETION FROM MODEL STARTS HERE ####################
+        self.beginRemoveRows(self.index(group.row(), 0, QtCore.QModelIndex()), deleted.row(), deleted.row())
+        group.remove_child(deleted)
+        # after deletion of object store the current list of objects into the self.app.all_objects_list
+        self.update_list_signal.emit()
+        self.endRemoveRows()
+        # ############ OBJECT DELETION FROM MODEL STOPS HERE ####################
+
+        if self.app.is_legacy is False:
+            self.app.plotcanvas.redraw()
+
+        if select_project:
+            # always go to the Project Tab after object deletion as it may be done with a shortcut key
+            self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
+
+        self.app.should_we_save = True
+
+        # decide if to show or hide the Notebook side of the screen
+        if self.app.defaults["global_project_autohide"] is True:
+            # hide the notebook if there are no objects in the collection
+            if not self.get_list():
+                self.app.ui.splitter.setSizes([0, 1])
+
+    def on_update_list_signal(self):
+        self.app.all_objects_list = self.get_list()
+
     def delete_all(self):
         FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
 
@@ -859,6 +973,11 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 raise
 
     def get_list(self):
+        """
+        Will return a list of all objects currently opened.
+
+        :return:
+        """
         obj_list = []
         for group in self.root_item.child_items:
             for item in group.child_items:

+ 78 - 4118
README.md

@@ -1,4127 +1,87 @@
-FlatCAM: 2D Computer-Aided PCB Manufacturing
+FlatCAM BETA (c) 2019 - by Marius Stanciu
+Based on FlatCAM: 
+2D Computer-Aided PCB Manufacturing by (c) 2014-2016 Juan Pablo Caram
 =================================================
 
-(c) 2014-2016 Juan Pablo Caram
-
 FlatCAM is a program for preparing CNC jobs for making PCBs on a CNC router.
 Among other things, it can take a Gerber file generated by your favorite PCB
 CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
-8.01.2019
-
-- working in NCC Tool
-- selected rows in the Tools Tables will stay colored in blue after loosing focus instead of the default gray
-- in NCC Tool the Tool name in the Parameters section will be the Tool ID in the Tool Table
-- added an exception catch in case the plotcanvas init failed for the OpenGL graphic engine and warn user about what happened
-
-7.01.2019
-
-- solved issue #368 - when using the Enable/Disable prj context menu entries the plotted status is not updated in the object properties
-- updates in NCC Tool
-
-6.01.2019
-
-- working on new NCC Tool
-
-2.01.2020
-
-- started to rework the NCC Tool GUI in preparation for adding a Tool DB feature
-- for auto-completer, now clicking an entry in the completer popup will select that entry and insert it
-- made available only for Linux and Windows (not OSX) the starting of the thread that checks if another instance of FlatCAM is already running at the launch of FLatCAM
-- modified Toggle Workspace function to work in the new Preferences UI configuration
-- cleaned the app from progress signal usage since it is not used anymore
-
-1.01.2020
-
-- fixed bug in NCC Tool: after trying to add a tool already in the Tool Table when trying to change the Tool Type the GUI does not change
-- final fix for app not quiting when running a script as argument, script that has the quit_flatcam Tcl command; fixed issue #360
-- fixed issue #363. The Tcl command drillcncjob does not create tool cut, does not allow creation of gcode, it forces the usage of dwell and dwelltime parameters
-- in NCC Tool I've added a warning so the user is warned that the NCC margin has to have a value of at least the tool diameter that is doing an iso_op job in the Tool Table
-- modified the Drillcncjob and Cncjob Tcl commands to be allowed to work without the 'dwell' and 'toolchange' arguments. If 'dwelltime' argument is present it will be assumed that the 'dwell' is True and the same for 'toolchangez' parameter, if present then 'toolchange' will be assumed to be True, else False
-- modified the extracut and multidepth parameters in Cncjob Tcl command like for dwell and toolchange
-- added ability for Tcl commands to have optional arguments with None value (meaning missing value). This case should be treated for each Tcl command in execute() method
-- fixed the Drillcncjob Tcl command by adding an custom self.options key "Tools_in_use" and build it's value, in case it does not exist, to make the toolchange command work
-- middle mouse click on closable tabs will close them
-
-30.12.2019
-
-- Buffer sub-tool in Transform Tool: added the possibility to apply a factor effectively scaling the aperture size thus the copper features sizes
-- in Transform Tool adjusted the GUI
-- fixed some decimals issues in NCC Tool, Paint Tool and Excellon Editor (they were still using the hardcoded values)
-- some small updates in the NCC Tool
-- changes in the Preferences UI for NCC and Paint Tool in Tool Dia entry field
-- fixed Tcl commands that use the overlap parameter to switch from fraction to percentage
-- in Transform Tool made sure that the buffer sub-tool parameters are better explained in tooltips
-- attempt to make TclCommand quit_flatcam work under Linux
-- some fixes in the NCC Tcl command (using the bool() method on some params)
-- another attempt to make TclCommand quit_flatcam work under Linux
-- another attempt to make TclCommand quit_flatcam work under Linux - use signal to call a hard exit when in Linux
-- TclCommand quit_flatcam work under Linux
-
-29.12.2019
-
-- the Apply button text in Preferences is now made red when changes were made and require to be applied
-- the Gerber UI is built only once now so the process is lighter on CPU
-- the Gerber apertures marking shapes storage is now built only once because the more are built the more sluggish is the interface
-- added a new function called by shortcut key combo CTRL+G when the current widget in Plot Area is an Code Editor. It will jump to the specified line in the text.
-- fixed a small bug where the app tried to hide a label that I've removed previously
-- in Paint Tool Preferences is allowed to add a list of initial tools separated by comma
-- in Geometry Paint Tool fixed the Overlap rate to work between 0 and 99.9999%
-
-28.12.2019
-
-- more updates to the Preferences window and in some other parts of the GUI
-- updated the translations (less Russian)
-- fixed a minor issue that when saving a project with CNCJob objects, the variable that holds the origin of the CNCJob object was not saved in the project. Added to the serializable objects also the exc_cnc_tools dictionary 
-- some changes in the File menu
-
-28.12.2019
-
-- updated all the translations files
-- fixed the big mouse cursor in OpenGL(3D) graphic mode to get the set color
-- fixed the cursor to have the set color and set cursor width in the Legacy(2D) graphic engine
-- in Legacy(2D) graphic mode fixed the cursor toggle when the big cursor is activated
-- in Legacy(2D) fixed big mouse cursor to snap to the grid
-- RELEASE 8.991
-
-27.12.2019
-
-- updated the POT file and the translation files for German, Spanish and French languages
-- fixed some typos
-
-26.12.2019
-
-- modified the ToolDB class and changed some strings
-- Preferences classes now have access to the App attributes through app.setup_obj_classes() method
-- moved app.setup_obj_classes() upper in the App.__init__()
-- added a new Preferences setting allowing to modify the mouse cursor color
-- remade the GUI in Preferences -> General grouping the settings in a more clear way
-- made available the Jump To function in Excellon Editor
-- added a clean_up() method in all the Editor Tools that need it, to be run when aborting using the ESC key
-- fixed an error in the Gerber parser; it did not took into consideration the aperture size declared before the beginning of a Gerber region. Detected for Gerber files generated by KiCAD 5.x
-- in Panelize Tool made sure that for Gerber objects if one of the apertures is without geometry then it is ignored
-- further modifications in Preferences -> General GUI
-- further modifications in Preferences -> General GUI - extended the changes
-- in Legacy(2D) graphic engine made to work the mouse color change
-- theme changing is no longer auto-reboot upon change; it require now to press a button
-- cleaned the Preferences classes and added the signals and signal slots in those classes, removing them from the main app class
-- each FlatCAM object found in Preferences has it's own set of controls for changing the colors
-- added a set of gray icons to be used when the theme is complete dark (for now it is useful only for MacOS with dark theme because at the moment the app is not styled to dark UI except the plot area)
-
-25.12.2019
-
-- fixed an issue in old default file detection and in saving the factory defaults file
-- in Preferences window removed the Import/Export Preferences buttons because they are redundant with the entries in the File -> Menu -> Backup. and added a button to Restore Defaults
-- when in Basic mode the Tool type of the tool in the Geometry UI Tool Table after isolating a Gerber object is automatically selected as 'C1'
-- let the multiprocessing Pool have as many processes as needed
-- added a new Preferences setting allowing a custom mouse line width (to make it thicker or thinner)
-- changed the extension of the Tool Database file to FlatDB for easy recognition (in the future double clicking such a file might import the new tools in the FC database)
-
-24.12.2019
-
-- edited some icons so they don't contain white background
-- fixed an incorrect usage of object in the app.select_objects() method
-- fixed a typo in ToolDB.on_tool_add()
-
-23.12.2019
-
-- some fixes in the Legacy(2D) graphic mode regarding the possibility of changing the color of the Gerber objects
-- added a method to darken the outline color for Gerber objects when they have the color set
-- when Printing as PDF Gerber objects now the rendered color is the print color
-- speed up the plotting in OpenGL(3D) graphic mode
-- speed up the color setting for Gerber object when using the OpenGL(3D) graphic mode
-- setting color for Gerber objects work on a selection of Gerber objects
-- ~~when the selection is changed in the Project Tree the selection shape on canvas is deleted~~
-- if an object is selected on Project Tree and it does not have the selection shape drawn, first click on canvas over it will draw the selection shape 
-- in Tool Transform added a new feature named 'Buffer'. For Geometry and Gerber objects will create (and replace) a geometry at a distance from the original geometry and for Excellon will adjust the Tool diameters
-- solved issue #355 - when the tool diameter field in the Edit → Preferences → Geometry → Geometry General → Tools → Tool dia is only one the app failed to read it
-- solved issue #356 - in Tools DB can not be added more than one tool if a translation is active 
-- some changes related to the fact that the geometry default tool diameter value can be comma separated string of tool diameters
-
-22.12.2019
-
-- added a new option for the Gerber objects: on the project context menu now can be chosen a color for the selected Gerber object
-- fixed issue in Gerber UI where a label was not hidden when in Basic mode
-- added the color parameters of the objects to the serializable attributes
-- fixed Gerber object color set for Legacy(2D) graphic engine; glitch on the OpenGL(3D) graphic engine
-- fixed the above mentioned glitch in the OpenGL(3D) graphic engine when an Gerber object has been set with a color
-
-21.12.2019
-
-- fixed a typo in Distance Tool
-
-20.12.2019
-
-- fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab)
-- Print function is now printing a PDF file for a selection of objects in the colors from canvas 
-- added an icon in the infobar that will show more clearly the status of the grid snapping
-- in Geometry Object UI (selected tab) when a tool type is changed from no matter what to V-shape, the cut_z value is saved and when the tool type is changed back to something different than V-shape, this saved cut-z value is restored
-- fixed re-cut length entry not staying disabled when the re-cut cb is not checked
-
-19.12.2019
-
-- in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid
-- in 2-Sided Tool fixed the Reset Tool button handler to reset the bounds value too; changed a string
-- added Preferences values for PDF margins when saving text in Code Editor as PDF
-- when clicking Cancel in Preferences now the values are reverted to what they used to be before opening Preferences tab and start changing values
-- starting to work to a general Print function; for now it will generate PDF files; currently it works only for one object not for a selection
-- added shortcut key CTRL+P for printing to PDF method
-
-18.12.2019
-
-- added new parameters to improve Gerber parsing
-- small optimizations in the Preferences UI
-- the Jump To function reference is now saving it's last used value
-- added the ability to use the Jump To method in the Gerber Editor
-- improved the loading of Config File by using the advanced code editor
-- fixed a bug in the new feature 'extra buffering'
-- fixed the creation of CNCJob objects out of multigeo Geometry objects (objects with multiple tools)
-- optimized the NCC Tool
-
-17.12.2019
-
-- more optimizations in NCC Tool
-- optimizations in Paint Tool
-- maximum range for Cut Z is now zero to deal with the situation when using V-shape with tip-dia same value with cut width
-- modified QValidator in FCDoubleSpinner() GUI element to allow entering the minus sign when the range maximum is set as 0.0; also for positive numbers allowed entering the symbol plus
-- made sure that if in Gerber UI the isolation is made with a V-Shape tool then the tool type is automatically updated on the generated Geometry Object
-- added ability to save the Source File as PDF (still have to adjust the page size)
-- fixed the generate_from_geometry_2() method to use the default values in case the parameters are None
-- added ability to save the Source File as PDF - fixed page size and added line breaks
-- more mods to generate_from_geometry_2() method
-- fixed bug saving the FlatCAM project saying the file is used by another application
-- fixed issue #347 - a Gerber generated by Sprint Layout with copper pour ON will not have rendered the copper pour
-
-16.12.2019
-
-- in Geometry Editor added support for Jump To function such as that it works within the Editor Tools themselves. For now it works only in absolute jumps
-- 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
-- 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
-- fixed the CNCJob geometry created with HPGL preprocessor
-- fixed GCode generated with HPGL preprocessor to output only integer coordinates
-- fixed the HPGL2 import parsing for absolute linear movements
-- fixed the line endings for setup_ubuntu.sh
-
-15.12.2019
-
-- fixed a bug that created a crash in special conditions; it's related to the QSettings in FlatCAMGui.py
-- added a script to remove the bad profiles from resource pictures. From here: https://stackoverflow.com/questions/22745076/libpng-warning-iccp-known-incorrect-srgb-profile/43415650, link mentioned by @camellan (Andrey Kultyapov)
-- prepared the application for usage of dark icons in case of using the dark theme
-- updated the languages
-- fixed a typo
-- fixed layout on first launch of the app
-- fixed some issues with the recent preparation for dark icons resource usage
-- added a new preprocessor file contributed by Daniel Friderich and added fixes for it
-- modified the export_gcode() method and the preprocessors such that the preprocessors now have the information if to include the gcode header
-- updated all the translation PO files and the POT file
-- RELEASE 8.99
-
-14.12.2019
-
-- finished the strings update in the Google-translated Spanish
-- finished the strings update in the Google-translated French
-
-13.12.2019
-
-- HPGL2 import: added support for circles, arcs and 3-point arcs. Everything works only for absolute coordinates.
-- removed the .plt extension from Gcode extensions
-- some strings updated; update on the Romanian translate
-- more strings updated; finished the Romanian translation update
-- some work in updating the Spanish Google-translation
-- small updates (Google Translate) in Russian and Brazilian-PT languages
-
-12.12.2019
-
-- finished the Calibration Tool
-- 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
-- some changes in the ObjectUI and for the Geometry UI
-- finished a very rough and limited HPGL2 file import 
-
-11.12.2019
-
-- started work in HPGL2 parser
-- some more work in Calibration Tool
-
-10.12.2019
-
-- small changes in the Geometry UI
-- now extracut option in the Geometry Object will recut as many points as many they are within the specified re-cut length
-- if extracut_length is zero then the extracut will cut up until the first point in path no matter what the distance is
-- in Gerber isolation, when selection mode is checked, now area selection works too
-- in CNCJob UI, now the CNCJob objects made out of Excellon objects will display their CNC tools (drill bits)
-- fixed a cumulative error when using the Tool Offset for Excellon objects
-- added the display of the real depth of cut (cut z + offset_z) for CNC tools made out of an Excellon object
-- for OpenGL graphic mode added a fit_view() execution on canvas initialization
-- fixed Excellon scaling the UI values
-- replaced the SpindleSpeed entry with a FCSpinner() GUI element; if speed is set to 0 it will amount to None
-
-9.12.2019 
-
-- updated the border for fit view on OpenGL graphic mode
-- Calibration Tool - added preferences values
-- Calibration Tool - more work on it
-- 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
-- Calibration Tool - finished the calibrated object creation method
-- updated the POT file
-- fixed an error in the German PO file
-- updated the languages PO files
-- some fixes on the app.jump_to() method
-- made sure that the ToolFilm will not start saving a file if there are no objects loaded
-- some fixes on the app.jump_to() method for the Legacy(2D) graphic mode
-
-8.12.2019
-
-- Calibrate Tool - rearranged the GUI
-- in Geometry UI made sure that the Label that points to the Tool parameters show clearly that those parameters apply only for the selected tool
-- fixed an small issue in Object UI
-- small fixes: selected object in Project used to ask twice for UI build; if scale factor in Object UI is 1 do nothing as there is no point in scaling with a factor of 1
-- in Geometry UI added a button that allow updating all the tools in the Tool Table with the current values in the UI form
-- updated Tcl commands to make use of either 0 or False for False value or 1 or True for True in case of a parameter with type Bool
-
-7.12.2019 
-
-- renamed Calibrate Excellon Tool to a simpler Calibrate Tool
-- Calibrate Tool - when generating verification GCode it will always load into an Editor from which it can be edited and/or saved. On save the editor will close.
-- updated the CNCJob and Drillcncjob Tcl Commands to use 0 and 1 as values for the parameters that are stated as of bool type, beside the normal keywords of False and True
-- Calibrate Tool - working on it
-
-6.12.2019
-
-- fixed the toggle_units() method so now the grid values are accurate to the decimal
-- cleaned up the Excellon parser and fixed some bugs (old and new); Excellon parser has it's own convert_units() method no longer inheriting from Geometry
-- in Excellon UI fixed bug that did not allow editing of the Offset Z parameter from the Tool table
-- in Properties Tool added new information's for the tools in the CNCjob objects
-- few bugs solved regarding the newly created empty objects
-- changed everywhere the name "postprocessor" with "preprocessor"
-- updated the preprocessor files in the toolchange section in order to avoid a graphical representation of travel lines glitch
-- fixed a GUI glitch in the Excellon tool table
-- added units to some of the parameters in the Properties Tool
-
-5.12.2019 
-
-- in NCC Tool, the new Geometry object that is created on copper clear now has the solid_geometry attribute where the geometry is stored not only in the obj.tools attribute
-- Copper Thieving Tool - added units label for the pattern plated area
-- Properties Tool - added a new parameter, the copper area which show the area of the copper features for the Gerber objects
-- Copper Thieving Tool - added a default value for the mask clearance when generating pattern plating mask
-- application wide change: introduced the precision parameters in Edit -> Preferences who will control how many decimals to use in the app parameters
-- changed the FCDoubleSpinner, FCSpinner and FCEntry GUI elements to allow passing an alignment value: left, right or center (not yet available in the app)
-- fixed the GUI of the Geometry Editor Tool Transform and adapted it to use the precision setting
-- updated Gerber Editor to use the precision setting and the Gerber Editor Transform Tool to use the FCDoubleSpinner GUI element
-- in Properties Tool added more information's regarding the Excellon tools, about travelled distance and job time; fixed issues when doing Properties on the CNCjob objects
-- TODO: I need to solve the mess in units conversion: it's too convoluted 
-
-4.12.2019 
-
-- made sure that if an older preferences file is detected then there are no errors and only the parameters that are currently active are loaded; the factory defaults file is deleted and recreated in the new format
-- in Preferences added a new button: 'Close' to close the Preferences window without saving
-- fixed bug in FCSpinner and FCDoubleSpinner GUI elements, that are now the main GUI element in FlatCAM, that made partial selection difficult
-- updated the Paint Tool in Geometry Editor to use the FCDoubleSpinner
-- added the possibility for suffix presence on the FCSpinner and FCDoubleSpinner GUI Elements
-- added the '%' symbol for overlap fields; I still need to divide the content by 100 to get the original (0 ... 1) value
-- fixed the overlap parameter all over the app to reflect the change to percentage
-- in Copper Thieving Tool added the display of the patterned plated area (approximate area) 
-- Copper Thieving Tool - updated the way plated area is calculated making it a bit more precise but still it is a bit bigger than the actual area
-- fixed the Copy Object function to copy also the source_file content
-- Copper Thieving Tool - when the clearance value for the pattern plating mask is negative it will be applied to the origin soldermask too
-- modified the GUI in all tools making the text of the buttons bold and adding a new button named Reset Tool which have to reset the tool GUI and variables (need to check all tools to see if happen)
-- when the Tool tab is in focus, clicking on canvas will no longer change the focus to Project tab
-- Copper Thieving Tool - when creating the pattern platting mask will make a new Gerber object with it
-- small fix in the GUI layout in Gerber Editor
-
-3.12.2019
-
-- in Preferences added an Apply button which apply the modified preferences but does not save to a file, minimizing the file IO operations; CTRL+S key combo does the Apply now.
-- updated some of the default values to metric, values that were missed previously
-- remade the Gerber Editor way to import an Gerber object into the editor in such a way to use the multiprocessing
-- various small fixes
-- fix for toggle grid lines updating canvas only after moving the mouse (hack, actually)
-- some changes in the UI layout in Cutout Tool
-- added some geometry parameters in Cutout Tool as a convenience, to be passed to the generated Geometry objects
-
-2.12.2019
-
-- fixed issue #343; updated the Image Tool
-- improvements in Importing SVG as Gerber - added an automatic source generation (it is not infallible)
-- a hack to import correctly the QRCode exported as SVG from FlatCAM
-- added 3 new tcl commands: export dxf, export excellon and export gerber
-- added a Cancel button in Tools DB when requesting to add a tool in the Geometry Tool Table
-- modified the default values for the METRIC system; the app now starts in the METRIC units since the majority of the world use the METRIC units system
-- small changes, updated the estimated release date
-- Tool Copper Thieving - added pattern plating mask generation feature
-
-28.11.2019
-
-- small fixes in NCC Tool and in the FlatCAMGeometry class
-
-27.11.2019
-
-- in Tool Film added the page size and page orientation in case of saving the film as PDF file
-- the application workspace has now a lot more options selectable in the Edit -> Preferences -> General -> GUI Preferences
-- updated the drawing of the workspace such that the application overall start time is improved and after first turn on of the workspace, toggling it will have no performance penalty
-- updated the workspace functions to work in Legacy(2D) graphic mode
-- adjusted the selection color transparency for the Legacy(2D) graphic mode because it was too transparent for the fill
-
-26.11.2019
-
-- updated the Film Tool to allow exporting PDF and PNG file (besides the SVG file)
-
-25.11.2019
-
-- 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
-- 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
-- in Gerber isolation added the option to selectively isolate only certain polygons - made it to work for Legacy(2D) graphic mode
-- remade the Paint Tool - single polygon painting; now it can single paint a list of polygons that are clicked onto (right click will start the actual painting)
-
-23.11.2019
-
-- 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
-- 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
-- added Robber Bar option to Copper Thieving Tool
-
-22.11.2019
-
-- Tool Fiducials - added GUI in Preferences and entries in self.defaults dict
-- 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
-
-21.11.2019
-
-- Tool Fiducials - finished the part with adding copper fiducials: manual and auto
-- Tool Fiducials - added choice of shapes: circular or non-standard cross
-- Tool Fiducials - finished the work on adding soldermask openings
-- Tool Fiducials - finished the tool
-- updated requirements.txt and setup_ubuntu.sh files
-
-20.11.2019
-
-- Tool Fiducials - added the GUI and the shortcut key
-- Tool Fiducials - updated the icon
-
-19.11.2019
-
-- removed the f-strings replacing them with the traditional string formatting due of not being supported by older versions of Python 3
-- fixed some TclCommands: MillDrills and OpenGerber
-- fixed bug in Tool Subtract that did not allow subtracting Gerber objects
-- starting to work on Tool Fiducials - created the file
-
-18.11.2019
-
-- finished the Dots and Squares options in the Copper Thieving Tool
-- working on the Lines option in Copper Thieving Tool
-- finished the Lines option in the Copper Thieving Tool; still have to add threading to maximize performance
-- finished Copper Thieving Tool improvements
-- working on the Calibrate Excellon Tool - remade the UI
-
-17.11.2019
-
-- optimized the storage of the Gerber mark shapes by making them one layer only
-- optimized the Distance Tool such that the distance utility geometry will be shown even when the mark shapes are plotted.
-- updated the make_freezed.py file to make sure that all the required files are included
-- updated the setup_ubuntu.sh to include the sudo command (courtesy of Krishna Torque on bitbucket)
-
-16.11.2019
-
-- fixed issue #341 that affected both postprocessors that have inlined feedrate: marlin and repetier. THe used feedrate was the Feedrate X-Y and instead had to be Feedrate Z.
-
-15.11.2019
-
-- added all the recognized extensions to the save dialog filters; latest extension used will be preselected next time a save operation occur
-- fixed issue #335. The FCDoubleSPinBox (and FCSpinBox) value was not used when the user entered data but just hovered away the mouse expecting the data to be already confirmed
-- converted setup_ubuntu.sh to Linux line endings
-
-14.11.2019
-
-- made sure that the 'default' postprocessor file is always loaded first such that this name is always first in the GUI comboboxes
-- added a class in GUIElements for a TextEdit box with line numbers and highlight
-
-13.11.2019
-
-- 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
-
-12.11.2019
-
-- added two new postprocessor files for ISEL CNC and for BERTA CNC
-- clicking on a FCTable GUI element empty space will also clear the focus now
-
-11.11.2019
-
-- in Tools Database added a contextual menu to add/copy/delete tool; CTRL+C, DEL keys work too; key T for adding a tool is now only partially working
-- in Tools Database made the status bar messages show when adding/copying/deleting tools in DB
-- changed all Except statements that were single to except Exception as recommended in some PEP
-- renamed the Copper Fill Tool to Copper Thieving Tool as this is a more appropriate name; started to add ability for more types of copper thieving besides solid
-- fixed some issues recently introduced in ParseSVG
-- updated POT file
-- fixed GUI in 2Sided Tool
-- extending the Copper Thieving Tool - wip
-
-9.11.2019
-
-- fixed a new bug that did not allow to open the FlatCAM Preferences files by doubleclick in Windows
-- added a new feature: Tools Database for Geometry objects; resolved issue #308
-- added tooltips for the Tools Database table headers and buttons
-
-8.11.2019
-
-- updated the make file for frozen executable
-
-7.11.2019
-
-- added the '.ngc' file extension to the GCode Save file dialog filter
-- made the 'M2' Gcode command footer optional, default is False (can be set using the TclCommand: set_sys cncjob_footer True)
-- added a setting in Preferences to force the GCode output to have the Windows line-endings even for non-Windows OS's
-
-6.11.2019
-
-- 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
-- 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
-
-5.11.2019
-
-- added a new setting named 'Allow Machinist Unsafe Settings' that will allow the Travel Z and Cut Z to take both positive and negative values
-- fixed some issues when editing a multigeo geometry
-
-4.11.2019
-
-- wip
-- getting rid of all the Options GUI and related functions as it is no longer supported
-- updated the UI in Geometry UI
-- optimized the order of the defaults storage declaration and the update of the Preferences GUI from the defaults
-- started to add a Tool Database
-
-3.11.2019
-
-- fixed the V-shape tool diameter calculation in NCC Tool
-- in NCC Tool made the new tool dia (circular type) a parameter in Preferences
-- fixed a small issue with clicking in a disabled FCDoubleSpinner or FCSpinner still doing a selection
-
-30.10.2019
-
-- converted SolderPaste Tool to usage of SpinBoxes; changed the SolderPaste Tool UI in Preferences too
-- fixed a bug in SolderPaste Tool that did not allow to view the GCode
-
-29.10.2019
-
-- a bug fix in Geometry Object
-- fixed some missing properties in Tool Calculators
-
-28.10.2019
-
-- in Tools: Paint, NCC and Copper Fill, when using the Area Selection, now the selected areas will stay drawn as markers until the user click RMB
-- in legacy2D graphic engine, adding an utility geometry no longer draw the older ones, overwriting them
-- fixed some issues in the Gerber Editor (Aperture add was double adding an aperture)
-- converted Gerber Editor to usage of SpinBoxes
-- working on the Calibrate Excellon Tool
-- converted Excellon Editor to usage of SpinBoxes
-- Calibrate Excellon Tool: working on self.calculate_factors() method
-
-27.10.2019
-
-- Copper Fill Tool: some PEP8 corrections
-
-26.10.2019
-
-- fixed an error in the FCDoubleSpinner class when FlatCAM is run on system with locale that use the comma as decimal separator
-
-25.10.2019
-
-- QRCode Tool: added ability to add negative QRCodes (perhaps they can be isolated on copper?); added a clear area surrounding the QRCode in case it is dropped on a copper pour (region); fixed the Gerber export
-- QRCode Tool: all parameters are hard-coded for now
-- small update
-- fixed imports in all TclCommands
-- fixed the requirements.txt and setup_ubuntu.sh files
-- QRCode Tool: change the plot method parameter
-- QRCode Tool: added ability to save the generated QRCode as SVG file or PNG file
-- QRCode Tool: added the feature to save the PNG file with transparent background
-- QRCode Tool: added GUI category in Preferences window
-- QRCode Tool: shortcut key for this tool is now ALT+Q while PDF import Tool was relegated to CTRL+Q combo key shortcut
-- added a new FlatCAM Tool: Copper Fill Tool. It will pour copper into a Gerber filling all empty space with copper, at a clearance distance of the Gerber features
-- Copper Fill Tool: added possibility to select between a bounding box rectangular or convex hull when the reference is the geometry of the source Gerber object
-- Copper Fill Tool: cleanup on not regular tool exit
-- Copper Fill Tool: added GUI category in Edit -> Preferences window
-- QRCode Tool: added a selection limit parameter to control the selection shape vs utility geo
-
-24.10.2019
-
-- added some placeholder texts in the TextBoxes.
-- working on QRCode Tool; added the utility geometry and initial functional layout
-- working on QRCode Tool; finished adding the QRCode geometry to the selected Gerber object and also finished adding the 'follow' geometry needed when exporting the Gerber object as a Gerber file in addition to the 'solid' geometry in the obj.apertures
-- working on QRCode Tool; finished offsetting the geometry both in apertures and in solid_geometry; updated the source_file of the source object
-
-23.10.2019
-
-- QRCode Tool - a SVG object is generated and plotted on screen having the QRCode data
-- fixed an import error in Distance Tool
-- fixed the Toggle Grid Lines functionality
-
-22.10.2019
-
-- working on the Calibrate Excellon Tool
-- finished the GUI layout for the Calibrate Excellon Tool
-- start working on QRCode Tool - not working yet
-- start working on QRCode Tool - searching for alternatives
-
-21.10.2019
-
-- the context menu for the Tabs in notebook and PlotTabArea is launched now on right mouse click on tabs themselves
-- fixed an error when trying to view the source file and there is no object selected
-- updated the Objects menu signals so whenever an object is (de)selected in the Project Tab, it's state will reflect the (un)checked state of the actions in the Object menu
-- fixed issue in Gerber Object UI of not updating the value of CutZ entry on TipDia or TipAngle entries change. Fixed issue #324
-
-18.10.2019
-
-- fixed a small bug in BETA status change
-- updated the About FlatCAM window
-- reverted change in tool dia being able to take only positive values in Gerber Object UI
-- started to work to a new tool: Calibrate Excellon Tool
-- solved the issue #329
-
-18.10.2019
-
-- finished the update on the Google translated Spanish translation.
-- updated the new objects icons for Gerber, Geometry and Excellon
-- small import problem fixed
-- RELEASE 8.98
-
-17.10.2019
-
-- fixed a bug in milling holes due of a message wrongly formatted
-- added an translator email address
-- finished the update on German Google translation. Part of it was corrected by Jens Karstedt
-- finished the update of the Romanian translation.
-- finished the Objects menu by adding the ability of actions to be checked so they will show the selected status of the objects and by adding to actions to (de)select all objects
-- fixed and optimized the click selection on canvas
-- fixed Gerber parsing for very simple Gerber files that have only one Polygon but many LPC zones
-- fixed SVG export; fix bug #327
-- finished the update on French Google translation.
-
-16.10.2019
-
-- small update to Romanian translation files
-
-15.10.2019
-
-- adjusted the layout in NCC Tool
-- fixed bug in Panelization Tool for which in case of Excellon objects, the panel kept a reference to the source object which created issues when moving or disabling/enabling the plots
-- cleaned up the module imports throughout the app (the TclCommands are not yet verified)
-- removed the styling on the comboboxes cellWidget's in the Tool Tables
-- replaced some of the icons that did not looked Ok on the dark theme
-- added a new toolbar button for the Copy object functionality
-- changed the Panelize tool icon
-- corrected some strings
-
-14.10.2019
-
-- modified the result highlight color in Check Rules Tool
-- added the Check Rules Tool parameters to the unit conversion list
-- converted more of the Preferences entries to FCDoubleSpinner and FCSpinner
-- converted all ObjectUI entries to FCDoubleSpinner and FCSpinner
-- updated the translation files (~ 89% translation level)
-- changed the splash screen as it seems that FlatCAM beta will never be more than beta
-- changed some of the signals from returnPressed to editingFinished due of now using the SpinBoxes
-- fixed an issue that caused the impossibility to load a GCode file that contained the % symbol even when was loaded in a regular way from the File menu
-- re-added the CNC tool diameter entry for the CNCjob object in Selected tab.FCSpinner
-- since the CNCjob geometry creation is only useful for graphical purposes and have no impact on the GCode creation I have removed the cascaded union on the GCode geometry therefore speeding up the Gcode display by many factors (perhaps hundreds of times faster)
-- added a secondary link in the bookmark manager
-- fixed the bookmark manager order of bookmark links; first two links are always protected from deletion or drag-and-drop to other positions
-- fixed a whole load of PyQT signal problems generated by recent changes to the usage of SpinBoxes; added a signal returnPressed for the FCSpinner and for FCDoubleSpinner
-- fixed issue in Paint Tool where the first added tool was expected to have a float diameter but it was a string
-- updated the translation files to the latest state in the app
-
-13.10.2019
-
-- fixed a bug in the Merge functions
-- fixed the Export PNG function when using the 2D legacy graphic engine
-- added a new capability to toggle the grid lines for both graphic engines: menu link in View and key shortcut combo ALT+G
-- changed the grid colors for 3D graphic engine when in Dark mode
-- enhanced the Tool Film adding the Film adjustments and added the GUI in Preferences
-- set the GUI layout in Preferences for a new category named Tools 2
-- added the Preferences for Check Rules Tool and for Optimal Tool and also updated the Film Tool to use the default settings in Preferences
-
-12.10.2019
-
-- fixed the Gerber Parser convert units unnecessary usage. The only units conversion should be done when creating the new object, after the parsing
-- more fixes in Rules Check Tool
-- optimized the Move Tool
-- added support for key-based panning in 3D graphic engine. Moving the mouse wheel while pressing the CTRL key will pan up-down and while pressing SHIFT key will pan left-right
-- fixed a bug in NCC Tool and start trying to make the App responsive while the NCC tool is run in a non-threaded way
-- fixed a GUI bug with the QMenuBar recently introduced
-
-11.10.2019
-
-- added a Bookmark Manager and a Bookmark menu in the Help Menu
-- added an initial support for rows drag and drop in FCTable in GUIElements; it crashes for CellWidgets for now, if CellWidgetsare in the table rows
-- fixed some issues in the Bookmark Manager
-- modified the Bookmark manager to be installed as a widget tab in Plot Area; fixed the drag & drop function for the table rows that have CellWidgets inside
-- marked in gray color the rows in the Bookmark Manager table that will populate the BookMark menu
-- made sure that only one instance of the BookmarkManager class is active at one time
-
-10.10.2019
-
-- fixed Tool Move to work only for objects that are selected but also plotted, therefore disabled objects will not be moved even if selected
-
-9.10.2019
-
-- updated the Rules Check Tool - solved some issues
-- made FCDoubleSpinner to use either comma or dot as a decimal separator
-- fixed the FCDoubleSpinner to only allow the amount of decimals already set with set_precision()
-- fixed ToolPanelize to use FCDoubleSpinner in some places
-
-8.10.2019
-
-- modified the FCSpinner and FCDoubleSpinner GUI elements such that the wheel event will not change the values inside unless there is a focus in the lineedit of the SpinBox
-- in Preferences General, Gerber, Geometry, Excellon, CNCJob sections made all the input fields of type SpinBox (where possible)
-- updated the Distance Tool utility geometry color to adapt to the dark theme canvas
-- Toggle Code Editor now works as expected even when the user is closing the Editor tab and not using the command Toggle Code Editor
-- more changes in Preferences GUI, replacing the FCEntries with Spinners
-- some small fixes in toggle units conversion
-- small GUI changes
-
-7.10.2019
-
-- fixed an conflict in a signal usage that was triggered by Tool SolderPaste when a new project was created
-- updated Optimal Tool to display both points coordinates that made a distance (and the minimum) not only the middle point (which is still the place where the jump happen)
-- 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
-- added inverted icons
-
-6.10.2019
-
-- remade the Mark area Tool in Gerber Editor to be able to clear the markings and also to delete the marked polygons (Gerber apertures)
-- working in adding to the Optimal Tool the rest of the distances found in the Gerber and the locations associated; added GUI
-- added display of the results for the Rules Check Tool in a formatted way
-- made the Rules Check Tool document window Read Only
-- made Excellon and Gerber classes from camlib into their own files in the flatcamParser folder
-- moved the ApertureMacro class from camlib to ParseGerber file
-- moved back the ApertureMacro class to camlib for now and made some import changes in the new ParseGerber and ParseExcellon classes
-- some changes to the tests - perhaps I will try adding a few tests in the future
-- changed the Jump To icon and reverted some changes to the parseGerber and ParseExcellon classes
-- updated Tool Optimal with display of all distances (and locations of the middle point between where they happen) found in the Gerber Object
-
-5.10.2019
-
-- remade the Tool Calculators to use the QSpinBox in order to simplify the user interaction and remove possible errors
-- remade: Tool Cutout, Tool 2Sided, Tool Image, Panelize Tool, NCC Tool, Paint Tool  to use the QSpinBox GUI elements
-- optimized the Transformation Tool both in GUI and in functionality and replaced the entries with QSpinBox
-- fixed an issue with the tool table context menu in Paint Tool
-- made some changes in the GUI in Paint Tool, NCC Tool and SolderPaste Tool
-- changed some of the icons; added attributions for icons source in the About FlatCAM window
-- added a new tool in the Geometry Editor named Explode which is the opposite of Union Tool: it will explode the polygons into lines
-
-4.10.2019
-
-- updated the Film Tool and added the ability to generate Punched Positive films (holes in the pads) when a Gerber file is the film's source. The punch holes source can be either an Excellon file or the pads center
-- optimized Rules Check Tool so it runs faster when doing Copper 2 Copper rule
-- small GUI changes in Optimal Tool and in Film Tool
-- some PEP8 corrections
-- some code annotations to make it easier to navigate in the FlatCAMGUI.py
-- fixed exit FullScreen with Escape key
-- added a new menu category in the MenuBar named 'Objects'. It will hold the objects found in the Project tab. Useful when working in FullScreen
-- disabled a log.debug in ObjectColection.get_by_name()
-- added a Toggle Notebook button named 'NB' in the QMenBar which toggle the notebook
-- in Gerber isolation section, the tool dia value is updated when changing from Circular to V-shape and reverse
-- in Tool Film, when punching holes in a positive film, if the resulting object geometry is the same as the source object geometry, the film will not ge generated
-- fixed a bug that when a Gerber object is edited and it has as solid_geometry a single Polygon, saving the result was failing due of len() function not working on a single Polygon
-- added the Distance Tool, Distance Min Tool, Jump To and Set Origin functions to the Edit Toolbar
-
-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
-
-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
-- 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
-- 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
-
-1.10.2019
-
-- fixed the FCSpinner and FCDoubleSpinner GUI elements to select all on first click and deselect on second click in the Spinbox LineEdit
-- for Gerber object in Selected Tab added ability to chose a V-Shape tool and therefore control the isolation better by adjusting the cut width of the isolation in function of the cut depth, tip width of the tool and the tip angle of the tool
-- when in Gerber UI is selected the V-Shape tool, all those parameters (tip dia, tip angle, tool_type = 'V' and cut Z) are transferred to the generated Geometry and prefilled in the Geoemtry UI
-- added a fix in the Gerber parser to work even when there is no information about zero suppression in the Gerber file
-- added new settings in Edit -> Preferences -> Gerber for Gerber Units and Gerber Zeros to be used as defaults in case that those informations are missing from the Gerber file
-- 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
-
-30.09.2019
-
-- modified the Distance Tool such that the number of decimals all over the tool is set in one place by the self.decimals
-- added a new tool named Minimum Distance Tool who will calculate the minimum distance between two objects; key shortcut: SHIFT + M
-- finished the Minimum Distance Tool in case of using it at the object level (not in Editors)
-- completed the Minimum Distance Tool by adding the usage in Editors
-- made the Minimum Distance Tool more precise for the Excellon Editor since in the Excellon Editor the holes shape are represented as a cross line but in reality they should be evaluated as circles
-- small change in the UI layout for Check Rules Tool by adding a new rule (Check trace size)
-- changed a tooltip in Optimal Tool
-- in Optimal Tool added display of how frequent that minimum distance is found
-- in Tool Distance and Tool Minimal Distance made the entry fields read-only
-- in Optimal Tool added the display of the locations where the minimum distance was detected
-- added support to use Multi Processing (multi core usage, not simple threading) in Rules Check Tool
-- in Rules Check Tool added the functionality for the following rules: Hole Size, Trace Size, Hole to Hole Clearance
-- in Rules Check Tool added the functionality for Copper to Copper Clearance
-- in Rules Check Tool added the functionality for Copper to Outline Clearance, Silk to Silk Clearance, Silk to Solder Mask Clearance, Silk to Outline Clearance, Minimum Solder Mask Sliver, Minimum Annular Ring
-- fixes to cover all possible situations for the Minimum Annular Ring Rule in Rules Check Tool
-- some fixes in Rules Check Tool and added a QSignal that is fired at the end of the job
-
-29.09.2019
-
-- work done for the GUI layout of the Rule Check Tool
-- setup signals in the Rules Check Tool GUI
-- changed the name of the Measurement Tool to Distance Tool. Moved it's location to the Edit Menu
-- added Angle parameter which is continuously updated to the Distance Tool
-
-28.09.2019
-
-- changed the icon for Open Script and reused it for the Check Rules Tool
-- added a new tool named "Optimal Tool" which will determine the minimum distance between the copper features for a Gerber object, in fact determining the maximum diameter for a isolation tool that can be used for a complete isolation
-- fixed the ToolMeasurement geometry not being displayed
-- fixed a bug in Excellon Editor that crashed the app when editing the first tool added automatically into a new black Excellon file
-- made sure that if the big mouse cursor is selected, the utility geometry in Excellon Editor has a thicker line width (2 pixels now) so it is visible over the geometry of the mouse cursor
-- fixed issue #319 where generating a CNCJob from a geometry made with NCC Tool made the app crash; also #328 which is the same
-- replaced in FlatCAM Tools and in FLatCAMObj.py  and in Editors all references to hardcoded decimals in string formats for tools with a variable declared in the __init__()
-- fixed a small bug that made app crash when the splash screen is disabled: it was trying to close it without being open
-
-27.09.2019
-
-- optimized the toggle axis command
-- added possibility of using a big mouse cursor or a small mouse cursor. The big mouse cursor is made from 2 infinite lines. This was implemented for both graphic engines
-- added ability to change the cursor size when the small mouse cursor is selected in Preferences -> General
-- removed the line that remove the spaces from the path parameter in the Tcl commands that open something (Gerber, Gcode, Excellon)
-- fixed issue with the old SysTray icon not hidden when the application is restarted programmatically
-- if an object is edited but the result is not saved, the app will reload the edited object UI and set the Selected tab as active
-- made the mouse cursor (big, small) change in real time for both graphic engines
-- started to work on a new FlatCAM tool: Rules Check
-- created the GUI for the Rule Check Tool
-- if there are (x, y) coordinates in the clipboard, when launching the "Jump to" function, those coordinates will be preloaded in the Dialog box.
-- when the combo SHIFT + LMB is executed there is no longer a deselection of objects
-- when the "Jump to" function is called, the mouse cursor (if active) will be moved to the new position and the screen position labels will be updated accordingly
-
-
-27.09.2019
-
-- RELEASE FlatCAM 8.97
-
-26.09.2019
-
-- added a Copy All button in the Code Editor, clicking this button will copy all text in the editor to the clipboard
-- added a 'Milling Type' radio button in Geometry Editor Preferences to contorl the type of geometry will be generated in the Geo Editor (for conventional milling or for the climb milling)
-- added the functionality to allow climb/conventional milling selection for the geometry created in the Geometry Editor
-- now any Geometry that is edited in Geometry editor will have coordinates ordered such that the resulting Gcode will allow the selected milling type in the 'Milling Type' radio button in Geometry Editor Preferences (which depends also of the spindle direction)
-- some strings update
-- French Google-translation at 100%
-- German Google-translation update to 100%
-- updated the other languages and the .POT file
-- changed some strings (that should not have been included for translation) and updated language files and the .POT file
-- fixed issue when rebooting from within in cx_freezed state (it issued a startup arg with the path to FlatCAM.exe but that triggered the last sys.exit(2) that I had in the App.args_at_startup())
-- modified the make_win script for the presence of MatPlotLib
-
-25.09.2019
-
-- French translation at 33%
-- fixed the 'Jump To' function to work in legacy graphic engine
-- in legacy graphic engine fixed the mouse cursor shape when grid snapping is ON, such that it fits with the shape from the OpenGL graphic engine
-- in legacy graphic engine fixed the axis toggle
-- French Google-translation at 48%
-
-24.09.2019
-
-- fixed the fullscreen method to show the application window in fullscreen wherever the mouse pointer it is therefore on the screen we are working on; before it was showing always on the primary screen
-- fixed setup_ubuntu.sh to include the matplotlib package required by the Legacy (2D) graphic engine
-- in legacy graphic engine, fixed issue where immediately after changing the mouse cursor snapping the mouse cursor shape was not updated
-- in legacy graphic engine, fixed issue where while zooming the mouse cursor shape was not updated
-- in legacy graphic engine, fixed issue where immediately after panning finished the mouse cursor shape was not updated
-- unfortunately the fix for issue where while zooming the mouse cursor shape was not updated braked something in way that Matplotlib work with PyQt5, therefore I removed it
-- fixed a bug in legacy graphic engine: when doing the self.app.collection.delete_all() in new_project an app crash occurred
-- implemented the Annotation change in CNCJob Selected Tab for the legacy graphic engine
-
-23.09.2019
-
-- in legacy graphic engine, fixed bug that made the old object disappear when a new object was loaded
-- in legacy graphic engine, fixed bug that crashed the app when creating a new project
-- in legacy graphic engine, fixed a bug that when deleting an object all objects where deleted
-- added a new TclCommand named "set_origin" which will set the origin for all loaded objects to zero if the -auto True argument is used and to a certain x,y location if the format is: set_origin 5,7
-- added a new TclCommand named "bounds" which will return a list of bounds values from a supplied list of objects names. For use in Tcl Scripts
-- updated strings in the translations and the .POT file
-- added the new keywords to the default keywords list
-- fixed the FullScreen option not working for the 3D graphic engine (due bug of Qt5 when OpenGL window is fullscreen) by creating a sort of fullscreen
-- added a final fix that allow full coverage of the screen in FullScreen in Windows and still the menus are working
-- optimized the Gerber mark shapes display
-- fixed a color format bug in Tool Move for 3D engine
-- made sure that when the Tool Move is used on a Gerber file with mark shapes active, those mark shapes are deleted before the actual move
-- in legacy graphic engine, fixed issue with Delete shortcut key trying to delete twice
-- 26% in Google-translated French translation and updated some strings too
-
-22.09.2019
-
-- fixed zoom directions legacy graphic engine (previous commit)
-- fixed display of MultiGeo geometries in legacy graphic engine
-- fixed Paint tool to work in legacy graphic engine
-- fixed CutOut Tool to work in legacy graphic engine
-- fixed display of distance labels and code optimizations in ToolPaint and NCC Tool
-- adjusted axis at startup for legacy graphic engine plotcanvas
-- when the graphic engine is changed in Edit -> Preferences -> General -> App Preferences, the application will restart
-- made hover shapes work in legacy graphic engine
-- fixed bug in display of the apertures marked in the Aperture table found in the Gerber Selected tab and through this made it to also work with the legacy graphic engine
-- fixed annotation in Mark Area Tool in Gerber Editor to work in legacy graphic engine
-- fixed the MultiColor plot option Gerber selected tab to work in legacy graphic engine
-- documented some methods in the ShapeCollectionLegacy class
-- updated the files: setup_ubuntu.sh and requirements.txt
-- some strings changed to be easier for translation
-- updated the .POT file and the translation files
-- updated and corrected the Romanian and Spanish translations
-- updated the .PO files for the rest of the translations, they need to be filled in.
-- fixed crash when trying to set a workspace in FlatCAM in the Legacy engine 2D mode by disabling this function for the case of 2D mode
-- fixed exception when trying to Fit View (shortcut key 'V') with no object loaded, in legacy graphic engine
-
-21.09.2019
-
-- fixed Measuring Tool in legacy graphic engine
-- fixed Gerber plotting in legacy graphic engine
-- fixed Geometry plotting in legacy graphic engine
-- fixed CNCJob and Excellon plotting in legacy graphic engine
-- in legacy graphic engine fixed the travel vs cut lines in CNCJob objects
-- final fix for key shortcuts with modifier in legacy graphic engine
-- refactored some of the code in the legacy graphic engine
-- fixed drawing of selection box when dragging mouse on screen and the selection shape drawing on the selected objects
-- fixed the moving drawing shape in Tool Move in legacy graphic engine
-- fixed moving geometry in Tool Measurement in legacy graphic engine
-- fixed Geometry Editor to work in legacy graphic engine
-- fixed Excellon Editor to work in legacy graphic engine
-- fixed Gerber Editor to work in legacy graphic engine
-- fixed NCC tool to work in legacy graphic engine
-
-20.09.2019
-
-- final fix for the --shellvar having spaces within the assigned value; now they are retained
-- legacy graphic engine - made the mouse events work (click, release, doubleclick, dragging)
-- legacy graphic engine - made the key events work (simple or with modifiers)
-- legacy graphic engine - made the mouse cursor work (enabled/disabled, position report); snapping is not moving the cursor yet
-- made the mouse cursor snap to the grid when grid snapping is active
-- changed the axis color to the one used in the OpenGL graphic engine
-- work on ShapeCollectionLegacy
-- fixed mouse cursor to work for all objects
-- fixed event signals to work in both graphic engines: 2D and 3D
-
-19.09.2019
-
-- made sure that if FlatCAM is registered with a file extension that it does not recognize it will exit
-- added some fixes in the the file extension detection
-- added some status messages for the Tcl script related methods
-- made sure that optionally, when a script is run then it is also loaded into the code editor
-- added control over the display of Sys Tray Icon in Edit -> Preferences -> General -> GUI Settings -> Sys Tray Icon checkbox
-- updated some of the default values to more reasonable ones
-- FlatCAM can be run in HEADLESS mode now. This mode can be selected by using the --headless=1 command line argument or by changing the line headless=False to True in config/configuration.txt file. In this mod the Sys Tray Icon menu will hold only the Run Scrip menu entry and Exit entry.
-- added a new TclCommand named quit_flatcam which will ... quit FlatCAM from Tcl Shell or from a script
-- fixed the command line argument --shellvar to work when there are spaces in the argument value
-- fixed bug in Gerber editor that did not allow to display all shapes after it encountered one shape without 'solid' geometry
-- fixed bug in Gerber Editor -> selection area handler where if some of the selected shapes did not had the 'solid' geometry will silently abort selection of further shapes
-- added new control in Edit -> Preferences -> General -> Gui Preferences -> Activity Icon. Will select a GIF from a selection, the one used to show that FlatCAM is working.
-- changed the script icon to a smaller one in the sys tray menu
-- fixed bug with losing the visibility of toolbars if at first startup the user tries to change something in the Preferences before doing a first save of Preferences
-- changed a bit the splash PNG file
-- moved all the GUI Preferences classes into it's own file flatcamGUI.PreferencesUI.py
-- changed the default method for Paint Tool to 'all'
-
-18.09.2019
-
-- added more functionality to the Extension registration with FLatCAM and added to the GUI in Edit -> Preferences -> Utilities
-- fixed the parsing of the Manufacturing files when double clicking them and they are registered with FlatCAM
-- fixed showing the GUI when some settings (maximized_GUI) are missing from QSettings
-- added sys tray menu
-- added possibility to edit the custom keywords used by the autocompleter (in Tcl Shell and in the Code Editor). It is done in the Edit -> Preferences -> Utilities
-- added a new setting in Edit -> Preferences -> General -> GUI Settings -> Textbox Font which control the font on the Textbox GUI elements
-- fixed issue with the sys tray icon not hiding after application close
-- added option to run a script from the context menu of the sys tray icon. Changed the color of the sys tray icon to a green one so it will be visible on light and dark themes
-
-17.09.2019
-
-- added more programmers that contributed to FlatCAM over the years, in the "About FlatCAM" -> Programmers window
-- fixed issue #315 where a script run with the --shellfile argument crashed the program if it contained a TclCommand New
-- added messages in the Splash Screen when running FlatCAM with arguments at startup
-- fixed issue #313 where TclCommand drillcncjob is spitting errors in Tcl Shell which should be ignored
-- fixed an bug where the pywrapcp name from Google OR-Tools is not defined; fix issue #316
-- if FlatCAM is started with the 'quit' or 'exit' as argument it will close immediately and it will close also another instance of FlatCAM that may be running
-- added a new command line parameter for FlatCAM named '--shellvars' which can load a text file with variables for Tcl Shell in the format: one variable assignment per line and looking like: 'a=3' without quotes
-- made --shellvars into --shellvar and make it only one list of commands passed to the Tcl. The list is separated by comma but without spaces. The variables are accessed in Tcl with the names shellvar_x where x is the index in the list of command comma separated values
-- fixed an issue in the TclShell that generated an exception IndexError which crashed the software
-- fixed the --shellvar and --shellfile FlatCAM arguments to work together but the --shellvar has precedence over --shellfile as it is most likely that whatever variable set by --shellvar will be used in the script file run by --shellfile
-
-16.09.2019
-
-- modified the TclCommand New so it will no longer close all tabs when called (it closed the Code Editor tab which may have been holding the code that run)
-- fixed the App.on_view_source() method for CNCJob objects: the Gcode will now contain the Prepend and Append code from the Edit -> Preferences -> CNCJob -> CNCJob Options
-- added a new parameter named 'muted' for the TclCommands: cncjob, drillcncjob and write_gcode. Setting it as -muted 1 will disable the error reporting in TCL Shell
-- some GUI optimizations
-- more GUI optimizations related to being part of the Advanced category or not
-- added possibility to change the positive SVG exported file color in Tool Film
-- fixed some issues recently introduced in the TclCommands CNCJob, DrillCNCJob and write_gcode; changed some parameters names
-- fixed issue in the Laser postprocessor where the laser was turned on as soon as the GCode started creating an unwanted cut up until the job start
-- added new links in Menu -> Help (Excellon, Gerber specifications and a Report Bug)
-- made the splashscreen to be showed on the current monitor on systems with multiple monitors
-- added a new entry in Menu -> View -> Redraw All which is doing what the name says: redraw all loaded objects
-- fixed issue where in TCl Shell the Windows paths were not understood due of backslash symbol understood as escape symbol instead of path separator
-- made sure that in for the TclCommand cncjob and for the drillcncjob if one of the args is stated but no value then the value used will be the default one
-- made available the TSA algorithm for drill path optimization when the used OS is 64bit. When used OS is 32bit the only available algorithm is TSA
-
-15.09.2019
-
-- refactored FlatCAMGeometry.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)
-- fixed UI showing before the initialization of FlatCAM is finished when the last state of GUI was maximized
-- finished updating the TclCommand cncjob to work for multi-geo Geometry objects with the parameters from the args
-- fixed the TclCommand cncjob to use the -outname parameter
-- added some more keywords in the data_model for auto-completer
-- fixed isolate TclCommand to use correctly the -outname parameter
-- added possibility to see the GCode when right clicking on the Project tab on a CNCJob object and then clicking View Source
-- added a new TclCommand named PlotObjects which will plot a list of FlatCAM objects
-- made that after opening an object in FlatCAM it is not automatically plotted. If the user wants to plot it can use the TclCommands PlotAll or PlotObjects
-- modified the TclCommands so that open files do not plot the opened files automatically
-- made all TclCommands not to be plotted automatically
-- made sure that all TclCommands are not threaded
-- added new TclCommands: NewExcellon, NewGerber
-- fixed the TclCommand open_project
-- added the outname parameter (and established an default name when outname not used) for the AlignDrillGrid and AlignDrill TclCommands
-- fixed Scripts repeating multiple time when the Code Editor is used. This repetition was correlated with multiple openings of the Code Editor window (especially after an error)
-- added the autocomplete keywords that can be changed to the defaults dictionary
-
-14.09.2019
-
-- more string changes
-- updated translation files
-- fixed a small bug
-- minor changes in the Code Editor GUI
-- minor changes in the 'FlatCAM About' GUI
-- added a new shortcut key F5 for doing the 'Plot All'
-- updated the google-translated Spanish translation strings
-- fixed the layouts to include toolbars breaks where it was needed
-- whenever the user changes the Excellon format values for loading files, the Export Excellon Format values will be updated
-- made optional the behavior of Excellon Export values following the values in the Excellon Loading section
-- updated the translations (except RU) and the POT file
-- added to the NonCopperClear.clear_copper() a parameter to be able to run it non-threaded
-
-13.09.2019
-
-- added control for simplification when loading a Gerber file in Preferences -> Gerber -> Gerber General -> Simplify
-- added some messages for the Edit -> Conversions -> Join methods() to make sure that there are at least 2 objects selected for join
-- added a grid layout in on_about()
-- upgraded the Script Editor to be able to run Tcl commands in batches
-- added some ToolTips for the buttons in the Code Editor
-- converted the big strings that hold the shortcut keys descriptions to smaller string to make translations easier
-- fixed some of the strings that were left in the old way
-- updated the POT file
-- updated Romanian language partially
-- added a new way to handle scripts with repeating Tcl commands
-- added new buttons in the Tools toolbar for running, opening and adding new scripts
-- finished the Romanian translation update and updated the POT file
-
-12.09.2019
-
-- small changes in the TclCommands: MillDrills, MillSlots, DrillCNCJob: the new parameter for tolerance is now named: diatol
-- cleaned up the 'About FlatCAM' window, started to give credits for the translation team
-- started to add an application splash screen
-- now, Excellon and Gerber edited objects will have the source_code updated and ready to be saved
-- the edited Gerber (or Excellon) object now is kept in the app after editing and the edited object is a new object
-- added a message to the splash screen
-- remade the splash screen to show multiple messages on app initialization
-- added a new splash image
-- added a control in Preferences -> General -> GUI Settings -> Splash Screen that control if the splash screen is shown at startup
-
-11.09.2019
-
-- added the Gerber code as source for the panelized object in Panelize Tool
-- whenever a Gerber file is deleted, the mark_shapes objects are deleted also
-- made faster the Gerber parser for the case of having a not valid geometry when loading a Gerber file without buffering
-- updated code in self.on_view_source() to make it more responsive
-- fixed the TclCommand MillHoles
-- changed the name of TclCommand MillHoles to MillDrills and added a new TclCommand named MillSlots
-- modified the MillDrills and MillSlots TclCommands to accept as parameter a list of tool diameters to be milled instead of tool indexes
-- fixed issue #302 where a copied object lost all the tools
-- modified the TclCommand DrillCncJob to have as parameter a list of tool diameters to be drilled instead of tool indexes
-- updated the Spanish translation (Google-translation)
-- added a new parameter in the TclCommands: DrillCNCJob, MillDrills, MillSlots named tol (from tolerance). If the diameters of the milled (drilled) dias are within the tolerance specified of the diameters in the Excellon object than those diameters will be processed. This is to help account for rounding errors when having units conversion
-
-10.09.2019
-
-- made isolation threaded
-- fixed a small typo in TclCommandCopperCLear
-- made changing the Plot kind in CNCJob selected tab, threaded
-- fixed an object used before declaring it in NCC Tool - Area option
-- added progress for the generation of Isolation geometry
-- added progress and possibility of graceful exit in Panel Tool
-- added graceful exit possibility when creating Isolation
-- changed the workers thread priority back to Normal
-- when disabling plots, if the selection shape is visible, it will be deleted
-- small changes in Tool Panel (eliminating some deepcopy() calls)
-- made sure that all the progress counters count to 100%
-
-9.09.2019
-
-- changed the triangulation type in VisPyVisuals for ShapeCollectionVisual class
-- added a setting in Preferences -> Gerber -> Gerber General named Buffering. If set to 'no' the Gerber objects load a lot more faster (perhaps 10 times faster than when set to 'full') but the visual look is not so great as all the aperture polygons can be seen
-- added for NCC Tool and Paint Tool a setting in the Preferences -> Tools --> (NCC Tool/ Paint Tool) that can set a progressive plotting (plot shapes as they are processed)
-- some fixes in Paint Tool when done over the Gerber objects in case that the progressive plotting is selected
-- some fixes in Gerber isolation in case that the progressive plotting is selected; added a 'Buffer solid geometry' button shown only when progressive plotting for Gerber object is selected. It will buffer the entire geometry of the object and plot it, in a threaded way.
-- modified FlatCAMObj.py file to the new string format that will allow easier translations
-- modified camlib.py, FlatCAMAPp.py and ObjectCollection.py files to the new string format that will allow easier translations
-- updated the POT file and the German language
-- fixed issue when loading unbuffered a Gerber file that has negative regions
-- fixed Panelize Tool to save the aperture geometries into the panel apertures. Also made the tool faster by removing the buffering at the end of the job
-- modified FlatCAMEditor's files to the new string format that will allow easier translations
-- updated POT file and the Romanian translation
-
-8.09.2019
-
-- added some documentation strings for methods in FlatCAMApp.App class
-- removed some @pyqtSlot() decorators as they interfere with the current way the program works
-
-7.09.2019
-
-- added a method to gracefully exit from threaded tasks and implemented it for the NCC Tool and for the Paint Tool
-- modified the on_about() function to reflect the reality in 2019 - FlatCAM it is an Open Source contributed software
-- remade the handlers for the Enable/Disable Project Tree context menu so they are threaded and activity is shown in the lower right corner of the main window
-- added to GUI new options for the Gerber object related to area subtraction
-- added new feature in the Gerber object isolation allowing for the isolation to avoid an area defined by another object (Gerber or Geometry)
-- all transformation functions show now the progress (rotate, mirror, scale, offset, skew)
-- made threaded the Offset and Scale operations found in the Selected tab of the object
-- corrected some issues and made Move Tool to show correctly when it is plotting and when it is offsetting the objects position
-- made Set Origin feature, threaded
-- updated German language translation files
-- separated the Plotting thread from the transformations threads
-
-6.09.2019
-
-- remade visibility threaded
-- reimplemented the thread listening for new FlatCAM process starting with args so it is no longer subclassed but using the moveToThread function
-- added percentage display for work done in NCC Tool
-- added percentage display for work done in Paint Tool
-- some fixes and prepared the activity monitor area to receive updated texts
-- added progress display in status bar for generating CNCJob from Excellon objects
-- added progress display in status bar for generating CNCJob from Geometry objects
-- modified all the FlatCAM tools strings to the new format in which the status is no longer included in the translated strings to make it easier for the future translations
-- more customization for the progress display in case of NCC Tool, Paint Tool and for the Gcode generation
-- updated POT file with the new strings
-- made the objects offset (therefore the Move Tool) show progress display
-
-5.09.2019
-
-- fixed issue with loading files at start-up
-- fixed issue with generating bounding box geometry for CNCJob objects
-- added some more infobar messages and log.debug
-- increased the priority for the worker tasks
-- hidden the configuration for G91 coordinates due of deciding to leave this development for another time; it require too much refactoring
-- added some messages for the G-code generation so the user know in which stage the process is
-
-4.09.2019
-
-- started to work on support for G91 in Gcode (relative coordinates)
-- added support for G91 coordinates
-- working in plotting the CNCjob generated with G91 coordinates
-
-3.09.2019
-
-- in NCC tool there is now a depth of cut parameter named 'Cut Z' which will dictate how deep the tool will enter into the PCB material
-- in NCC tool added possibility to choose between the type of tools to be used and when V-shape is used then the tool diameter is calculated from the desired depth of cut and from the V-tip parameters
-- small changes in NCC tool regarding the usage of the V-shape tool
-- fixed the isolation distance in NCC Tool for the tools with iso_op type
-- in NCC Tool now the Area adding is continuous until RMB is clicked (no key modifier is needed anymore)
-- fixed German language translation
-- in NCC Tool added a warning in case there are isolation tools and if those isolation's are interrupted by an area or a box
-- in Paint Tool made that the area selection is repeated until RMB click
-- in Paint Tool and NCC Tool fixed the RMB click detection when Area selection is used
-- finished the work on file extensions registration with FlatCAM. If the file extensions are deleted in the Preferences -> File Associations then those extensions are unregistered with FlatCAM
-- fixed bug in NCC Tools and in SolderPaste Tool if in Edit -> Preferences only one tool is entered
-- fixed bug in camblib.clear_polygon3() which caused that some copper clearing / paintings were not complete (some polygons were not processed) when the Straight Lines method was used
-- some changes in NCC Tools regarding of the clearing itself
-
-2.09.2019
-
-- fixed issue in NCC Tool when using area option
-- added formatting for some strings in the app strings, making the future translations easier
-- made changes in the Excellon Tools Table to make it more clear that the tools are selected in the # column and not in the Plot column
-- in Excellon and Gerber Selected tab made the Plot (mark) columns not selectable
-- some ToolTips were modified
-- in Properties Tool made threaded the calculation of convex_hull area and also made it to work for multi-geo objects
-- in NCC tool the type of tool that is used is transferred to the Geometry object
-- in NCC tool the type of isolation done with the tools selected as isolation tools can now be selected and it has also an Edit -> Preferences entry
-- in Properties Tool fixed the dimensions calculations (length, width, area) to work for multi-geo objects
-
-1.09.2019
-
-- fixed open handlers
-- fixed issue in NCC Tool where the tool table context menu could be installed multiple times
-- added new ability to create simple isolation's in the NCC Tool
-- fixed an issue when multi depth step is larger than the depth of cut
-
-27.08.2019
-
-- made FlatCAM so that whenever an associated file is double clicked, if there is an opened instance of FlatCAM, the file will be opened in the first instance without launching a new instance of FlatCAM. If FlatCAM is launched again it will spawn a new process (hopefully it will work when freezed).
-
-26.08.2019
-
-- added support for file associations with FlatCAM, for Windows
-
-25.08.2019
-
-- initial add of a new Tcl Command named CopperClear
-- remade the NCC Tool in preparation for the newly added TclCommand CopperClear
-- finished adding the TclCommandCopperClear that can be called with alias: 'ncc'
-- added new capability in NCC Tool when the reference object is of Gerber type and fixed some newly introduced errors
-- fixed issue #298. The changes in postprocessors done in Preferences dis not update the object UI layout as it was supposed to. The selection of Marlin postproc. did not unhidden the Feedrate Rapids entry.
-- fixed minor issues
-- fixed Tcl Command AddPolygon, AddPolyline
-- fixed Tcl Command CncJob
-- fixed crash due of Properties Tool trying to have a convex hull area on FlatCAMCNCJob objects which is not possible due of their nature
-- modified Tcl Command SubtractRectangle
-- fixed and modernized the Tcl Command Scale to be able to scale on X axis or on Y axis or on both and having as scale reference either the (0, 0) point or the minimum point of the bounding box or the center of the bounding box.
-- fixed and modernized the Tcl Command Skew
-
-24.08.2019
-
-- modified CutOut Tool so now the manual gaps adding will continue until the user is clicking the RMB
-- added ability to turn on/off the grid snapping and to jump to a location while in CutOut Tool manual gap adding action
-- made PlotCanvas class inherit from VisPy Canvas instead of creating an instance of it (work of JP)
-- fixed selection by dragging a selection shape in Geometry Editor
-- modified the Paint Tool. Now the Single Polygon and Area/Reference Object painting works with multiple tools too. The tools have to be selected in the Tool Table.
-- remade the TclCommand Paint to work in the new configuration of the the app (the painting functions are now in their own tool, Paint Tool)
-- fixed a bug in the Properties Tool
-- added a new TcL Command named Nregions who generate non-copper regions
-- added a new TclCommand named Bbox who generate a bounding box.
-
-23.08.2019
-
-- in Tool Cutout for the manual gaps, right mouse button click will exit from the action of adding gaps
-- in Tool Cutout tool I've added the possibility to create a cutout without bridge gaps; added the 'None' option in the Gaps combobox
-- in NCC Tool added ability to add multiple zones to clear when Area option is checked and the modifier key is pressed (either CTRL or SHIFT as set in Preferences). Right click of the mouse is an additional way to finish the job.
-- fixed a bug in Excellon Editor that made that the selection of drills is always cumulative
-- in Paint Tool added ability to add multiple zones to paint when Area option is checked and the modifier key is pressed (either CTRL or SHIFT as set in Preferences). Right click of the mouse is an additional way to finish the job.
-- in Paint Tool and NCC Tool, for the Area option, now mouse panning is allowed while adding areas to process
-- for all the FlatCAM tools launched from toolbar the behavior is modified: first click it will launch the tool; second click: if the Tool tab has focus it will close the tool but if another tab is selected, the tool will have focus
-- modified the NCC Tool and Paint Tool to work multiple times after first launch
-- fixed the issue with GUI entries content being deselected on right click in the box in order to copy the value
-- some changes in GUI tooltips
-- modified the way key modifiers are detected in Gerber Editor Selection class and in Excellon Editor Selection class
-- updated the translations
-- fixed aperture move in Gerber Editor
-- fixed drills/slots move in Excellon Editor
-- RELEASE 8.96
-
-22.08.2019
-
-- added ability to turn ON/OFF the detachable capability of the tabs in Notebook through a context menu activated by right mouse button click on the Notebook header
-- added ability to turn ON/OFF the detachable capability of the tabs in Plot Tab Area through a context menu activated by right mouse button click on the Notebook header
-- added possibility to turn application portable from the Edit -> Preferences -> General -> App. Preferences -> Portable checkbox
-- moved the canvas setup into it's own function and called it in the init() function
-- fixed the Buffer Tool in Geometry Editor; made the Buffer entry field a QDoubleSpinner and set the lower limit to zero.
-- fixed Tool Cutout so when the target Gerber is a single Polygon then the created manual geometry will follow the shape if shape is freeform
-- fixed TclCommandFollow command; an older function name was used who yielded wrong results
-- in Tool Cutout for the manual gaps, now the moving geometry that cuts gaps will orient itself to fit the angle of the cutout geometry
-
-21.08.2019
-
-- added feature in Paint Tool allowing the painting to be done on Gerber objects
-- added feature in Paint Tool to set how (and if) the tools are sorted
-- added Edit -> Preferences GUI entries for the above just added features
-- added new entry in Properties Tool which is the calculated Convex Hull Area (should give a more precise area for the irregular shapes than the box area)
-- added some more strings in Properties Tool for the translation
-- in NCC Tool added area selection feature
-- fixed bug in Excellon parser for the Excellon files that do not put the type of zero suppression they use in the file (like DipTrace eCAD)
-- fixed some issues introduced in NCC Tool
-
-20.08.2019
-
-- added ability to do copper clearing through NCC Tool on Geometry objects
-- replaced the layout from Grid to Form for the Reference objects comboboxes in Paint Tool and in NCC Tool
-
-19.08.2019
-
-- updated the Edit -> Preferences to include also the Gerber Editor complete Preferences
-- started to update the app strings to make it easier for future translations
-- fixed the POT file and the German translation
-- some mods in the Tool Sub
-- fixed bug in Tool Sub that created issues when toggling visibility of the plots
-- fixed the Spanish, Brazilian Portuguese and Romanian translations
-
-18.08.2019
-
-- made the exported preferences formatted therefore more easily read
-- projects at startup don't work in another thread so there is no multithreading if I want to double click an project and to load it
-- added messages in the application window title which show the progress in loading a project (which is not thread-safe therefore keeping the app from fully initialize until finished)
-- in NCC Tool added a new parameter (radio button) that offer the choice on the order of the tools both in tools table and in execution of engraving; added as a parameter also in Edit -> Preferences -> Tools -> NCC Tool
-- added possibility to drag & drop FlatCAM config files (*.FlatConfig) into the canvas to be opened into the application
-- added GUI in Paint tool in beginning to add Paint by external reference object 
-- finished adding in Paint Tool the usage of an external object to set the extent of th area painted. For simple shapes (single Polygon) the shape can be anything, for the rest will be a convex hull of the reference object
-- modified NCC tool so for simple objects (single Polygon) the external object used as reference can have any shape, for the other types of objects the copper cleared area will be the convex hull of the reference object
-- modified the strings of the app wherever they contained the char seq <b> </b> so it is not included in the translated string
-- updated the translation files for the modified strings (and for the newly added strings)
-- added ability to lock toolbars within the context menu that is popped up on any toolbars right mouse click. The value is saved in QSettings and it is persistent between application startup's.
-
-17.08.2019
-
-- added estimated time of routing for the CNCJob and added travelled distance parameter for geometry, too
-- fixed error when creating CNCJob due of having the annotations disabled from preferences but the plot2() function from camlib.CNCJob class still performed operations who yielded TypeError exceptions
-- coded a more accurate way to estimate the job time in CNCJob, taking into consideration if there is a usage of multi depth which generate more passes
-- another fix (final one) for the Exception generated by the annotations set not to show in Preferences
-- updated translations and changed version
-- fixed installer issue for the x64 version due of the used CX_FREEZE python package which was in unofficial version (obviously not ready to be used)
-- fixed bug in Geometry Editor, in disconnect_canvas_event_handlers() where I left some part of code without adding a try - except block which was required
-- moved the initialization of the FlatCAM editors after a read of the default values. If I don't do this then only at the first start of the application the Editors are not functional as the Editor objects are most likely destroyed
-- fixed bug in FlatCAM editors that caused the shapes to be drawn without resolution when the app units where INCH
-- modified the transformation functions in all classes in camlib.py and FlatCAMObj.py to work with empty geometries
-- RELEASE 8.95
-
-17.08.2019
-
-- updated the translations for the new strings
-- RELEASE 8.94
-
-16.08.2019
-
-- working in Excellon Editor to Tool Resize to consider the slots, too
-- fixed a weird error that created a crash in the following scenario: create a new excellon, edit it, add some drills/slots, delete it without saving, create a new excellon, try to edit and a crash is issued due of a wrapped C++ error
-- fixed bug selection in Excellon editor that caused not to select the corresponding row (tool dia) in the tool table when a selection rectangle selected an even number of geometric elements
-- updated the default values to more convenient ones
-- remade the enable/disable plots functions to work only where it needs to (no sense in disabling a plot already disabled)
-- made sure that if multi depth is choosed when creating GCode then if the multidepth is more than the depth of cut only one cut is made (to the depth of cut)
-- each CNCJob object has now it's own text_collection for the annotations which allow for the individual enabling and disabling of the annotations
-- added new menu category in File -> Backup with two menu entries that duplicate the functions of the export/import preferences buttons from the bottom of the Preferences window
-- in Excellon Editor fixed the display of the number of slots in the Tool Table after the resize done with the Resize tool
-- in Excellon Editor -> Resize tool, made sure that when the slot is resized, it's length remain the same, because the tool should influence only the 'thickness' of the slot. Since I don't know anything but the geometry and tool diameters (old and new), this is only an approximation and computationally intensive
-- in Excellon Editor -> remade the Tool edit made by editing the diameter values in the Tools Table to work for slots too
-- In Excellon Editor -> fixed bug that caused incorrect display of the relative coordinates in the status bar
-
-15.08.2019
-
-- added Edit -> Preferences GUI and storage for the Excellon Editor Add Slots
-- added a confirmation message for objects delete and a setting to activate it in Edit -> Preferences -> Global
-- merged pull request from Mike Smith which fix an application crash when attempting to open a not-a-FlatCAM-project file as project
-- merged pull request from Mike Smith that add support for a new SVG element: <use>
-- stored inside FlatCAM app the VisPy data files and at the first start the application will try to copy those files to the APPDATA (roaming) folder in case of running under Windows OS
-- created a configuration file in the root/config/configuration.txt with a configuration line for portability. Set portable to True to run the app as portable
-- working on the Slots Array in Excellon Editor - building the GUI
-- added a failsafe path to the source folder from which to copy the VisPy data
-- fixed the GUI for Slot Arrays in Excellon Editor
-- finished the Slot Array tool in Excellon Editor
-- added the key shortcut handlers for Add Slot and Add Slot Array tools in Excellon Editor
-- started to work on the Resize tool for the case of Excellon slots in Excellon Editor
-- final fix for the VisPy data files; the defaults files are saved to the Config folder when the app is set to be portable
-- added the Slot Type parameter for exporting Excellon in Edit -> Preferences -> Excellon -> Export Excellon. Now the Excellon object can be exported also with drilled slot command G85
-- fixed bug in Excellon export when there are no zero suppression (coordinates with decimals)
-
-14.08.2019
-
-- fixed the loading of Excellon with slots and the saving of edited Excellon object in regard of slots, in Excellon Editor
-- fixed the Delete tool, Select tool in Excellon Editor to work for Slots too
-- changes in the way the edited Excellon with added slots is saved
-- added more icons and cursor in Excellon Editor for Slots related functions
-- in Excellon Editor fixed the selection issue which in a certain step created a failure in the Copy and Move tools.
-- in Excellon Editor fixed the selection with key modifier pressed
-- edited the mouse cursors and saved them without included thumbnail in a bid to remove some CRC warnings made by libpng
-
-13.08.2019
-
-- added new option in ToolSub: the ability to close (or not) the resulting paths when using tool on Geometry objects. Added also a new category in the Edit -> Preferences -> Tools, the Substractor Tool Options
-- some PEP8 changes in FlatCAMApp.py
-- added new settings in Edit -> Preferences -> General for Notebook Font size (set font size for the items in Project Tree and for text in Selected Tab) and for canvas Axis font size. The values are stored in QSettings.
-- updated translations
-- fixed a bug in FCDoubleSpinner GUI element
-- added a new parameter in NCC tool named offset. If the offset is used then the copper clearing will finish to a set distance of the copper features
-- fixed bugs in Geometry Editor
-- added protection's against the 'bowtie' geometries for Subtract Tool in Geometry Editor
-- added all the tools from Geometry Editor to the the contextual menu
-- fixed bug in Add Text Tool in Geometry Editor that gave error when clicking to place text without having text in the box
-- added all the tools from Gerber Editor to the the contextual menu
-- added the menu entry "Edit" in the Project contextual menu for Gerber objects
-- started to work in adding slots and slots array in Excellon Editor
-- in FCSlot finished the utility geometry and the GUI for it
-
-12.08.2019
-
-- 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)
-- 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
-
-11.08.2019
-
-- small changes regarding the Project Title
-- trying to fix reported bugs
-- made sure that the annotations are deleted when the object that contain them is deleted
-- fixed issue where the annotations for all the CNCJob objects are toggled together whenever the ones for an single object are toggled
-- optimizations in GeoEditor
-- updated translations
-
-10.08.2019
-
-- added new feature in NCC Tool: now another object can be used as reference for the area extent to be cleared of copper
-- fixed issue in the latest feature in NCC Tool: now it works also with reference objects made out of LineStrings (tool 'Path' in Geometry Editor)
-- translation files updated for the new strings (Google Translate)
-- RELEASE 8.93
-
-9.08.2019
-
-- added Exception handing for the case when the user is trying to save & overwrite a file already opened in another file
-- finished added 'Area' type of Paint in Paint Tool
-- fixed bug that created a choppy geometry for CNCJob when working in INCH
-- fixed bug that did not asked the user to save the preferences after importing a new set of preferences, after the user is trying to close the Preferences tab window
-
-7.08.2019
-
-- replaced setFixedWidth calls with setMinimumWidth
-- recoded the camlib.Geometry.isolation_geometry() function
-- started to work on Paint Area in Paint Tool
-
-6.08.2019
-
-- fixed bug that crashed the app after creating a new geometry, if a new object is loaded and the new geometry is deleted and then trying to select the just loaded new object
-- made some GUI elements in Edit -> Preferences to have a minimum width as opposed to the previous fixed one
-- fixed issue in the isolation function, if the isolation can't be done there will be generated no Geometry object 
-- some minor UI changes
-- strings added and translations updated
-
-5.08.2019
-
-- made sure that if using an negative Gerber isolation diameter, the resulting Geometry object will use a tool with positive diameter
-- fixed bug that when isolating a Gerber file made out of a single polygon, an RecursionException was issued together with inability to create tbe isolation
-- when applying a new language if there are any changes in the current project, the app will offer to save the project before the reboot
-
-3.08.2019
-
-- added project name to the window title
-- fulfilled request: When saving a CNC file, if the file name is changed in the OS window, the new name does appear in the “Selected” (in name) and “Project” tabs (in cnc_job)
-- solved bug such that the app is not crashing when some apertures in the Gerber file have no geometry. More than that, now the apertures that have geometry elements are bolded as opposed to the ones without geometry for which the text is unbolded
-- merged a pull request with language changes for Russian translate
-- updated the other translations
-
-31.07.2019
-
-- changed the order of the menu entries in the FIle -> Open ...
-- organized the list of recent files so the Project entries are to the top and separated from the other types of file
-- work on identification of changes in Preferences tab
-- added categories names for the recent files
-- added a detection if any values are changed in the Edit -> Preferences window and on close it will ask the user if he wants to save the changes or not
-- created a new menu entry in the File menu named Recent projects that will hold the recent projects and the previous "Recent files" will hold only the previous loaded files
-- updated all translations for the new strings
-- fixed bug recently introduced that when changing the units in the Edit -> Preferences it did not converted the values
-- fixed another bug that when selecting an Excellon object after disabling it it crashed the app
-- RELEASE 8.92
-
-30.07.2019
-
-- fixed bug that crashed the software when trying to edit a GUI value in Geometry selected tab without having a tool in the Tools Table
-- fixed bug that crashed the app when trying to add a tool without a tool diameter value
-- Spanish Google translation at 77%
-- changed the Disable plots menu entry in the context menu, into a Toggle Visibility menu entry
-- Spanish Google translation 100% but two strings (big ones) - needs review
-- added two more strings to translation strings (due of German language)
-- completed the Russian translation using the Google and Yandex translation engines (minus two big strings) - needs review
-
-28.07.2019
-
-- fixed issue with not using the current units in the tool tables after unit conversion
-- after unit conversion from Preferences, the default values are automatically saved by the app
-- in Basic mode, the tool type column is no longer hidden as it may create issues when using an painted geometry
-- some PEP8 clean-up in FlatCAMGui.py
-- fixed Panelize Tool to do panelization for multiple passes type of geometry that comes out of the isolation done with multiple passes
-
-20.07.2019
-
-- updated the CutOut tool so it will work on single PCB Gerbers or on PCB panel Gerbers
-- updated languages
-- 70% progress in Spanish Google translation
-
-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
-- some changes in the widgets used in the Selected tab for Geometry object
-- some PEP8 cleanup in FlatCAMObj.py
-- updated languages
-- 60% progress in Spanish Google translation
-
-17.07.2019
-
-- added some more strings to the translatable ones, especially the radio button labels
-- updated the .POT file and the available translations
-- 51% progress in Spanish Google translation
-- version date change
-
-16.07.2019
-
-- PEP8 correction in flatcamTools
-- merged the Brazilian-portuguese language from a pull request made by Carlos Stein
-- more PEP8 corrections
-
-15.07.2019
-
-- some PEP8 corrections
-
-13.07.2019
-
-- fixed a possible issue in Gerber Object class
-- added a new tool in Gerber Editor: Mark Area Tool. It will mark the polygons in a edited Gerber object with areas within a defined range, allowing to delete some of the not necessary  copper features
-- added new menu links in the Gerber Editor menu for Eraser Tool and Mark Area Tool
-- added key shortcuts for Eraser Tool (CTRL+E) and Mark Area Tool (ALT+A) and updated the shortcuts list
-
-9.07.2019
-
-- some changes in the app.on_togle_units() to make sure we don't try to convert empty parameters which may cause crashes on FlatCAM units change
-- updated setup_ubuntu.sh file
-- made sure to import certain libraries in some of the FlatCAM files and not to rely on chained imports
-
-8.07.2019
-
-- fixed bug that allowed empty tool in the tools generated in Geometry object
-- fixed bug in Tool Cutout that did not allow the transfer of used cutout tool diameter to the cutout geometry object
-
-5.07.2019
-
-- fixed bug in CutOut Tool
-- some other bug in CutOut tool fixed
-
-1.07.2019
-
-- Spanish translation at 36%
-
-28.06.2019
-
-- Spanish translation (Google Translate) at 21%
-
-27.06.2019
-
-- added new translation: Spanish. Finished 10%
-
-23.06.2019
-
-- fixes issues with units conversion when the tool diameters are a list of comma separated values (NCC Tool, SolderPaste Tool and Geometry Object)
-- fixed a "typo" kind of bug in SolderPaste Tool
-- RELEASE 8.919
-
-22.06.2019
-
-- some GUI layout optimizations in Edit -> Preferences
-- added the possibility for multiple tool diameters in the Edit -> Preferences -> Geometry -> Geometry General -> Tool dia separated by comma
-- fixed scaling for the multiple tool diameters in Edit -> Preferences -> Geometry -> Geometry General -> Tool dia, for NCC tools more than 2 and for Solderpaste nozzles more than 2
-- fixed bug in CNCJob where the CNC Tools table will show always only 2 decimals for Tool diameters regardless of the current measuring units
-- made the tools diameters decimals in case of INCH FlatCAM units to be 4 instead of 3
-- fixed bug in updating Grid values whenever toggling the FlatCAM units and the X, Y Grid values are linked, bugs which caused the Y value to be scaled incorrectly
-- set the decimals for Grid values to be set to 6 if the units of FlatCAM is INCH and to set to 4 if FlatCAM units are METRIC
-- updated translations
-- updated the Russian translation from 51% complete to 69% complete using the Yandex translation engine
-- fixed recently introduced bug in milling drills/slots functions
-- moved Substract Tool from Menu -> Edit -> Conversions to Menu -> Tool
-- fixed bug in Gerber isolation (Geometry expects now a value in string format and not float)
-- fixed bug in Paint tool: now it is possible to paint geometry generated by External Isolation (or Internal isolation)
-- fixed bug in editing a multigeo Geometry object if previously a tool was deleted
-- optimized the toggle of annotations; now there is no need to replot the entire CNCJob object too on toggling of the annotations
-- on toggling off the plot visibility the annotations are turned off too
-- updated translations; Russian translation at 76% (using Yandex translator engine - needs verification by a native speaker of Russian)
-
-20.06.2019
-
-- fixed Scale and Buffer Tool in Gerber Editor
-- fixed Editor Transform Tool in Gerber Editor
-- added a message in the status bar when copying coordinates to clipboard with SHIFT + LMB click combo
-- languages update
-
-19.06.2019
-
-- milling an Excellon file (holes and/or slots) will now transfer the chosen milling bit diameter to the resulting Geometry object
-
-17.06.2019
-
-- fixed bug where for Geometry objects after a successful object rename done in the Object collection view (Project tab), deselect the object and reselect it and then in the Selected tab the name is not the new one but the old one
-- for Geometry objects, adding a new tool to the Tools table after a successful rename will now store the new name in the tool data
-
-15.06.2019
-
-- fixed bug in Gerber parser that made the Gerber files generated by Altium Designer 18 not to be loaded
-- fixed bug in Gerber editor - on multiple edits on the same object, the aperture size and dims were continuously multiplied due of the file units not being updated
-- restored the FlatCAMObj.visible() to a non-threaded default
-
-11.06.2019
-
-- fixed the Edit -> Conversion -> Join ... functions (merge() functions)
-- updated translations
-- Russian translate by @camellan is not finished yet
-- some PEP8 cleanup in camlib.py
-- RELEASE 8.918
-
-9.06.2019
-
-- updated translations
-- fixed the the labels for shortcut keys for zoom in and zoom out both in the Menu links and in the Shortcut list
-- made sure the zoom functions use the global_zoom_ratio parameter from App.self.defaults dictionary.
-- some PEP8 cleanup
-
-8.06.2019
-
-- make sure that the annotation shapes are deleted on creation of a new project
-- added folder for the Russian translation
-- made sure that visibility for TextGroup is set only if index is not None in VisPyVisuals.TextGroup.visible() setter
-
-7.06.2019
-
-- 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
-- updated translations
-
-5.06.2019
-
-- updated translations
-- some layout changes in Edit -> Preferences such that the German translation (longer words than English) to fit correctly
-- after editing an parameter the focus is lost so the user knows that something happened
-
-4.06.2019
-
-- PEP8 updates in FlatCAMExcEditor.py
-- added the Excellon Editor parameters to the Edit -> Preferences -> Excellon GUI
-- fixed a small bug in Excellon Editor
-- PEP8 cleanup in FlatCAMGui
-- finished adding the Excellon Editor parameters into the app logic and added a selection limit within Excellon Editor just like in the other editors
-
-3.06.2019
-
-- TclCommand Geocutout is now creating a new geometry object when working on a geometry, preserving also the origin object
-- added a new parameter in Edit -> Preferences -> CNCJob named Annotation Color; it controls the color of the font used for annotations
-- added a new parameter in Edit -> Preferences -> CNCJob named Annotation Size; it controls the size of the font used for annotations
-- made visibility change threaded in FlatCAMObj()
-
-2.06.2019
-
-- fixed issue with geometry name not being updated immediately after change while doing geocutout TclCommand
-- some changes to enable/disable project context menu entry handlers
-
-1.06.2019
-
-- fixed text annotation for CNC job so there are no overlapping numbers when 2 lines meet on the same point
-- fixed issue in CNC job plotting where some of the isolation polygons are painted incorrectly
-- fixed issue in CNCJob where the set circle steps is not used 
-
-31.05.2019
-
-- added the possibility to display text annotation for the CNC travel lines. The setting is both in Preferences and in the CNC object properties
-
-30.05.2019
-
-- editing a multi geometry will no longer pop-up a Tcl window
-- solved issue #292 where a new geometry renamed with many underscores failed to store the name in a saved project
-- the name for the saved projects are updated to the current time and not to the time of the app startup
-- some PEP8 changes related to comments starting with only one '#' symbol
-- more PEP8 cleanup
-- solved issue where after the opening of an object the file path is not saved for further open operations
-
-24.05.2019
-
-- added a toggle Grid button to the canvas context menu in the Grids submenu
-- added a toggle left panel button to the canvas context menu
-
-23.05.2019
-
-- fixed bug in Gerber editor FCDisk and FCSemiDisc that the resulting geometry was not stored into the '0' aperture where all the solids are stored
-- fixed minor issue in Gerber Editor where apertures were included in the saved object even if there was no geometric data for that aperture
-- some PEP8 cleanup in FlatCAMApp.py
-
-22.05.2019
-
-- Geo Editor - added a new editor tool, Eraser
-- some PEP8 cleanup of the Geo Editor
-- fixed some selection issues in the new tool Eraser in Geometry Editor
-- updated the translation files
-- RELEASE 8.917
-
-21.05.2019
-
-- added the file extension .ncd to the Excellon file extension list
-- solved parsing issue for Excellon files generated by older Eagle versions (v6.x)
-- Gerber Editor: finished a new tool: Eraser. It will erase certain parts of Gerber geometries having the shape of a selected shape.
-
-20.05.2019
-
-- more PEP8 changes in Gerber editor
-- Gerber Editor - started to work on a new editor tool: Eraser
-
-19.05.2019
-
-- fixed the Circle Steps parameter for both Gerber and Geometry objects not being applied and instead the app internal defaults were used.
-- fixed the Tcl command Geocutout issue that gave an error when using the 4 or 8 value for gaps parameter
-- made wider the '#' column for Apertures Table for Gerber Object and for Gerber Editor; in this way numbers with 3 digits can be seen
-- PEP8 corrections in FlatCAMGrbEditor.py
-- added a selection limit parameter for Geometry Editor
-- added entries in Edit -> Preferences for the new parameter Selection limit for both the Gerber and Geometry Editors.
-- set the buttons in the lower part of the Preferences Window to have a preferred minimum width instead of fixed width
-- updated the translation files
-
-18.05.2019
-
-- added a new toggle option in Edit -> Preferences -> General Tab -> App Preferences -> "Open" Behavior. It controls which path is used when opening a new file. If checked the last saved path is used when saving files and the last opened path is used when opening files. If unchecked then the path for the last action (either open or save) is used.
-- fixed App.convert_any2gerber to work with the new Gerber apertures data structure
-- fixed Tool Sub to work with the new Gerber apertures data structure
-- fixed Tool PDF to work with the new Gerber apertures data structure
-
-17.05.2019
-
-- remade the Tool Cutout to work on panels
-- remade the Tool Cutout such that on multiple applications on the same object it will yield the same result
-- fixed an issue in the remade Cutout Tool where when applied on a single Gerber object, the Freeform Cutout produced no cutout Geometry object
-- remade the Properties Tool such that it works with the new Gerber data structure in the obj.apertures. Also changed the view for the Gerber object in Properties
-- fixed issue with false warning that the Gerber object has no geometry after an empty Gerber was edited and added geometry elements
-
-16.05.2019
-
-- Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy
-- added a new parameter/feature: now the spindle can work in clockwise mode (CW) or counter clockwise mode (CCW)
-
-15.05.2019
-
-- rewrited the Gerber Parser in camlib - success
-- moved the self.apertures[aperture]['geometry'] processing for clear_geometry (geometry made with Gerber LPC command) in Gerber Editor
-- Gerber Editor: fixed the Poligonize Tool to work with new geometric structure and took care of a special case
-- Gerber Export is fixed to work with the new Gerber object data structure and it now works also for Gerber objects edited in Gerber Editor
-- Gerber Editor: fixed units conversion for obj.apertures keys that require it
-- camlib Gerber parser - made sure that we don't loose goemetry in regions
-- Gerber Editor - made sure that for some tools the added geometry is clean (the coordinates are non repeating)
-- covered some possible issues in Gerber Export
-
-12.05.2019
-
-- some modifications to ToolCutout
-
-11.05.2019
-
-- fixed issue in camlib.CNCjob.generate_from_excellon_by_tool() in the drill path optimization algorithm selection when selecting the MH algorithm. The new API's for Google OR-tools required some changes and also the time parameter can be now just an integer therefore I modified the GUI
-- made the Feedrate Rapids parameter to depend on the type of postprocessor choosed. It will be showed only for a postprocessor which the name contain 'marlin' and for any postprocessor's that have 'custom' in the name
-- fixed the camlib.Gerber functions of mirror, scale, offset, skew and rotate to work with the new data structure for apertures geometry
-- fixed Gerber Editor selection to work with the new Gerber data structure in self.apertures
-- fixed Gerber Editor FCPad class to work with the new Gerber data structure in self.apertures
-- fixed camlib.Gerber issues related to what happen after parsing rectangular apertures 
-- wip in camblib.Gerber
-- completely converted the Gerber editor to the new data structure
-- Gerber Editor: added a threshold limit for how many elements a move selection can have. If above the threshold only a bounding box Poly will be painted on canvas as utility geometry.
-
-10.05.2019
-
-- Gerber Editor - working in conversion to the new data format
-- made sure that only units toggle done in Edit -> Preferences will toggle the data in Preferences. The menu entry Edit -> Toggle Units and the shortcut key 'Q' will change only the display units in the app
-- optimized Transform tool
-- RELEASE 8.916
-
-9.05.2019
-
-- reworked the Gerber parser
-
-8.05.2019
-
-- added zoom fit for Set Origin command
-- added move action for solid_geometry stored in the gerber_obj.apertures
-- fixed camlib.Gerber skew, rotate, offset, mirror functions to work for geometry stored in the Gerber apertures
-- fixed Gerber Editor follow_geometry reconstruction
-- Geometry Editor: made the tool to be able to continuously move until the tool is exited either by ESC key or by right mouse button click
-- Geometry Editor Move Tool: if no shape is selected when triggering this tool, now it is possible to make the selection inside the tool
-- Gerber editor Move Tool: fixed a bug that repeated the plotting function unnecessarily 
-- Gerber editor Move Tool: if no shape is selected the tool will exit
-
-7.05.2019
-
-- remade the Tool Panelize GUI
-- work in Gerber Export: finished the header export
-- fixed the Gerber Object and Gerber Editor Apertures Table to not show extra rows when there are aperture macros in the object
-- work in Gerber Export: finished the body export but have some errors with clear geometry (LPC)
-- Gerber Export - finished
-
-6.05.2019
-
-- made units change from shortcut key 'Q' not to affect the preferences
-- made units change from Edit -> Toggle Units not to affect the preferences
-- remade the way the aperture marks are plotted in Gerber Object
-- 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 it can be edited
-- modified the default values for toolchangez and endz parameters so they are now safe in all cases
-
-5.05.2019
-
-- another fix for bug in clear geometry processing for Gerber apertures
-- added a protection for the case that the aperture table is part of a deleted object
-- in Script Editor added support for auto-add closing parenthesis, brace and bracket
-- in Script Editor added support for "CTRL + / " key combo to comment/uncomment line
-
-4.05.2019
-
-- fixed bug in camlib.parse_lines() in the clear_geometry processing section for self.apertures
-- fixed bug in parsing Gerber regions (a point was added unnecessary)
-- renamed the menu entry Edit -> Copy as Geo to Convert Any to Geo and moved it in the Edit -> Conversion
-- created a new function named Convert Any to Gerber and installed it in Edit -> Conversion. It's doing what the name say: it will convert an Geometry or Excellon FlatCAM object to a Gerber object.
-
-01.05.2019
-
-- the project items color is now controlled from Foreground Role in ObjectCollection.data()
-- made again plot functions threaded but moved the dataChanged signal (update_view() ) to the main thread by using an already existing signal (plots_updated signal) to avoid the errors with register QVector
-- Enable/Disable Object toggle key ("Space" key) will trigger also the datChanged signal for the Project MVC
-- added a new setting for the color of the Project items, the color when they are disabled.
-- fixed a crash when triggering 'Jump To' menu action (shortcut key 'J' worked ok)
-- made some mods to what can be translated as some of the translations interfered with the correct functioning of FlatCAM
-- updated the translations
-- fixed bugs in Excellon Editor
-- Excellon Editor:  made Add Pad tool to work until right click
-- Excellon Editor: fixed mouse right click was always doing popup context menu
-- GUIElements.FCEntry2(): added a try-except clause
-- made sure that the Tools Tab is cleared on Editors exit
-- Geometry Editor: restored the old behavior: a tool is active until it is voluntarily exited: either by using the 'ESC' key, or selecting the Select tool or new: right click on canvas
-- RELEASE 8.915
-
-30.04.2019
-
-- in ObjectCollection class, made sure that renaming an object in Project View does not result in an empty name. If new name is blank the rename is cancelled.
-- made ObjectCollection.TreeItem() inherit KeySensitiveListVIew and implicitly QTreeView (in the hope that the theme applied on app will be applied on the tree items, too (for MacOs new DarkUI theme)
-- renamed SilkScreen Tool to Substract Tool and move it's menu location in Edit -> Conversion
-- started to modify the Substract Tool to work on Geometry objects too
-- progress in the new Substract Tool for Geometry Objects
-- finished the new Substract Tool
-- added new setting for the color of the Project Tree items; it helps in providing contrast when using dark theme like the one in MacOS
-
-29.04.2019
-
-- solved bug in Gerber Editor: the '0' aperture (the region aperture) had no size which created errors. Made the size to be zero.
-- solved bug in editors: the canvas selection shape was not deleted on mouse release if the grid snap was OFF
-- solved bug in Excellon Editor: when selecting a drill hole on canvas the selected row in the Tools Table was not the correct one but the next highest row
-- finished the Silkscreen Tool but there are some limitations (some wires fragments from silkscreen are lost)
-- solved the issue in Silkscreen Tool with losing some fragments of wires from silkscreen
-
-26.04.2019
-
-- small changes in GUI; optimized contextual menu display
-- made sure that the Project Tab is disabled while one of the Editors is active and it is restored after returning to app
-- fixed some bugs recently introduced in Editors due of the changes done to the way mouse panning is detected 
-- cleaned up the context menu's when in Editors; made some structural changes
-- updated the code in camlib.CNCJob.generate_from_excellon_by_tools() to work with the new API from Google OR-Tools
-- all Gerber regions (G36 G37) are stored in the '0' aperture
-- fixed a bug that added geometry with clear polarity in the apertures where was not supposed to be
-
-25.04.2019
-
-- Geometry Editor: modified the intersection (if the selected shapes don't intersects preserve them) and substract functions (delete all shapes that were used in the process)
-- work in the ToolSub
-- for all objects, if in Selected the object name is changed to the same name, the rename is not done (because there is nothing changed)
-- fixed Edit -> Copy as Geom function handler to work for Excellon objects, too
-- made sure that the mouse pointer is restored to default on Editor exit
-- added a toggle button in Preferences to toggle on/off the display of the selection box on canvas when the user is clicking an object or selecting it by mouse dragging.
-
-24.04.2019
-
-- PDF import tool: working in making the PDF layer rendering multithreaded in itself (one layer rendered on each worker)
-- PDF import tool: solved a bug in parsing the rectangle subpath (an extra point was added to the subpath creating nonexisting geometry)
-- PDF import tool: finished layer rendering multithreading
-- New tool: Silkscreen Tool: I am trying to remove the overlapped geo with the soldermask layer from overlay layer; layed out the class and functions - not working yet
-
-23.04.2019
-
-- Gerber Editor: added two new tools: Add Disc and Add SemiDisc (porting of Circle and Arc from Geometry Editor)
-- Gerber Editor: made Add Pad repeat until user exits the Add Pad through either mouse right click, or ESC key or deselecting the Add Pad menu item
-- Gerber and Geometry Editors: fixed some issues with the Add Arc/Add Semidisc; in mode 132, the norm() function was not the one from numpy but from a FlatCAM Class. Also fixed some of the texts and made sure that when changing the mode, the current points are reset to prepare for the newly selected mode.
-- Fixed Measurement Tool to show the mouse coordinates on the status bar (it was broken at some point)
-- updated the translation files
-- added more custom mouse cursors in Geometry and Gerber Editors
-- RELEASE 8.914
-
-22.04.2019
-
-- added PDF file as type in the Recent File list and capability to load it from there
-- PDF's can be drag & dropped on the GUI to be loaded
-- PDF import tool: added support for save/restore Graphics stack. Only for scale and offset transformations and for the linewidth. This is the final fix for Microsoft PDF printer who saves in PDF format 1.7
-- PDF Import tool: added support for PDF files that embed multiple Gerber layers (top, bottom, outline, silkscreen etc). Each will be opened in it's own Gerber file. The requirement is that each one is drawn in a different color
-- PDF Import tool: fixed bugs when drag & dropping PDF files on canvas the files geometry previously opened was added to the new one. Also scaling issues. Solved.
-- PDF Import tool: added support for detection of circular geometry drawn with white color which means actually invisible color. When detected, FlatCAM will build an Excellon file out of those geoms.
-- PDF Import tool: fixed storing geometries in apertures with the right size (before they were all stored in aperture D10)
-
-21.04.2019
-
-- fixed the PDF import tool to work with files generated by the Microsoft PDF printer (chained subpaths)
-- in PDF import tool added support for paths filled and at the same time stroked ('B' and 'B*'commands)
-- added a shortcut key for PDF Import Tool (ALT+Q) and updated the Shortcut list (also with the 'T' and 'R' keys for Gerber Editor where they control the bend in Track and Region tool and the 'M' and 'D' keys for Add Arc tool in Geometry Editor)
-
-20.04.2019
-
-- finished adding the PDF import tool although it does not support all kinds of outputs from PDF printers. Microsoft PDF printer is not supported.
-
-19.04.2019
-
-- started to work on PDF import tool
-
-
-18.04.2019
-
-- Gerber Editor: added custom mouse cursors for each mode in Add Track Tool
-- Gerber Editor: Poligonize Tool will first fuse polygons that touch each other and at a second try will create a polygon. The polygon will be automatically moved to Aperture '0' (regions).
-- Gerber Editor: Region Tool will add regions only in '0' aperture
-- Gerber Editor: the bending mode will now survive until the tool is exited
-- Gerber Editor: solved some bugs related with deleting an aperture and updating the last_selected_aperture
-
-17.04.2019
-
-- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
-- fixed version check
-- added custom mouse cursors for some tools in Gerber Editor
-- Gerber Editor: added multiple modes to lay a Region: 45-degrees, reverse 45-degrees, 90-degrees, reverse 90-degrees and free-angle. Added also key shortcuts 'T' and 'R' to cycle forward, respectively in reverse through the modes.
-- Excellon Editor: fixed issue not remembering last tool after adding a new tool
-- added custom mouse cursors for Excellon and Geometry Editors in some of their tools
-
-16.04.2019
-
-- added ability to use ENTER key to finish tool adding in Editors, NCC Tool, Paint Tool and SolderPaste Tool.
-- Gerber Editor: started to add modes of laying a track
-- Gerber Editor: Add Track Tool: added 5 modes for laying a track: 45-degrees, reverse-45 degrees, 90-degrees, reverse 90-degrees and free angle. Key 'T' will cycle forward through the modes and key 'R' will cycle in reverse through the track laying modes.
-- Gerber Editor: Add Track Tool: first right click will finish the track. Second right click will exit the Track Tool and return to Select Tool.
-- Gerber Editor: added protections for the Pad Array and Pad Tool for the case when the aperture size is zero (the aperture where to store the regions)
-
-15.04.2019
-
-- working on a new tool to process automatically PcbWizard Excellon files which are generated in 2 files
-- finished ToolPcbWizard; it will autodetect the Excellon format, units from the INF file
-- Gerber Editor: reduced the delay to show UI when editing an empty Gerber object
-- update the order of event handlers connection in Editors to first connect new handlers then disconnect old handlers. It seems that if nothing is connected some VispY functions like canvas panning no longer works if there is at least once nothing connected to the 'mouse_move' event
-- Excellon Editor: update so always there is a tool selected even after the Excellon object was just edited; before it always required a click inside of the tool table, not you do it only if needed.
-- fixed the menu File -> Edit -> Edit/Close Editor entry to reflect the status of the app (Editor active or not)
-- added support in Excellon parser for autodetection of Excellon file format for the Excellon files generated by the following ECAD sw: DipTrace, Eagle, Altium, Sprint Layout
-- Gerber Editor: finished a new tool: Poligonize Tool (ALT+N in Editor). It will fuse a selection of tracks into a polygon. It will fill a selection of polygons if they are apart and it will make a single polygon if the selection is overlapped. All the newly created filled polygons will be stored in aperture '0' (if it does not exist it will be automatically created)
-- fixed a bug in Move command in context menu who crashed the app when triggered
-- Gerber Editor: when adding a new aperture it will be store as the last selected and it will be used for any tools that are triggered until a new aperture is selected.
-
-14.04.2019
-
-- Gerber Editor: Remade the processing of 'clear_geometry' (geometry generated by polygons made with Gerber LPC command) to work if more than one such polygon exists
-- Gerber Editor: a disabled/enabled sequence for the VisPy cursor on Gerber edit make the graphics better
-- Editors: activated an old function that was no longer active: each tool can have it's own set of shortcut keys, the Editor general shortcut keys that are letters are overridden
-- Gerber and Geometry editors, when using the Backspace keys for certain tools, they will backtrack one point but now the utility geometry is immediately updated
-- In Geometry Editor I fixed bug in Arc modes. Arc mode shortcut key is now key 'M' and arc direction change shortcut key is 'D'
-- moved the key handler out of the Measurement tool to flatcamGUI.FlatCAMGui.keyPressEvent()
-- Gerber Editor: started to add new function of poligonize which should make a filled polygon out of a shape
-- cleaned up Measuring Tool
-- solved bug in Gerber apertures size and dimensions values conversion when file units are different than app units
-
-13.04.2019
-
-- updating the German translation
-- Gerber Editor: added ability to change on the fly the aperture after one of the tools: Add Pad or Add Pad Array is activated
-- Gerber Editor: if a tool is cancelled via key shortcut ESCAPE, the selection is now deleted and any other action require a new selection
-- finished German translation (Google translated with some adjustments)
-- final fix for issue #277. Previous fix was applied only for one case out of three.
-- RELEASE 8.913
-
-12.04.2019
-
-- Gerber Editor: added support for Oblong type of aperture
-- fixed an issue with automatically filled in aperture code when the edited Gerber file has no apertures; established an default with value 10 (according to Gerber specifications)
-- fixed a bug in editing a blank Gerber object
-- added handlers for the Gerber Editor context menu
-- updated the translation template POT file and the EN PO/MO files
-- Gerber Editor: added toggle effect to the Transform Tool
-- Gerber Editor: added shortcut for Transform Tool and also toggle effect here, too
-- updated the shortcut list with the Gerber Editor shortcut keys
-- Gerber Editor: fixed error when adding an aperture with code value lower than the ones that already exists
-- when adding an aperture with code '0' (zero) it will automatically be set with size zero and type: 'REG' (from region); here we store all the regions from a Gerber file, the ones without a declared aperture
-- Gerber Editor: added support for Gerber polarity change commands (LPD, LPC)
-- moved the polarity change processing from FlatCAMGrbEditor() class to camlib.Gerber().parse_lines()
-- made optional the saving of an edited object. Now the user can cancel the changes to the object.
-- replaced the standard buttons in the QMessageBox's used in the app with custom ones that can have text translated
-- updated the POT translation file and the MO/PO files for English and Romanian language
-
-11.04.2019
-
-- changed the color of the marked apertures to the global_selection_color
-- Gerber Editor: added Transformation Tool and Rotation key shortcut
-- in all Editors, manually deactivating a button in the editor toolbar will automatically select the 'Select' button
-- fixed Excellon Editor selection: when a tool is selected in Tools Table, all the drills belonging to that tool are selected. When a drill is selected on canvas, the associated tool will be selected without automatically selecting all other drills with same tool
-- Gerber Editor: added Add Pad Array tool
-- Gerber Editor: in Add Pad Array tool, if the pad is not circular type, for circular array the pad will be rotated to match the array angle
-- Gerber Editor: fixed multiple selection with key modifier such that first click selects, second deselects
-
-10.04.2019
-
-- Gerber Editor: added Add Track and Add Region functions
-- Gerber Editor: fixed key shortcuts
-- fixed setting the Layout combobox in Preferences according to the current layout
-- created menu links and shortcut keys for adding a new empty Gerber objects; on update of the edited Gerber, if the source object was an empty one (new blank one) this source obj will be deleted
-- removed the old apertures editing from Gerber Obj selected tab
-- Gerber Editor: added Add Pad (circular or rectangular type only)
-- Gerber Editor: autoincrement aperture code when adding new apertures
-- Gerber Editor: automatically calculate the size of the rectangular aperture
-
-9.04.2019
-
-- Gerber Editor: added buffer and scale tools
-- Gerber Editor: working on aperture selection to show on Aperture Table
-- Gerber Editor: finished the selection on canvas; should be used as an template for the other Editors
-- Gerber Editor: finished the Copy, Aperture Add, Buffer, Scale, Move including the Utility geometry
-- Trying to fix bug in Measurement Tool: the mouse events don't disconnect
-- fixed above bug in Measurement Tool (but there is a TODO there)
-
-7.04.2019
-
-- default values for Jump To function is jumping to origin (0, 0)
-
-6.04.2019
-
-- fixed bug in Geometry Editor in buffer_int() function that created an Circular Reference Error when applying buffer interior on a geometry.
-- fixed issue with not possible to close the app after a project save.
-- preliminary Gerber Editor.on_aperture_delete() 
-- fixed 'circular reference' error when creating the new Gerber file in Gerber Editor
-- preliminary Gerber Editor.on_aperture_add()
-
-5.04.2019
-
-- Gerber Editor: made geometry transfer (which is slow) to Editor to be multithreaded
-- Gerber Editor: plotting process is showed in the status bar
-- increased the number of workers in FlatCAM and made the number of workers customizable from Preferences
-- WIP in Gerber Editor: geometry is no longer stored in a Rtree storage as it is not needed
-- changed the way delayed plot is working in Gerber Editor to use a Qtimer instead of python threading module
-- WIP in Gerber Editor
-- fixed bug in saving the maximized state
-- fixed bug in applying default language on first start
-~~- on activating 'V' key shortcut (zoom fit) the mouse cursor is now jumping to origin (0, 0)~~
-- fixed bug in saving toolbars state; the file was saved before setting the self.defaults['global_toolbar_view]
-
-4.04.2019
-
-- added support for Gerber format specification D (no zero suppression) - PCBWizard Gerber files support
-- added support for Excellon file with no info about tool diameters - PCB Wizard Excellon file support
-- modified the bogus diameters series for Excellon objects that do not have tool diameter info
-- made Excellon Editor aware of the fact that the Excellon object that is edited has fake (bogus) tool diameters and therefore it will not sort the tools based on diameter but based on tool number
-- fixed bug on Excellon Editor: when diameter is edited in Tools Table and the target diameter is already in the tool table, the drills from current tool are moved to the new tool (with new dia) - before it crashed
-- fixed offset after editing drill diameters in Excellon Editor.
-
-3.04.2019
-
-- fixed plotting in Gerber Editor
-- working on GUI in Gerber Editor
-- added a Gcode end_command: default is M02
-- modified the calling of the editor2object() slot function to fix an issue with updating geometry imported from SVG file, after edit
-- working on Gerber Editor - added the key shortcuts: wip
-- made saving of the project file non-blocking and also while saving the project file, if the user tries again to close the app while project file is being saved, the app will close only after saving is complete (the project file size is non zero)
-- fixed the camlib.Geometry.import_svg() and camlib.Gerber.bounds() to work when importing SVG files as Gerber
-
-31.03.2019
-
-- fixed issue #281 by making generation of a convex shape for the freeform cutout in Tool Cutout a choice rather than the default
-- fixed bug in Tool Cutout, now in manual cutout mode the gap size reflect the value set
-- changed Measuring Tool to use the mouse click release instead of mouse click press; also fixed a bug when using the ESC key.
-- fixed errors when the File -> New Project is initiated while an Editor is still active.
-- the File->Exit action handler is now self.final_save() 
-- wip in Gerber editor
-
-29.03.2019
-
-- update the TCL keyword list
-- fix on the Gerber parser that makes searching for '%%' char optional when doing regex search for mode, units or image polarity. This allow loading Gerber files generated by the ECAD software TCl4.4
-- fix error in plotting Excellon when toggling units
-- FlatCAM editors now are separated each in it's own file
-- fixed TextTool in Geometry Editor so it will open the notebook on activation and close it after finishing text adding
-- started to work on a Gerber Editor
-- added a fix in the Excellon parser by allowing a comma in the tool definitions between the diameter and the rest
-
-28.03.2019
-
-- About 45% progress in German translation
-- new feature: added ability to edit MultiGeo geometry (geometry from Paint Tool)
-- changed all the info messages that are of type warning, error or success so they have a space added after the keyword
-- changed the Romanian translation by adding more diacritics  
-- modified Gerber parser to copy the follow_geometry in the self.apertures
-- modified the Properties Tool to show the number of elements in the follow_geometry for each aperture
-- modified the copy functions to copy the follow_geometry and also the apertures if it's possible (only for Gerber objects)
-
-27.03.2019
-
-- added new feature: user can delete apertures in Advanced mode and then create a new FlatCAM Gerber object
-- progress in German translation. About 27% done.
-- fixed issue #278. Crash on name change in the Name field in the Selected Tab.
-
-26.03.2019
-
-- fixed an issue where the Geometry plot function protested that it does not have an parameter that is used by the CNCJob plot function. But both inherit from FaltCAMObj plot function which does not have that parameter so something may need to be changed. Until then I provided a phony keyboard parameter to make that function 'shut up'
-- fixed bug: after using Paint Tool shortcut keys are disabled
-- added CNCJob geometry for the holes created by the drills from Excellon objects
-
-25.03.2019
-
-- in the TCL completer if the word is already complete don't add it again but add a space
-- added all the TCL keywords in the completer keyword list
-- work in progress in German translation ~7%
-- after any autocomplete in TCL completer, a space is added
-- fixed an module import issue in NCC Tool
-- minor change (optimization) of the CNCJob UI
-- work in progress in German translation ~20%
-
-22.03.2019
-
-- fixed an error that created a situation that when saving a project with some of the CNCJob objects disabled, on project reload the CNCJob objects are no longer loaded
-- fixed the Gerber.merge() function. When some of the Gerber files have apertures with same id, create a new aperture id for the object that is fused because each aperture id may hold different geometries.
-- changed the autoname for saving Preferences, Project and PNG file
-
-20.03.2019
-
-- added autocomplete finish with ENTER key for the TCL Shell
-- made sure that the autocomplete function works only for FlatCAM Scripts
-- ESC key will trigger normal view if in full screen and the ESC key is pressed
-- added an icon and title text for the Toggle Units QMessageBox
-
-19.03.2019
-
-- added autocomplete for Code editor;
-- autocomplete in Code Editor is finished by hitting either TAB key or ENTER key
-- fixed the Gerber.merge() to work for the case when one of the merged Gerber objects solid_geometry type is Polygon and not a list
-
-18.03.2019
-
-- added ability to create new scripts and open scripts in FlatCAM Script Editor
-- the Code Editor tab name is changed according to the task; 'save' and 'open' buttons will have filters installed for the QOpenDialog fit to the task
-- added ability to run a FlatCAM Tcl script by double-clicking on the file
-- in Code Editor added shortcut combo key CTRL+SHIFT+V to function as a Special Paste that will replace the '\' char with '/' so the Windows paths will be pasted correctly for TCL Shell. Also doing SHIFT + LMB on the Paste in contextual menu is doing the same.
-
-17.03.2019
-
-- remade the layout in 2Sided Tool
-- work in progress for translation in Romanian - 91%
-- changed some of the app strings formatting to work better with Poedit translation software
-- fixed bug in Drillcncjob TclCommand
-- finished translation in Romanian
-- made the translations work when the app is frozen with CX_freeze
-- some formatting changes for the application strings
-- some changes on how the first layout is applied
-- minor bug fixes (typos from copy/paste from another part of the program)
-
-16.03.2019
-
-- fixed bug in Paint Tool - Single Poly: no geometry was generated
-- work in progress for translation in Romanian - 70%
-
-13.03.2019
-
-- made the layout combobox current item from Preferences -> General window to reflect the current layout
-- remade the POT translate file
-- work in progress in translation for Romanian language 44%
-- fix for showing tools by activating them from the Menu - final fix.
-
-11.03.2019
-
-- changed some icons here and there
-- fixed the Properties Project menu entry to work on the new way
-- in Properties tool now the Gerber apertures show the number of polygons in 'solid_geometry' instead of listing the objects
-- added a visual cue in Menu -> Edit about the entries to enter the Editor and to Save & Exit Editor. When one is enabled the other is disabled.
-- grouped all the UI files in flatcamGUI folder
-- grouped all parser files in flatcamParsers folder
-- another changes to the final_save() function
-- some strings were left outside the translation formatting - fixed
-- finished the replacement of '_' symbols throughout the app which conflicted with the _() function used by the i18n
-- reverted changes in Tools regarding the toggle effect - now they work as expected
-
-10.03.2019
-
-- added a fix in the Gerber parser when adding the geometry in the self.apertures dict for the case that the current aperture is None (Allegro does that)
-- finished support for internationalization by adding a set of .po/.mo files for the English language. Unfortunately the final action can be done only when Beta will be out of Beta (no more changes) or when I will decide to stop working on this app.
-- changed the tooltip for 'feedrate_rapids' parameter to point out that this parameter is useful only for the Marlin postprocessor
-- fix app crash for the case that there are no translation files
-- fixed some forgotten strings to be prepared for internationalization in ToolCalculators
-- fixed Tools menu no longer working due of changes
-- added some test translation for the ToolCalculators (in Romanian)
-- fixed bug in ToolCutOut where for each tool invocation the signals were reconnected
-- fixed some issues with ToolMeasurement due of above changes
-- updated the App.final_save() function
-- fixed an issue created by the fact that I used the '_' char inside the app to designate unused info and that conflicted with the _() function used by gettext
-- made impossible to try to reapply current language that it's already applied (un-necessary)
-
-8.03.2019
-
-- fixed issue when doing th CTRL (or SHIFT) + LMB, the focus is automatically moved to Project Tab
-- further work in internationalization, added a fallback to English language in case there is no translation for a string
-- fix for issue #262: when doing Edit-> Save & Close Editor on a Geometry that is not generated through first entering into an Editor, the geometry disappear
-- finished preparing for internationalization for the files: camlib and objectCollection
-- fixed tools shortcuts not working anymore due of the new toggle parameter for the .run().
-- finished preparing for internationalization for the files: FlatCAMEditor, FlatCAMGUI
-- finished preparing for internationalization for the files: FlatCAMObj, ObjectUI
-- sorted the languages in the Preferences combobox
-
-7.03.2019
-
-- made showing a shape when hovering over objects, optional, by adding a Preferences -> General parameter
-- starting to work in internationalization using gettext()
-- Finished adding _() in FlatCAM Tools
-- fixed Measuring Tool - after doing a measurement the Notebook was switching to Project Tab without letting the user see the results
-- more work on the translation engine; the app now restarts after a language is applied
-- added protection against using Travel Z parameter with negative or zero value (in Geometry).
-- made sure that when the Measuring Tools is active after last click the Status bar is no longer deleted
-
-6.03.2019
-
-- modified the way the FlatCAM Tools are run from toolbar as opposed of running them from other sources
-- some Gerber UI changes
-
-5.03.2019
-
-- modified the grbl-laser postprocessor lift_code()
-- treated an error created by Z_Cut parameter being None
-- changed the hover and selection box transparency
-
-4.03.2019
-
-- finished work on object hovering
-- fixed Excellon object move and all the other transformations
-- starting to work on Manual Cutout Tool
-- remade the CutOut Tool
-- finished Manual Cutout Tool by adding utility geometry to the cutting geometry
-- added CTRL + click behavior for adding manual bridge gaps in Cutout Tool
-- in Tool Cutout added shortcut key 'Escape' to cancel the current adding of bridge gaps
-
-3.03.2019
-
-- minor UI changes for Gerber UI
-- ~~after an object move, the apertures plotted shapes are deleted from canvas and the 'mark all' button is deselected~~
-- after move tool action or any other transform (rotate, skew, scale, mirror, offset), the plotted apertures are kept plotted.
-- changing units now will convert all the default values from one unit type to another
-- prettified the selection shape and the moving shape
-- initial work in object hovering shape
-
-02.03.2019
-
-- fixed offset, rotate, scale, skew for follow_geometry. Fixed the move tool also.
-- fixed offset, rotate, scale, skew for 'solid_geometry' inside the self.apertures.
-
-28.02.2019
-
-- added a change that when a double click is performed in a object on canvas resulting in a selection, if the notebook is hidden then it will be displayed
-- progress in ToolChange Custom commands replacement and rename
-
-27.02.2019
-
-- made the Custom ToolChange Text area in CNCJob Selected Tab depend on the status of the ToolChange Enable Checkbox even in the init stage.
-- added some parameters throughout camlib gcode generation functions; handled some possible errors (e.g like when attempting to use an empty Custom GCode Toolchange)
-- added toggle effect for the tools in the toolbar.
-- enhanced the toggle effect for the tools in the Tools Toolbar and also for Notebook Tab selection: if the current tool is activated it will toggle the notebook side but only if the installed widget is itself. If coming from another tool, the notebook will stay visible
-- upgraded the Tool Cutout when done from Gerber file to create a convex_hull around the Gerber file rather than trying to isolate it
-- added some protections for the FlatCAM Tools run after an object was loaded
-
-26.02.2019
-
-- added a function to read the parameters from ToolChange macro Text Box (I need to move it from CNCJob to Excellon and Geometry)
-- fixed the geometry adding to the self.apertures in the case when regions are done without declaring any aperture first (Allegro does that). Now, that geometry will be stored in the '0' aperture with type REG
-- work in progress to Toolchange_Custom code replacement -> finished the parse and replace function
-- fixed mouse selection on canvas, mouse drag, mouse click and mouse double click
-- fixed Gerber Aperture Table dimensions
-- added a Mark All button in the Gerber aperture table.
-- because adding shapes to the shapes collection (when doing Mark or Mark All) is time consuming I made the plot_aperture() threaded.
-- made the polygon fusing in modified Gerber creation, a list comprehension in an attempt for optimization
-- when right clicking the files in Project tab, the Save option for Excellon no longer export it but really save the original. 
-- in ToolChange Custom Code replacement, the Text Box in the CNCJob Selected tab will be active only if there is a 'toolchange_custom' in the name of the postprocessor file. This assume that it is, or was created having as template the Toolchange Custom postprocessor file.
-
-
-25.02.2019
-
-- fixed the Gerber object UI layout
-- 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
-- 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
-
-24.02.2019
-
-- fixed a small bug in the Tool Solder Paste: the App don't take into consideration pads already filled with solder paste.
-- prettified the defaults files and the recent file. Now they are ordered and human readable
-- added a Toggle Code Editor Menu and key shortcut
-- added the ability to open FlatConfig configuration files in Code Editor, Modify them and then save them.
-- added ability to double click the FlatConfig files and open them in the FlatCAM Code Editor (to be verified)
-- when saving a file from Code Editor and there is no object active then the OpenFileDialog filters are reset to FlatConfig files.
-- reverted a change in GCode that might affect Gerber polarity change in Gerber parser
-- ability to double click the FlatConfig files and open them in the FlatCAM Code Editor - fixed and verified
-- fixed the Set To Origin function when Escape was clicked
-- added all the Tools in a new ToolBar
-- fixed bug that after changing the layout all the toolbar actions are no longer working
-- fixed bug in Set Origin function
-- fixed a typo in Toolchange_Probe_MACH3 postprocessor
-
-23.02.2019
-
-- remade the SolderPaste geometry generation function in ToolSoderPaste to work in certain scenarios where the Gerber pads in the SolderPaste mask Gerber may be just pads outlines
-- updated the Properties Tool to include more information's, also details if a Geometry is of type MultiGeo or SingleGeo
-- remade the Preferences GUI to include the Advanced Options in a separate way so it is obvious which are displayed when App Level is Advanced.
-- added protection, not allowing the user to make a Paint job on a MultiGeo geometry (one that is converted in the Edit -> Conversion menu)) because it is not supported
-
-22.02.2019
-
-- added Repetier postprocessor file
-- removed "added ability to regenerate objects (it's actually deletion followed by recreation)" because of the way Python pass parameters to functions by reference instead of copy
-- added ability to toggle globally the display of ToolTips. Edit -> Preferences -> General -> Enable ToolTips checkbox.
-- added true fullscreen support (for Windows OS)
-- added the ability of context menu inside the GuiElements.FCCombobox() object.
-- remade the UI for ToolSolderPaste. The object comboboxes now have context menu's that allow object deletion. Also the last object created is set as current item in comboboxes.
-- some GUI elements changes
-
-21.02.2019
-
-- added protection against creating CNCJob from an empty Geometry object (with no geometry inside)
-- changed the shortcut key for YouTube channel from F2 to key F4
-- changed the way APP LEVEL is showed both in Edit -> Preferences -> General tab and in each Selected Tab. Changed the ToolTips content for this.
-- added the functions for GCode View and GCode Save in Tool SolderPaste
-- some work in the Gcode generation function in Tool SolderPaste
-- added protection against trying to create a CNCJob from a solder_paste dispenser geometry. This one is different than the default Geometry and can be handled only by SolderPaste Tool.
-- ToolSolderPaste tools (nozzles) now have each it's own settings
-- creating the camlib functions for the ToolSolderPaste gcode generation functions
-- finished work in ToolSolderPaste
-- fixed issue with not updating correctly the plot kind (all, cut, travel) when clicking in the CNC Tools Table plot buttons
-- made the GCode Editor for ToolSolderPaste clear the text before updating the Code Editor tab
-- all the Tabs in Plot Area are closed (except Plot Area itself) on New Project creation
-- added ability to regenerate objects (it's actually deletion followed by recreation)
-
-20.02.2019
-
-- finished added a Tool Table for Tool SolderPaste
-- working on multi tool solder paste dispensing
-- finished the Edit -> Preferences defaults section
-- finished the UI, created the postprocessor file template
-- finished the multi-tool solder paste dispensing: it will start using the biggest nozzle, fill the pads it can, and then go to the next smaller nozzle until there are no pads without solder.
-
-19.02.2019
-
-- added the ability to compress the FlatCAM project on save with LZMA compression. There is a setting in Edit -> Preferences -> Compression Level between 0 and 9. 9 level yields best compression at the price of RAM usage and time spent.
-- made FlatCAM able to load old type (uncompressed) FlatCAM projects
-- fixed issue with not loading old projects that do not have certain information's required by the new versions of FlatCAM
-- compacted a bit more the GUI for Gerber Object
-- removed the Open Gerber with 'follow' menu entry and also the open_gerber Tcl Command attribute 'follow'. This is no longer required because now the follow_geometry is stored by default in a Gerber object attribute gerber_obj.follow_geometry
-- added a new parameter for the Tcl CommandIsolate, named: 'follow'. When follow = 1 (True) the resulting geometry will follow the Gerber paths.
-- added a new setting in Edit -> Preferences -> General that allow to select the type of saving for the FlatCAM project: either compressed or uncompressed. Compression introduce an time overhead to the saving/restoring of a FlatCAM project.
-- started to work on Solder Paste Dispensing Tool
-- fixed a bug in rotate from shortcut function
-- finished generating the solder paste dispense geometry
-
-18.02.2019
-
-- added protections again wrong values for the Buffer and Paint Tool in Geometry Editor
-- the Paint Tool in Geometry Editor will load the default values from Tool Paint in Preferences
-- when the Tools in Geometry Editor are activated, the notebook with the Tool Tab will be unhidden. After execution the notebook will hide again for the Buffer Tool.
-- changed the font in Tool names
-- added in Geometry Editor a new Tool: Transformation Tool.
-- in Geometry Editor by selecting a shape with a selection shape, that object was added multiple times (one per each selection) to the selected list, which is not intended. Bug fixed.
-- finished adding Transform Tool in Geometry Editor - everything is working as intended
-- fixed a bug in Tool Transform that made the user to not be able to capture the click coordinates with SHIFT + LMB click combo
-- added the ability to choose an App QStyle out of the offered choices (different for each OS) to be applied at the next app start (Preferences -> General -> Gui Pref -> Style Combobox)
-- added support for FlatCAM usage with High DPI monitors (4k). It is applied on the next app startup after change in Preferences -> General -> Gui Settings -> HDPI Support Checkbox
-- made the app not remember the window size if the app is maximized and remember in QSettings if it was maximized. This way we can restore the maximized state but restore the windows size unmaximized
-- added a button to clear the GUI preferences in Preferences -> General -> Gui Settings -> Clear GUI Settings
-- added key shortcuts for the shape transformations within Geometry Editor: X, Y keys for Flip(mirror), SHIFT+X, SHIFT+Y combo keys for Skew and ALT+X, ALT+Y combo keys for Offset
-- adjusted the plotcanvas.zomm_fit() function so the objects are better fit into view (with a border around) 
-- modified the GUI in Objects Selected Tab to accommodate 2 different modes: basic and Advanced. In Basic mode, some of the functionality's are hidden from the user.
-- added Tool Transform preferences in Edit -> Preferences and used them through out the app
-- made the output of Panelization Tool a choice out of Gerber and Geometry type of objects. Useful for those who want to engrave multiple copies of the same design.
-
-17.02.2019
-
-- changed some status bar messages
-- New feature: added the capability to view the source code of the Gerber/Excellon file that was loaded into the app. The file is also stored as an object attribute for later use. The view option is in the project context menu and in Menu -> Options -> View Source
-- Serialized the source_file of the Objects so it is saved in the FlatCAM project and restored.
-- if there is a single tool in the tool list (Geometry , Excellon) and the user click the Generate GCode, use that tool even if it is not selected
-- fixed issue where after loading a project, if the default kind of CNCjob view is only 'cuts' the plot will revert to the 'all' type
-- in Editors, if the modifier key set in Preferences (CTRL or SHIFT key) is pressed at the end of one tool operation it will automatically continue to that action until the modifier is no longer pressed when Select tool will be automatically selected.
-- in Geometry Editor, on entry the notebook is automatically hidden and restored on Geometry Editor exit.
-- when pressing Escape in Geometry Editor it will automatically deselect any shape not only the currently selected tool.
-- when deselecting an object in Project menu the status bar selection message is deleted
-- added ability to save the Gerber file content that is stored in FlatCAM on Gerber file loading. It's useful to recover from saved FlatCAM projects when the source files are no longer available.
-- fixed an issue where the function handler that changed the layout had a parameter changed accidentally by an index value passed by the 'activate' signal to which was connected
-- fixed bug in paint function in Geometry Editor that didn't allow painting due of overlap value
-
-16.02.2019
-
-- added the 'Save' menu entry to the Project context menu, for CNCJob: it will export the GCode.
-- added messages in info bar when selecting objects in the Project View list
-- fixed DblSided Tool so it correctly creates the Alignment Drills Excellon file using the new structure
-- fixed DblSided Tool so it will not crash the app if the user tries to make a mirror using no coordinates
-- added some relevant status bar messages in DblSided Tool
-- fixed DblSided Tool to correctly use the Box object (until now it used as reference only Gerber object in spite of Excellon or Geometry objects being available)
-- fixed DblSided Tool crash when trying to create Alignment Drills object without a Tool diameter specified
-- fixed DblSided Tool issue when entering Tool diameter values with comma decimal separator instead of decimal dot separator
-- fixed Cutout Tool Freeform to generate cutouts with options: LR, TB. 2LR, 2TB which didn't worked previously
-- fixed Excellon parser to detect correctly the units and zeros for Excellon's generated by Eagle 9.3.0
-- modified the initial size of the canvas on startup
-- modified the build file (make_win.py) to solve the issue with suddenly not accepting the version as Beta
-- changed the initial layout to 'compact'
-- updated the install scripts to uninstall a previously installed FlatCAM Beta (that has the same GUID)
-
-15.02.2019
-
-- rearranged the File and Edit menu's and added some explanatory tooltips on certain menu items that could be seen as cryptic
-- added Excellon Export Options in Edit -> Preferences
-- started to work in using the Excellon Export parameters
-- remade the Excellon export function to work with parameters entered in Edit -> Preferences -> Excellon Export
-- added a new entry in the Project Context Menu named 'Save'. It will actually work for Geometry and it will do Export DXF and for Excellon and it will do Export Excellon
-- reworked the offer to save a project so it is done only if there are objects in the project but those objects are new and/or are modified since last project load (if an old project was loaded.)
-- updated the Excellon plot function so it can plot the Excellon's from old projects
-- removed the message boxes that popup on Excellon Export errors and replaced them with status bar messages
-- small change in tab width so the tabs looks good in Linux, too.
-
-14.02.2019
-
-- 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()
-- 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
-- the notebook is automatically collapsed when there are no objects in the collection and it is showed when adding an object
-- added new options in Edit -> Preferences -> General -> App Preferences to control if the Notebook is showed at startup and if the notebook is closed when there are no objects in the collection and showed when the collection has objects.
-
-13.02.2019
-
-- added new parameter for Excellon Object in Preferences: Fast Retract. If the checkbox is checked then after reaching the drill depth, the drill bit will be raised out of the hole asap.
-- started to work on GUI forms simplification
-- changed the Preferences GUI for Geometry and Excellon Objects to make a difference between parameters that are changed often and those that are not.
-- changed the layout in the Selected Tab UI
-- started to add apertures table support
-- 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()
-
-12.02.2019
-
-- whenever a FlatCAM tool is activated, if the notebook side is hidden it will be unhidden
-- reactivated the Voronoi classes
-- added a new parameter named Offset in the Excellon tool table - work in progress
-- finished work on Offset parameter in Excellon Object (Excellon Editor, camlib, FlatCAMObj updated to take this param in consideration)
-- fixed a bug where in Excellon editor when editing a file, a tool was automatically added. That is supposed to happen only for empty newly created Excellon Objects.
-- starting to work on storing the solid_geometry for each tool in part in Excellon Object
-- stored solid_geometry of Excellon object in the self.tools dictionary
-- finished the solid_geometry restore after edit in Excellon Editor
-- finished plotting selection for each tool in the Excellon Tool Table
-- fixed the camlib.Excellon.bounds() function for the new type of Excellon geometry therefore fixed the canvas selection, too
-
-
-10.02.2019
-
-- the SELECTED type of messages are no longer printed to shell from 2 reasons: first, too much spam and second, issue with displaying html
-- on set_zero function and creation of new geometry or new excellon there is no longer a zoom fit 
-- repurposed shortcut key 'Delete' to delete tools in tooltable when the mouse is over the Seleted tab (with Geometry inside) or in Tools tab (when NCC Tool or Paint Tool is inside). Or in Excellon Editor when mouse is hovering the Selected tab selecting a tool, 'Delete' key will delete that tool, if on canvas 'Delete' key will delete a selected shape (drill). In rest, will delete selected objects.
-- adjusted the postprocessor files so the Spindle Off command (M5) is done before the move to Toolchange Z
-- adjusted the Toolchange Manual postprocessor file to have more descriptive messages on the toolchange event
-- added a strong focus to the object_name entry in the Selected tab
-- the keypad keyPressed are now detected correctly
-- added a pause and message/warning to do a rough zero for the Z axis, in case of Toolchange_Probe_MACH3 postprocessor file
-- changes in Toolchange_Probe_MACH3 postprocessor file
-
-9.02.2019
-
-- added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory
-- added into Preferences the Calculator Tools
-- made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before)
-- fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor
-- changed the messages from status bar on new object creation/selection
-- in Geometry Editor fixed the handler for the Rotate shortcut key ('R')
-
-8.02.2019
-
-- when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be made visible
-- changed the menu entry Toggle Grid name to Toggle Grid Snap
-- fixed errors in Toggle Axis
-- fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View
-- moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI
-- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999
-- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter
-- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected
-- changed the shortcut key for Transform Tool from 'T' to 'ALT+T'
-- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message
-- added Double Validator for the Offset value so only float numbers can be entered.
-- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab
-- if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value.
-
-7.02.2019
-
-- in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object
-- commented some debug messages
-- imported speedups for shapely
-- added a disable menu entry in the canvas contextual menu
-- small changes in Tools layout
-- added some new icons in the help menu and reorganized this menu
-- added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section
-- changed the Shortcut list shortcut key to F3
-- moved some graphical classes out of Tool Shell to GUIElements.py where they belong
-- when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed
-- in Move Tool I've added the type of object that was moved in the status bar message
-- color coded the status bar bullet to blue for selection
-- the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs.
-
-6.02.2019
-
-- fixed the units calculators crash FlatCAM when using comma as decimal separator
-- done a regression on Tool Tab default text. It somehow delete Tools in certain scenarios so I got rid of it
-- fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation
-- fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. 
-- fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it.
-- fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None
-- fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots.
-- changed the messages for Units Conversion
-- all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent()
-- renamed the theme to layout because it is really a layout change
-- added plot kind for CNC Job in the App Preferences
-- combined the geocutout and cutout_any TCL commands - work in progress
-- added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es) 
-- fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences
-- fixed the Tcl Command Geocutout to work with Gerber objects too (besides Geometry objects)
-
-5.02.3019
-
-- added a text in the Selected Tab which is showed whenever the Selected Tab is selected but without having an object selected to display it's properties
-- added an initial text in the Tools tab
-- added possibility to use the shortcut key for shortcut list in the Notebook tabs
-- added a way to set the Probe depth if Toolchange_Probe postprocessors are selected
-- finished the postprocessor file for MACH3 tool probing on toolchange event
-- added a new parameter to set the feedrate of the probing in case the used postprocessor does probing (has toolchange_probe in it's name)
-- fixed bug in Marlin postprocessor for the Excellon files; the header and toolchange event always used the parenthesis witch is not compatible with GCode for Marlin
-- fixed a issue with a move to Z_move before any toolchange
-
-4.02.2019
-
-- modified the Toolchange_Probe_general postprocessor file to remove any Z moves before the actual toolchange event
-- created a prototype postprocessor file for usage with tool probing in MACH3
-- added the default values for Tool Film and Tool Panelize to the Edit -> Preferences
-- added a new parameter in the Tool Film which control the thickness of the stroke width in the resulting SVG. It's a scale parameter.
-- whatever was the visibility of the corresponding toolbar when we enter in the Editor, it will be set after exit from the Editor (either Geometry Editor or Excellon Editor).
-- added ability to be detached for the tabs in the Notebook section (Project, Selected and Tool)
-- added ability for all detachable tabs to be restored to the same position from where they were detached.
-- changed the shortcut keys for Zoom In, Zoom Out and Zoom Fit from 1, 2, 3 to '-', '=' respectively 'V'. Added new shortcut keys '1', '2', '3' for Select Project Tab, Select Selected Tab and Select Tool Tab.
-- formatted the Shortcut List Tab into a HTML table
-
-3.3.2019
-
-- updated the new shortcut list with the shortcuts added lately
-- now the special messages in the Shell are color coded according to the level. Before they all were RED. Now the WARNINGS are yellow, ERRORS are red and SUCCESS is a dark green. Also the level is in CAPS LOCK to make them more obvious
-- some more changes to GUI interface (solved issues)
-- added some status bar messages in the Geometry Editor to guide the user when using the Geometry Tools
-- now the '`' shortcut key that shows the 'shortcut key list' in Editors points to the same window which is created in a tab no longer as a pop-up window. This tab can be detached if needed.
-- added a remove_tools() function before install_tools() in the init_tools() that is called when creating a new project. Should solve the issue with having double menu entry's in the TOOLS menu
-- fixed remove_tools() so the Tcl Shell action is readded to the Tools menu and reconnected to it's slot function
-- added an automatic name on each save operation based on the object name and/or the current date
-- added more information's for the statistics
-
-2.2.2019
-
-- code cleanup in Tools
-- some GUI structure optimization's
-- added protection against entering float numbers with comma separator instead of decimal dot separator in key points of FlatCAM (not everywhere)
-- added a choice of plotting the kind of geometry for the CNC plot (all, travel and cut kind of geometries) in CNCJob Selected Tab
-- added a new postprocessor file named: 'probe_from_zmove' which allow probing to be done from z_move position on toolchange event 
-- fixed the snap magnet button in Geometry Editor, restored the checkable property to True
-- some more changes in the Editors GUI in deactivate() function
-- a fix for saving as empty an edited new and empty Excellon Object
-
-1.02.2019
-
-- fixed postprocessor files so now the bounds values are right aligned (assuming max string length of 9 chars which means 4 digits and 4 decimals)
-- corrected small type in list_sys Tcl command; added a protection of the Plot Area Tab after a successful edit.
-- remade the way FlatCAM saves the GUI position data from a file (previously) to use PyQt QSettings
-- added a 'theme' combo selection in Edit -> Preferences. Two themes are available: standard and compact.
-- some code cleanup
-- fixed a source of possible errors in DetachableTab Widget.
-- fixed gcode conversion/scale (on units change) when multiple values are found on each line
-- replaced the pop-up window for the shortcut list with a new detachable tab
-- removed the pop-up messages from the rotate, skew, flip commands
-
-31.01.2019
-
-- added a parameter ('Fast plunge' in Edit -> Preferences -> Geometry Options and Excellon Options) to control if the fast move to Z_move is done or not
-- added new function to toggle fullscreen status in Menu -> View -> Toggle Full Screen. Shortcut key: Alt+F10
-- added key shortcuts for Enable Plots, Disable Plots and Disable other plots functions (Alt+1, Alt+2, Alt+3)
-- hidden the snap magnet entry and snap magnet toggle from the main view; they are now active only in Editor Mode
-- updated the camlib.CNCJob.scale() function so now the GCode is scaled also (quite a HACK :( it will need to be replaced at some point)). Units change work now on the GCODE also.
-- added the bounds coordinates to the GCODE header
-- FlatCAM saves now to a file in self.data_path the toolbar positions and the position of TCL Shell
-- Plot Area Tab view can now be toggled, added entry in View Menu and shortcut key CTRL+F10
-- All the tabs in the GUI right side are (Plot Are, Preferences etc) are now detachable to a separate windows which when closed it returns in the previous location in the toolbar. Those detached tabs can be also reattached by drag and drop.
-
-30.01.2019
-
-- added a space before Y coordinate in end_code() function in some of the postprocessor files
-- added in Calculators Tool an Electroplating Calculator.
-- remade the App Menu for Editors: now they will be showed only when the respective Editor is active and hidden when the Editor is closed.
-- added a traceback report in the TCL Shell for the errors that don't allow creation of an object; useful to trace exceptions/errors
-- in case that the Toolchange X,Y parameter in Selected (or in Preferences) are deleted then the app will still do the job using the current coordinates for toolchange
-- fixed an issue in camlib.CNCJob where tha variable self.toolchange_xy was used for 2 different purposes which created loss of information.
-- fixed unit conversion functions in case the toolchange_xy parameter is None
-- more fixes in camlib.CNCJob regarding usage of toolchange (in case it is None)
-- fixed postprocessor files to work with toolchange_xy parameter value = None (no values in Edit - Preferences fields)
-- fixed Tcl commands CncJob and DrillCncJob to work with toolchange
-- added to the postprocessor files the command after toolchange to go with G00 (fastest) to "Z Move" value of Z pozition.
-
-29.01.2019
-
-- fixed issue in Tool Calculators when a float value was entered starting only with the dot.
-- added protection for entering incorrect values in Offset and Scale fields for Gerber and Geometry objects (in Selected Tab)
-- added more shortcut keys in the Geometry Editor and in Excellon Editor; activated also the zoom (fit, in, out) shortcut keys ('1' , '2', '3') for the editors
-- disabled the context menu in tools table on Paint Tool in case that the painting method is single.
-- added protection when trying to do Intersection in Geometry Editor without having selected Geometry items.
-- fixed the scale, mirror, rotate, skew functions to work with Geometry Objects of multi-geometry type.
-- added a GUI for Excellon Search time for OR-TOOLS path optimization in Edit -> Preferences -> Excellon General -> Optimization Time
-- more changes in Edit -> Preferences -> Geometry, Gerber and in CNCJob
-- added new option for Cutout Tool Freeform Gaps in Edit -> Preferences -> Tools
-- fixed Freeform Cutout gaps issue (it was double than the value set)
-- added protection so the Cutout (either Freeform or Rectangular) cannot be done on a multigeo Geometry
-- added 2Sided Tool default values in Edit -> Preferences -> Tools
-- optimized the FlatCAMCNCJob.on_plot_cb_click_table() plot function and solved a bug regarding having tools numbers not in sync with the cnc tool table
-
-28.01.2018
-
-- fixed the FlatCAMGerber.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
-
-27.01.2018
-
-- added more key shortcuts into the application; they are now displayed in the GUI menu's
-- reorganized the Edit -> Preferences -> Global
-- redesigned the messagebox that is showed when quiting ot creating a New Project: now it has an option ('Cancel') to abort the process returning to the app
-- added options for trace segmentation that can be useful for auto-levelling (code snippet from Lei Zheng from a rejected pull request on FlatCAM https://bitbucket.org/realthunder/ )
-- added shortcut key 'L' for creating 'New Excellon' 
-- added shortcut key combo 'SHIFT+S' for Running a Script.
-- modified grbl_laser postprocessor file so it includes a Sxxxx command on the line with M03 (laser active) whenever a value is enter in the Spindlespeed entry field
-- remade the EDIT -> PREFERENCES window, the Excellon and Gerber sections. Created a new section named TOOLS
-
-26.01.2019
-
-- fixed grbl_11 postprocessor in linear_code() function
-- added icons to the Project Tab context menu
-- added new entries to the Canvas context menu (Copy, Delete, Edit/Save, Move, New Excellon, New Geometry, New Project)
-- fixed grbl_laser postprocessor 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  
-
-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
-- 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
-
-24.01.2019
-
-- trying to fix painting single when the actual painted object it's a MultiPolygon
-- fixed the Copy Object function when the object is Gerber
-- added the Copy entry to the Project context menu
-- made the functions behind Disable and Enable project context menu entries, non-threaded to fix a possible issue
-- added multiple object selection on Open ... and Import ... (idea and code snippet came from Travers Carter, BitBucket user https://bitbucket.org/travc/)
-- fixed 'grbl_laser' postprocessor bugs (missing functions)
-- fixed display geometry for 'grbl_laser' postprocessor
-- Excellon Editor - added possibility to create an linear drill array rotated at an custom angle
-- added the Edit and Properties entries to the Project context menu
-
-23.01.2019
-
-- added a new postprocessor file named 'line_xyz' which have x, y, z values on the same GCode line
-- fixed calculation of total path for Excellon Gcode file
-- modified the way FlatCAM preferences are saved. Now they can be saved as new files with .FlatConfig extension by the user and shared.
-- added possibility to open the folder where FlatCAM is saving the preferences files
-
-21.01.2019
-
-- changed some tooltips
-- added tooltips in Excellon tool table headers
-- in Excellon Tool Table the columns are now only selectable by clicking on the header (sorting is done automatically)
-- if CNCJob from Excellon then hide the CNC tools table in CNCJob Object
-
- 
-20.01.2019
-
-- fixed the HPGL code geometry rendering when travel
-- fixed the message box layout when asking to save the current work
-- made sure that whenever the HPGL postprocessor is selected the Toolchange is always ON and the MultiDepth is OFF
-- the HPGL postprocessor entry is not allowed in Excellon Object postprocessor selection combobox as it is only applicable for Geometry
-- when saving HPGL code it will be saved as a file with extension .plt
-- the units mentioned in HPGL format are only METRIC therefore if FlatCAM units are in INCH they will be transform to METRIC
-- the minimum unit in HPGL is 0.025mm therefore the coordinates are rounded to a multiple of 0.025mm
-- removed the raise statement in do_worker_task() function as this is fatal in conjunction with PyQt5
-- added a try - except clause for the situations when for a font can't be determined the family and name
-- moved font parsing to the Geometry Editor: it is done everytime the Text tool is invoked
-- made sure that the HPGL postprocessor is not populated in the Excellon postprocessors in Preferences as it make no sense (HPGL is useful only for Geometries)
-
-19.01.2019
-
-- added initial implementation of HPGL postprocessor
-- fixed display HPGL code geometry on canvas
-
-11.01.2019
-
-- added a status message for font parsing
-
-9.01.2019
-
-- added a fix to allow creating of Excellon geometry even when there are points with no tools by skipping those points and warning the user about this in a Tcl message
-- added a message box asking users if they want to save the project in case that either New Project menu entry is clicked or if Exit menu entry is clicked or if the app is closed from the close button. The message box will be showed only if there are objects in the collection.
-- modified the first line in the Gcode header to show the FlatCAM version and version_date
-
-8.01.2019
-
-- added checkboxes in Preferences -> General -> Global Preferences to switch on/off version check at application startup and also to control if the app will send anonymous statistics about FlatCAM usage to help improve FlatCAM
-
-7.01.2019
-
-- added tooltips in Edit->Convert menu
-- fixed cutting from copper features when doing Gerber isolation with multiple passes
-
-6.01.2019
-
-- fixed the Marlin postprocessor detection in GCode header
-- the version date in GCode header is now the one set in FlatCAMApp.App.version_date
-- fixed bug in postprocessor files: number of drills is now calculated only for the Excellon objects in toolchange function (only Excellon objects have drills) 
-
-5.01.2019
-
-- fixed cncjob TclCommand - it used the default values for parameters
-- fixed the layout in ToolTransform
-- 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.
-
-3.01.2019
-
-- initial merge into FlatCAM regular
-
-28.12.2018
-
-- changed the workspace drawing from 'gl' to 'agg'. 'gl' has better performance but it messes with the overlapping graphics
-- removed the initial obj.build_ui() in App.editor2object()
-
-25.12.2018
-
-- fixed bugs in Excellon Editor due of PyQt5 port
-- fixed bug when loading Gerber with follow
-- fixed bug that when a Gerber was loaded with -follow parameter it could not be isolated external and full
-- changed multiple status bar messages
-- changed some assertions to (status error message + return) combo
-- fixed issues in 32bit installers
-- added protection against using Excellon joining on different kind of objects
-- fixed bug in ToolCutout where the Rectangular Cutout used the Type of Gaps from Freeform Cutout
-- fixed bug that didn't allowed saving SVG file from a Gerber file
-- modified setup_ubuntu.sh file for PyQt5 packages
-
-23.12.2018
-
-- added move (as in Tool Move) capability for CNCJob object and the GCode is updated on each move --> finished both for Gcode loaded and for CNCJob generated in the app
-- fixed some errors related to DialogOpen widget that I've missed in PyQt5 porting
-- added a bounds() method for CNCJob class in camlib (perhaps overdone as it worked well with the one inherited)
-- small changes in Paint Tool - the rest machining is working only partially
-- added more columns in CNCjob Tool Table showing more info about the present tools
-- make the columns in CNCJob Tool Table not editable as it has no sense
-
-22.12.2018
-
-- fixed issues in Transform Tool regarding the message boxes
-- fixed more error in Double Sided Tool and added some more information's in ToolTips
-- added more information's in CutOut Tool ToolTips
-- updated the tooltips in amost all FlatCAM tools; in Tool Tables added column header ToolTips
-- fixed NCC rest machining in NCC Tool; added status message and stop object creation if there is no geometry on any tool
-- fixed version number: now it will made of a number in format main_version.secondary_version/working_version
-- modified the makefile for windows builds to accommodate both 32bit and 64bit executable generation
-
-21.12.2018
-
-- added shortcut "SHIFT + W" for workspace toggle
-- updated the list of shortcuts
-- forbid editing for the MultiGeo type of Geometry because the Geometry Editor is not prepared for this
-- finished a "sort" of rest-machining for Non Copper Clearing Tool but it's time consuming operation
-- reworked the NCC Tool as it was fundamental wrong - still has issues on the rest machining
-- added a parameter reset for each run of Paint Tool and NCC Tool
-
-20.12.2018
-
-- porting application to PyQt5
-- adjusted the level of many status bar messages
-- created new bounds() methods for Excellon and Gerber objects as the one inherited from Geometry failed in conjunction with PyQt5
-- fixed some small bugs where a string was divided by a float finally casting the result to an integer
-- removed the 'raise' conditions everywhere I could and make protections against loading files in the wrong place
-- fixed a "PyCharm stupid paste on the previous tab level even after one free line " in Excellon.bounds()
-- in Geometry object fixed error in tool_delete regarding deletion while iterating a dict
-- started to rework the NCC Tool to generate one file only
-- in Geometry Tool Table added checkboxes for individual plot of tools in case of MultiGeo Geometry
-- rework of NCC Tool UI
-- added a automatic selector: if the system is 32bit the OR-tools imports are not done and the OR-tools drill path optimizations are replaced by a default Travelling Salesman drill path optimization
-- created a Win32 make file to generate a Win32 executable
-- disabled the Plot column in Geometry Tool Table when the geometry is SingleGeo as it is not needed
-- solved a issue when doing isolation, if the solid_geometry is not a list will make it a list
-- added tooltips in the Geometry Tool Table headers explaining each column
-- added a new Tcl Command: clear. It clears the Tcl Shell of all text and restore it to the original state
-- fixed Properties Tool area calculation; added status bar messages if there is no object selected show an error and successful showing properties is confirmed in status bar
-- when Preferences are saved, now the default values are instantly propagated within the application
-- when a geometry is MultiGeo and all the tools are deleted, it will have no geometry at all therefore all that it's plotted on canvas that used to belong to it has to be deleted and because now it is an empty object we demote it to SingleGeo so it can be edited
-
-19.12.2018
-
-- fixed SVG_export for MultiGeo Geometries
-- fixed DXF_export for MultiGeo Geometries
-- fixed SingleGeo to MultiGeo conversion plotting bug
-
-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
-- 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
-- fixed Move Tool to support MultiGeo geometry objects moving
-- added tool edit in Geometry Object Tool Table
-- added Tool Table context menu in Geometry Object and in Paint Tool
-- modified some Status Bar messages in Geometry Object
-
-17.12.2018
-
-- added support for multiple solid_geometry in a geometry object; each tool can now have it's own geometry. Plot, project save/load are OK.
-- added support for single GCode file generation from multi-tool PaintTool job
-- added protection for results of Paint Tool job that do not have geometry at all. An Error will be issued. It can happen if the combination of Paint parameters is not good enough
-- solved a small bug that didn't allow the Paint Job to be done with lines when the results were geometries not iterable 
-- added protection for the case when trying to run the cncjob Tcl Command on a Geometry object that do not have solid geometry or one that is multi-tool
-- Paint Tool Table: now it is possible to edit a tool to a new diameter and then edit another tool to the former diameter of the first edited tool
-- added a new type of warning, [WARNING_NOTCL]
-- fixed conflict with "space" keyboard shortcut for CNC job
-
-16.12.2018
-
-- redone the Options menu; removed the Transfer Options as they were not used
-- deleted some folders in the project structure that were never used
-- Paint polygon Single works only for left mouse click allowing mouse panning
-- added ability to print errors in status bar without raising Tcl Shell
-- fixed small bug: when doing interiors isolation on a Gerber that don't allow it, no object is created now and an error in the status bar is issued
-- fixed bug in Paint All for Geometry made from exteriors Gerber isolation
-- fixed the join geometry: when the geometries has different tools the join will fail with a status bar message (as it should). Allow joining of geometries that have no tool. // Reverted on 18.12.2018
-- changed the error messages that are simple to the kind that do not open the TCl shell
-- fixed some issues in Geometry Object
-- Paint Tool - reworked the UI and made it compatible with the Geometry Object UI
-- Paint Tool - tool edit functional
-- added Clear action in the Context menu of the TCl Shell
-
-14.12.2018
-
-- fixed typo in setup_ubuntu.sh
-- minor changes in Excellon Object UI
-- added Tool Table in Paint Tool
-- now in Paint Tool and Non Copper Clearing Tool a selection of tools can be deleted (not only one by one)
-- minor GUI changes (added/corrected tooltips)
-- optimized vispy startup time from about >6 sec to ~3 seconds
-- removed vispy text collection starting in plotcanvas as it did nothing // RESTORED 18.12.2018 as it messed the graphical presentation
-- fixed cncjob TclCommand for the new type of Geometry
-- make sure that when using the TclCommands, the object names are case insensitive
-- updated the TCL Shell auto-complete function; now it will index also the names of objects created or loaded in the application
-- on object removal the name is removed from the Shell auto-complete model
-
-13.12.2018
-
-NEW Geometry Object and CNC Object architecture (3rd attempt) which allow multiple tools for one geometry
-
-- fixed issue with cumulative G-code after successive delete/create of a CNCJob on the same geometry (some references were kept after deletion of CNCJob object which kept the deleted tools data and added it to a new one)
-- fixed plot and export G-code in new format
-- fixed project save/load in the new format for geometry
-- added new feature in CNCJob Object UI: since we may have multiple tools per CNCJob object due of having multiple tool in Geometry Object,
-now there is a Tool Table in CNC Object UI and each tool GCode can be enabled or disabled
-
-12.12.2018
-
-- Geometry Tool Table: when the Offset type is 'custom' each tool it's storing the value and it is updated on UI when that tool is selected in UI table
-- Geometry Tool Table: fixed tool offset conversion when the Offset in Tool Table UI is set to Custom
-
-11.12.2018
-
-- cleaned up the generatecncjob() function in FlatCAMObj
-- created a new function for generating cncjob out of multitool geometry, mtool_generate_cncjob()
-- cleaned up the generate_from_geometry_2() method in camlib
-- Geometry Tool Table: new tool added copy all the form fields (data) from the last tool
-- finished work on generation of a single CNC Job file (therefore a single GCODE file) even for multiple tools in Geo Tool Table
-- GCode header is added only on saving the file therefore the time generation will be reflected in the file
-- modified postprocessors to accommodate the new CNC Job file with multiple tools
-- modified postprocessors so the last X,Y move will be to the toolchange X,Y pos (set in Preferences)
-- save_project and load_project now work with the new type of multitool geometry and cncjob objects
-
-10.12.2018
-
-- added new feature in Geometry Tool Table: if the Offset type in tool table is 'Offset' then a new entry is unhidden and the user can use custom offset
-- Geometry Tool Table: fixed add new tool with diameter with many decimals
-- Geometry Tool Table: when editing the tip dia or tip angle for the V Shape tool, the CutZ is automatically calculated
-
-9.12.2018
-
-- new Geometry Tool Table has functional unit conversion
-- when entering a float number in Spindle Speed now there is no error and only the integer part is used, the decimals are discarded
-- finished the Geometry Tool Table in the form that generates only multiple files
-- if tool type is V-Shape ('V') then the Cut Z entry is disabled and new 'Tip Dia' and 'Tip Angle' fields are showed. The values entered will calculate the Cut Z parameter
-
-5.12.2018
-
-- remade the Geometry Tool Table, before this change each tool could not store it's own set of data in case of multiple tools with same diameter
-- added a new column in Geo Tool Table where to specify which type of tool to use: C for circular, B for Ball and V for V-shape
-
-4.12.2018
-
-- new geometry/excellon object name is now only "new_g"/"new_e" as the type is clear from the category is into (and the associated icon)
-- always autoselect the first tool in the Geometry Tool table
-- issue error message if the user is trying to generate CNCJob without a tool selected in Geometry Tool Table
-- add the whole data from Geometry Object GUI as dict in the geometry tool dict so each tool (file) will have it's own set of data
-
-3.12.2018
-
-- Geometry Tool table: delete multiple tools with same diameter = DONE
-- Geometry Tool table: possibility to cut a path inside or outside or on path = DONE
-- Geometry Tool table: fixed situation when user tries to add a tool but there is no tool diameter entered
-- if a geometry is a closed shape then create a Polygon out of it
-- some fixes in Non Copper Clearing Tool
-- Geometry Tool table: added option to delete_tool function for delete_all
-- Geometry Tool table: added ability to delete even the last tool in tool_table and added an warning if the user try to generate a CNC Job without a tool in tool table
-- if a geometry is painted inside the Geometry Editor then it will store the tool diameter used for this painting. Only one tool cn be stored (the last one) so if multiple paintings are done with different tools in the same geometry it will store only the last used tool.
-- if multiple geometries have different tool diameters associated (contain a paint geometry) they aren't allowed to be joined and a message is displayed letting the user know
-
-2.12.2018
-
-- started to work on a geometry Tool Table
-- renamed FlatCAMShell as ToolShell and moved it (and termwidget) to flatcamTools folder
-- cleaned up the ToolShell by removing the termwidget separate file and added those classes to ToolShell
-- added autocomplete for TCL Shell - the autocomplete key is 'TAB'
-- covered some possible exceptions in rotate/skew/mirror functions
-- Geometry Tool table: add/delete tools = DONE
-- Geometry Tool table: add multiple tools with same diameter = DONE
-
-1.12.2018
-
-- fixed Gerber parser so now the Gerber regions that have D02 operation code just before the end of the region will be processed correctly. Autotrax Dex Gerbers are now loaded
-- fixed an issue with temporary geo storage "geo" being referenced before assignment
-- moved all FlatCAM Tools into a single directory
-
-30.11.2018
-
-- remade the CutOut Tool. I've put together the former Freeform Cutout tool and the Cutout Object fount in Gerber Object GUI and left only a link in the Gerber Object GUI. This tidy the GUI a bit.
-- created a Paint Tool and replaced the Paint Area section in Geometry Object GUI with a link to this tool.
-- fixed bug in former Paint Area and in the new Paint Tool that made the paint method not to be saved in App preferences
-- solved a bug in Gerber parser: in case that an operation code D? was encountered alone it was not remembered - fixed
-- fixed bug related to the newly entered toolchange feature for Geometry: it was trying to evaluate toolchange_z as a comma separated value like for toolchange x,y
-- fixed bug in scaling units in CNC Job which made the unit change between INCH and MM not possible if a CNC Job was present in the project objects
-
-29.11.2018
-
-- added checks for using a Z Cut with positive value. The Z Cut parameter has to be negative so if the app will detect a positive value it will automatically convert it to negative
-- started to implement rest-machining for Non Copper clearing Tool - for now the results are not great
-- added Toolchange X,Y position parameters and modified the default and manual_toolchange postprocessor file to use them
-For now they are used only for Excellon objects who do have toolchange events
-- added Toolchange event selection for Geometry objects; for now it is as before, single tool on each file
-- remade the GUI for objects and in Preferences to have uniformity
-- fixed bug: after editing a newly created excellon/geometry object the object UI used to not keep the original settings
-- fixed some bugs in Tool Add feature of the new Non Copper Clear Tool
-- added some messages in the Non Copper Clear Tool
-- added parameters for coordinates no of decimals and for feedrate no of decimals used in the resulting GCODE. They are in EDIT -> Preferences -> CNC Job Options
-- modified the postprocessors to use the "decimals" parameters
-
-28.11.2018
-
-- added different methods of copper clearing (standard, seed, line_based) and "connect", "contour" options found in Paint function
-- remake of the non-copper clearing tool as a separate tool
-- modified the "About" menu entry to mention the main contributors to FlatCAM 3000 
-- modified Marlin postprocessor according to modifications made by @redbull0174 user from FlatCAM.org forum
-- modified Move Tool so it will detect if there is no object to move and issue a message
-
-27.11.2018
-
-- fixed bug in isolation with multiple passes
-- cosmetic changes in Buffer and Paint tool from Geometry Editor
-- changed the way selection box is working in Geometry Editor; now cumulative selection is done with modifier key (SHIFT or CONTROL) - before it was done by default
-- changed the default value for CNCJob tooldia to 1mm
-
-25.11.2018
-
-- each Tool change the name of the Tools tab to it's name
-- all open objects are no longer autoselected upon creation. Only on new Geometry/Excellon object creation it will be autoselected
-
-24.11.2018
-
-- restored the selection method in Geometry Editor to the original one found in FlatCAM 8.5
-- minor changes in Clear Copper function
-- minor changes in some postprocessors
-- change Join Geometry menu entry to Join Geo/Gerber
-- added menu entry for Toggle Axis in Menu -> View
-- added menu entry for Toggle Workspace in Menu -> View
-- added Bounding box area to the Properties (when metric units, in cm2)
-- non-copper clearing function optimization
-- fixed Z_toolchange value in the GCODE header
-
-21.11.2018
-
-- not very precise jump to location function
-- added shortcut key for jump to coordinates (J) and for Tool Transform (T)
-- some work in shortcut key
-
-19.11.2018
-
-- fixed issue with nested comment in postprocessors
-- fixed issue in Paint All; reverted changes
-
-18.11.2018
-
-- renamed FlatCAM 2018 to FlatCAM 3000
-- added new entries in the Help menu; one will show shortcut list and the other will start a YouTube webpage with a playlist where I will publish future training videos for this version of FlatCAM
-- if a Gerber region has issues the file will be loaded bypassing the error but there will be a TCL message letting the user know that there are parser errors. 
-
-17.11.2018
-
-- added Excellon parser support for units defined outside header
-
-
-12.11.2018
-
-- fixed bug in Paint Single Polygon
-- added spindle speed in laser postprocessor
-- added Z start move parameter. It controls the height at which the tool travel on the fist move in the job. Leave it blank if you don't need it.
-
-9.11.2018
-
-- fixed a reported bug generated by a typo for feedrate_z object in camlib.py. Because of that, the project could not be saved.
-- fixed a G01 usage (should be G1) in Marlin postprocessor.
-- changed the position of the Tool Dia entry in the Object UI and in FlatCAMGUI
-- fixed issues in the installer
-
-30.10.2018
-
-- fixed a bug in Freeform Cutout Tool - it was missing a change in the name of an object
-
-29.10.2018
-
-- added Excellon export menu entry and functionality that can export in fixed format 2:4 LZ INCH (format that Altium can load and it is a more generic format).
-It will be usefull for those who need FlatCAM to only convert the Excellon to a more useful format and visualize Gerbers.
-The other Excellon Export menu entry is exporting in units either Metric or INCH depending on the current units in FlatCAM, but it will always use the decimal format which may not be loaded in all cases.
-- disabled the Selected Tab while in Geometry Editor; the user is not supposed to have access to those functions while in Geometry Editor
-- added an menu entry in Menu -> File -> Recent Files named Clear Recent files which does exactly that
-- fixed issue: when a New Project is created but there is a Geometry still in Geometry Editor (or Excellon Editor) not saved, now that geometry is deleted
-- fixed problem when doing Clear Copper with Cut over 1st point option active. When the shape is not closed then it may cut over copper features. Originally the feature was meant to be used only with isolation geometry which is closed. Fixed
-
-28.10.2018
-
-- fixed Excellon Editor shortcut messages; also fixed differences in messages between usage by shortcuts and usage by menu toolbar actions
-- fixed Excellon Editor bug: it was triggering exceptions when the user selected a tool in tooltable and then tried to add a drill (or array) by clicking on canvas
-Clicking on canvas by default clear all the used tools, therefore the action could not be done. Fixed.
-- fixed bug Excellon Editor: when all the drills from a tool are resized, after resize they can't be selected.
-- Excellon Editor: added ability to delete multiple tools at once by doing multiple selection on the tooltable
-- Excellon Editor: if there are no more drills to a tool after doing drills resize then delete that tool from the tooltable
-- Excellon Editor: always select the last tool added to the tooltable
-- Excellon Editor: added a small canvas context menu for Excellon Editor
-
-27.10.2018
-
-- added a Paint tool toolbar icon and added shortcut key 'I' for Paint Tool
-- fixed unreliable multiple selection in Geometry Editor; some clicks were not registered
-- added utility geometry for Add Drill Array in Excellon Editor
-- fixed bug Excellon Editor: drills in drill array start now from the array start point (x, y); previously array start point was used only for calculating the radius
-- fixed bug Excellon Editor: Measurement Tool was not acting correctly in Exc Editor regarding connect/disconnect of events
-- in Excellon Editor every time a tool is clicked (except Select which is the default) the focus will return to Selected tab
-- added protection in Excellon Editor: if there is no tool/drill selected no operation over drills can be performed and a status bar message will be displayed
-- Excellon Editor: added relevant messages for all actions
-- fixed bug Excellon Editor: multiple selection with key modifier pressed (CTRL/SHIFT) either by simple click or through selection box is now working
-- fixed dwell parameter for Excellon in Preferences to be default Off
-
-26.10.2018
-
-- when objects are disabled they can't be selected
-- added Feedrate_z (Plunge) parameter for Geometry Object
-- fixed bug in units convert for Geometry Tab; added some missing parameters to the conversion list
-- fixed bug in isolation Geometry when the isolated Gerber was a single Polygon
-- updated the Paint function in Geometry Editor
-
-25.10.2018
-
-- added a verification on project saving to make sure that the project was saved successfully. If not, a message will be displayed in the status bar saying so.
-
-20.10.2018
-
-- fixed the SVG import as Gerber. But unfortunately, when there is a ground pour in a imported PCB SVG, the ground pour will be isolated inside
-instead to be isolated outside like every other feature. That's no way around this. The end result will be thinner features
-for the ground pour and if one is relying on those thin connections as GND links then it will not work as intended ,they may be broken.
-Of course one can edit the isolation geometry and delete the isolation for the ground pour.
-- delete selection shapes on double clicking on object as we may not want to have selection shape while Selected tab is active
-
-19.10.2018
-
-- solved some value update bugs in tool_table in Excellon Editor when editing tools followed by deleting another tool,
-and then re-adding the just-deleted tool.
-- added support for chaining blocks in DXF Import
-- fixed the DXF arc import
-- added support for a type of Gerber files generated by OrCAD where the Number format is combined with G74 on the same line
-- in Geometry Editor added the possibility for buffer to use different kinds of corners
-- added protection against loading an GCODE file as Excellon through drag & drop on canvas or file open dialog
-- added shortcut key 'B' for buffer operation inside Geometry Editor
-- added shell message in case the Font used in Text Tool in Geometry editor is not supported. Only Regular, Bold, Italic adn BoldItalic are supported as of yet.
-- added shortcut key 'T' for Text Tool inside Geometry Editor
-- added possibility for Drag & Drop on FlatCAM GUI with multiple files at once 
-
-18.10.2018
-
-- fixed DXF arc import in case of extrusion enabled
-- added on Geo Editor Toolbar the button for Buffer Geometry; added the possibility to create exterior and interior buffer
-- fixed a numpy import error
-
-17.10.2018
-
-- added Spline support and Ellipse (chord) support in DXF Import: chord might have issues
-(borrowed from the work of Vasilis Vlachoudis, https://github.com/vlachoudis/bCNC)
-- added Block support in DXF Import - no support yet for chained blocks (INSERT in block)
-- support for repasted block insertions
-
-16.10.2018
-
-- added persistent toolbar view: the enabled toolbars will be active at the next app startup while those that are not enabled will not be
-enabled at the next app startup. To enable/disable toolbars right click on the toolbar.
-
-15.10.2018
-
-- DXF Export works now also for Exteriors only and Interiors only geometry generated from Gerber Object
-- when a Geometry is edited, now the interiors and exterior of a Polygon that is part of the Geometry can be selected individually. In practice, if
-doing full isolation geometry, now both external and internal trace can be selected individually.
-
-13.10.2018
-
-- solved issue in CNC Code Editor: it appended text to the previous one even if the CNC Code Editor was closed
-- added .GBD Gerber extension to the lists
-- added support for closed polylines/lwpolylines in Import DXF; now PCB patterns found in PDF format can be imported in INKSCAPE
-and saved as DXF. FlatCAM can import DXF as Gerber and the user now can do isolation on it.
-
-12.10.2018
-
-- added zoom in, zoom out and zoom fit buttons on the View toolbar
-- fixed bug that on Double Sided Tool when a Excellon Alignment is created does not reset the list of Alignment drills
-- added a message warning the user to add Point coordinates in case the reference used in Double Sided Tool is Point
-- added new feature: DXF Export for Geometry
-
-10.10.2018
-
-- fixed a small bug in Setup Recent Files
-- small fix in Freeform Cutout Tool regarding objects populating the combo boxes
-- Excellon object name will reflect the number of edits performed on it
-
-9.10.2018
-
-- In Geometry Editor, now Path and Polygon draw mode can be finished not only with shortcut key Enter but also with right click on canvas
-- fixes regarding of circle linear approximation - final touch
-- fix for interference between Geo Editor and Excellon Editor
-- fixed Cut action in Geometry Editor so it can now be done multiple times on the target geometry without need for saving in between.
-- initial work on DXF import; made the GUI interface and functional structure
-- added import functions for DXF import
-- finished DXF Import (no blocks support, no SPLINE support for now)
-
-8.10.2018
-
-- completed toggle canvas selection when there is only one object under click position for the case when clicking the object is done
-while other object is already selected.
-- added static utility geometry just upon activating an Editor function
-- changed the way the canvas is showed on FlatCAM startup
-
-7.10.2018
-
-- solved mouse click not setting relative measurement origin to zero
-- solved bug that always added one drill when copying a selection of drills in the EXCELLON EDITOR
-- solved bug that the number of copied drills in Excellon Editor was not updated in the tool table
-- work in the Excellon Editor: found useful to change the diameter of one tool to another already in the list;
-could help for all those tools that are a fraction difference that comes from imperial to mm (or reverse) conversion,
-to reduce the tool changes - Done
-- in Excellon Editor, always auto-select the last tool added
-- in Excellon Editor fixed shortcuts for drill add and drill_array add: they were reversed. Now key 'A' is for array add
-and key 'D' is for drill add
-- solved a small bug in Excellon export: even when there were no slots in the file, it always added the tools list that
-acted as unnecessary toolchanges
-- after Move action, all objects are deselected
-
-
-6.10.2018
-
-- Added basic support for SVG text in SVG import. Will not work if some letters in a word have different style (italic bold or both)
-- added toggle selection to the canvas selection if there is only one object under the click position
-- added support for "repeat" command in Excellon file
-- added support for Allegro Gerber and Excellon files
-- Python 3.7 is used again; solved bug where the activity icon was not playing when FlatCAM active
-
-5.10.2018
-
-- fixed undesired setting focus to Project Tab when doing the SHIFT + LMB combo (to capture the click coordinates)
-
-4.10.2018
-
-- Excellon Editor: finished Add Drill Array - Linear type action
-- Excellon Editor: finished Add Drill Array - Circular type action
-- detected bug in shortcuts: Fixed
-- Excellon Editor: added constrain for adding circular array, if the number of drills multiplied by angle is more than 360
-the app will return with an message
-- solved sorting bug in the Excellon Editor tool table
-- solved bug in Menu -> Edit -> Sort Origin ; the selection box was not updated after offset
-- added Excellon Export in Menu -> File -> Export -> Export Excellon
-- added support to save the slots in the Excellon file in case there were some in the original file
-- fixed Double Sided Tool for the case of using the box as mirroring reference.
-
-2.10.2018
-
-- made slots persistent after edit
-- bug detected: in Excellon Editor if new tool added diameter is bigger than 10 it mess things up: SOLVED
-- Excellon Editor: finished Drill Resize action
-- after an object is deleted from the Project list, if the current tab in notebook is not Project,
-always focus in the Project Tab (deletion can be done by shortcut key also)
-- changed the initial view to include the possible enabled workspace guides
-
-1.10.2018
-
-- added GUI for Excellon Editor in the Tool Tab
-- Excellon Editor: created and populated the tool list
-- Excellon Editor: added possibility to add new tools in the list
-- Excellon Editor: added possibility to delete a tool (and the drills that it contain) by selecting a row in the tool table and 
-clicking the Delete Tool button
-- Excellon Editor: added possibility to change the tool diameter in the tool list for existing tool diameters.
-- Excellon Editor: when selecting a drill, it will highlight the tool in the Tool table
-- Excellon Editor: optimized single click selection
-- Excellon Editor: added selection for all drills with same diameter upon tool selection in tool table; fix in tool_edit
-- Excellon Editor: added constrain to selection by single click, it will select if within a certain area around the drill
-- Excellon Editor: finished Add Drill action
-- Excellon Editor: finished Move Drill action
-- Excellon Editor: finished Copy Drill action
-
-- fixed issue: when an object is selected before entering the Editor mode, now the selecting shape is deleted before entry 
-in the Editor (be it Geometry or Excellon).
-- fixed a few glitches regarding the units change
-- when an object is deselected on the Plot Area, the notebook will switch to Project Tab
-- changed the selection behavior for the dragging rectangle selection box in Editor (Geometry, Excellon): by dragging a
-selection box and selecting is cumulative: it just adds. To remove from selection press key Ctrl (or Shift depending of 
-the setting in the Preferences) and drag the rectangle across the objects you want to deselect.
-
-29.09.2018
-
-- optimized the combobox item population in Panelization Tool and in Film Tool
-- FlatCAM now remember the last path for saving files not only for opening
-- small fix in GUI
-- work on Excellon Editor. Excellon editor working functions are: loading an Excellon object into Editor, 
-saving an Excellon object from editor to FlatCAM, selecting drills by left click, selection of drills by dragging rectangle, deletion of drills.
-- fixed Excellon merge
-- added more Gcode details (depthperpass parameter in Gcode header) in postprocessors
-- deleted the Tool informations from header in postprocessors due to Mach3 not liking the lot of square brackets
-- more corrections in postprocessors
-
-
-28.09.2018
-
-- added a save_defaults() call on App exit from action on Menu -> File -> Exit
-- solved a small bug in Measurement Tool
-- disabled right mouse click functions when Measurement Tools is active so the user can do panning and find the destination point easily
-- added a new button named "Measure" in Measurement Tool that allow easy access to Measurement Tool from within the tool
-- fixed a bug in Gerber parser that when there was a rectangular aperture used within a region, some artifacts were generated.
-- some more work on Excellon Editor
-
-27.09.2018
-
-- fixed bug when creating a new project, if a previous object was selected on screen, the selection shape
-survived the creation of a new project
-- added compatibility with old type of FlatCAM projects
-- reverted modifications to the way that Excellon geometry was stored to the old way.
-- added exceptions for Paint functions so the user can know if something failed.
-- modified confirmation messages to use the color coded messages (error = red, success = green, warning = yellow)
-- restored activity icon
-
-26.09.2018
-
-- disabled selection of objects in Project Tab when in Editor
-- the Editor Toolbar is hidden in normal mode and it is showed when Editor
-is activated. I may change this behaviour back.
-- changed names in classes, functions to prepare for the Excellon editor
-
-- fixed bugs in Paint All function
-- fixed a bug in ParseSVG module in parse_svg_transform(), related to 'scale'
-
-- moved all the Editor menu/toolbar creation to FlatCAMUI where they belong
-- fixed a Gerber parse number issue when Gerber zeros are TZ (keep trailing zeros)
-
-- changed the way of how the solid_geometry for Excellon files is stored
-and plotted. Before everything was put in the same "container". Now,
-the geometries of drills and slots are organized into dictionaries having
-as keys the tool diameters and as values list of Shapely objects (polygons)
-- fix for Excellon plotting for newly created empty Excellon Object
-- fixed geometry.bounds() in camlib to work with the new format of the Excellon geometry (list of dicts)
-
-24.09.2018
-
-- added packages in the Requirements and setup_ubuntu.sh. Tested in Ubuntu and
-it's OK
-- added Replace (All) feature in the CNC Code Editor
-- made CNC Code generation for Excellon to show progress
-- added information about transforms in the object properties (like skew
-and how much, if it was mirrored and so on)
-- made all the transforms threaded and make them show progress in the progress bar
-- made FlatCAM project saving, threaded.
+------------ Installation instructions ----------
+
+Works with Python version 3.5 or greater and PyQt5.
+More on the YouTube channel: https://www.youtube.com/playlist?list=PLVvP2SYRpx-AQgNlfoxw93tXUXon7G94_
+You can contact me on my email address found in the app in:
+Menu -> Help -> About FlatCAM -> Programmers -> Marius Stanciu
+
+- Make sure that your OS is up-to-date
+- Download sources from: https://bitbucket.org/jpcgt/flatcam/downloads/
+- Unzip them on a HDD location that your user has permissions for.
+
+1. Windows
+- download the provided installer (for your OS flavor 64bit or 32bit) from:
+https://bitbucket.org/jpcgt/flatcam/downloads/
+- execute the installer and install the program. It is recommended to install as a Local User.
+
+or from sources:
+- download the sources from the same location
+- unzip them on a safe location on your HDD that your user has permissions for
+- install WinPython e.g WinPython 3.8 downloaded from here: https://sourceforge.net/projects/winpython/files/WinPython_3.8/
+Use one of the versions (64bit or 32it) that are compatible with your OS. 
+To save space use one of the versions that have the smaller size (they offer 2 versions: one with size of few hundred MB and one smaller with size of few tens of MB)
+
+- add Python folder and Python\Scripts folder to your Windows Path (https://docs.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ee537574(v%3Doffice.14))
+- verify that the pip package can be run by opening Command Prompt(Admin) and running the command: pip -V
+
+- look in the requirements.txt file (found in the sources folder) and install all the dependencies using the pip package. 
+The required wheels can be downloaded either from:
+https://www.lfd.uci.edu/~gohlke/pythonlibs/
+or
+https://pypi.org/
  
-23.09.2018
-
-- added support for "header-less" Excellon files. It seems that Mentor PADS does generate such
-non-standard Excellon files. The user will have to guess: units (IN/MM), type of zero suppression LZ/TZ 
-(leading zeros or trailing zeros are kept) and Excellon number format(digits and decimals). 
-All of those can be adjusted in Menu -> Edit -> Preferences -> Excellon Object -> Excellon format
-- fixed svgparse for Path. Now PCB rasted images can traced in Inkscape or PDF's can be converted
-and then saved as SVG files which can be imported into FlatCAM. This is a convolute way to convert a PDF
-to Gerber file.
-
-22.09.2018
-
-- added Drag & Drop capability. Now the user can drag and drop to FlatCAM GUI interface a file 
-(with the right extension) that can be a FlatCAM project file (.FlatPrj) a Gerber file, 
-an Excellon file, a G-Code file or a SVG file.
-- made the Move Tool command threaded
-- added Image import into FlatCAM
-
-21.09.2018
-
-- added new information's in the object properties: all used Tool-Table items
-are included in a new entry in self.options dictionary
-- modified the postprocessor files so they now include information's about
-how many drills (or slots) are for each tool. The Gcode will have this
-information displayed on the message from ToolChange.
-- removed some log.debug and add new log.debug especially for moments when some process is finished
-- fixed the utility geometry for Font geometry in Geometry Editor
-- work on selection in Geometry Editor
-- added multiple selection key as a Preference in Menu -> Edit -> Preferences
-It can be either Shift or Ctrl.
-- fixed bug in Gerber Object -> Copper Clearing.
-- added more comprehensive tooltips in Non-copper Clearing as advice on how to proceed.
-- adjusted make_win32.py file so it will work with Python 3.7 (cx_freeze can't copy OpenGL files, so
-it has to be done manually)
-
-19.09.2018
-
-- optimized loading FlatCAM project by double clicking on project file; there is no need to clean up everything by using 
-the function not Thread Safe: on_file_new() because there is nothing to clean since FlatCAM just started.
-
-- added a workspace delimitation with sizes A3, A4 and landscape or portrait format
-- The Workspace checkbox in Preferences GUI is doing toggle on the workspace
-- made the workspace app default state = False
-- made the workspace to resize when units are changed
-- disabled automatic defaults save (might create SSD wear)
-- added an automatic defaults save on FlatCAM application close
-- made the draw method for the Workspace lines 'agg' so the quality of the FC objects will not be affected
-
-- added Area constrain to the Panelization Tool: if the resulting area is too big to fit within constrains, the number
-of columns and/or rows will be reduced to the maximum that still fits is.
-- removed the Flip command from Panelization Tools because Flipping (Mirroring) should be done properly with the 
-Transform Tool or using the provided shortcut keys.
-
-- made Font parsing threaded so the application will not wait for the font parsing to complete therefore the app start
-is faster
-
-
-17.09.2018
-
-- fixed Measuring Tool not working when grid is turned OFF
-- fixed Roland MDX20 postprocessor
-- added a .GBR extension in the open_gerber filter
-- added ability to Scale and Offset (for all types of objects) to just
-press Enter after entering a value in the Entry just like in Tool Transform
-- added capability in Tool Transform to mirror(flip) around a certain Point.
-The point coordinates can either be entered by hand or they can be captured
-by left clicking while pressing key "SHIFT" and then clicking the Add button
-- added the .ROL extension when saving Machine Code
-- replaced strings that reference to G-Code from G-Code to CNC Code
-- added capability to open a project by serving the path/project_name.FlatPrj as a parameter
-to FlatCAM.py
-
-15.09.2018
-
-- removed dwell line generator and included dwell generation in the postprocessor files
-- added a proposed RML1 Roland_MDX20 postprocessor file.
-- added a limit of 15mm/sec (900mm/min) to the feedrate and to the feedrate_rapid. Anything faster than this
-will be capped to 900mm/min regardless what is entered in the program GUI. This is because Roland MDX-20 has
-a mechanical limit of the speed to 15mm/sec (900mm/min in GUI)
-
-14.09.2018
-- remade the Double Sided Tool so it now include mirroring of Excellon and Geometry Objects along Gerber.
-Made adding points easier by adding buttons to GUI that allow adding the coordinates captured by
-left mouse click + SHIFT key
-- added a few fixes in code to the other FlatCAM tools regarding reset_fields() function. The issue
-was present when clicking New Project entry in Menu -> File.
-- FIXED: fix adding/updating bounding box coords for the mirrored objects in Double side Tool.
-- FIXED: fix the bounding box values from within FlatCAM objects, upon units change.
-- fixed issue with running again the constructor of the drawing tools after the tool action was complete,
-in Geometry Editor
-- fixed issue with Tool tab not closed after Text Input tool is finished.
-- fixed issue with TEXT to GEOMETRY tool, the resulting geometry was not scaled depending of current units
-- fixed case when user is clicking on the canvas to place a Font Geometry without clicking apply button first
-or the Font Geometry is empty, in Geometry Editor - > Text Input tool
-- reworked Measuring Tool by adding more information's (START, STOP point coordinates) and remade the 
-strings
-- added to Double Sided Tool the ability to use as reference box Excellon and Geometry Objects
-
-12.09.2018
-
-- fixed Excellon Object class such that Excellon files that have both drills and slots are supported
-- remade the GUI interface for the Excellon Object in a more compact way; added a column with slots numbers
-(if any) along the drills numbers so now there is only one tool table for drills and slots.
-- remade the GUI in Preferences and removed unwanted stretch that was broken the layout.
-- if for a certain tool, the slots number is zero it will not be displayed
-- reworked Text to Geometry feature to work in Linux and MacOS
-- remade the Text to Geometry so font collection process is done once at app start-up improving the performance
-
-
-09.09.2018
-
-- added TEXT ENTRY SUPPORT in Geometry Editor. It will convert strings of True Type Fonts to geometry. 
-The actual dimensions are approximations because font size is in points and not in metric or inch units.
-For now full support is limited to Windows. In Linux/MacOS only the fonts for which the font name is the same 
-as the font filename are supported. Italic and Bold functions may not work in Linux/MacOS.
-- solved bug: some Drawing menu entries not having connected functions
-
-28.08.2018
-
-- fixed Gerber parser so now G01 "moving" rectangular 
-aperture is supported.
-- fixed import_svg function; it can import SVG as geometry (solved bug)
-- fixed import_svg function; it can import SVG as Gerber (it did not work previously)
-- added menu entry's for SVG import as Gerber and separated import as Geometry
-
-27.08.2018
-
-- fixed Gerber parser so now FlatCAM can load Gerber files generated by Mentor Graphics EDA programs.
-
-26.08.2018
-
-- added awareness for missing coordinates in Gerber parsing. It will try to use the previous coordinates but if there
-are not any those lines will be ignored and an Warning will be printed in Tcl Shell.
-- fixed TCL commands AlignDrillGrid and DrilCncJob
-- added TCL script file load_and_run support in GUI
-- made the tool_table in Excellon to automatically adjust the table height depending on the number of rows such that
-all the rows will be displayed.
-- structural changes in the Excellon build_ui()
-- icon changes and menu compress
-
-23.08.2018
-
-- added Excellon routing support
-- solved a small bug that crippled Excellon slot G85 support when the coordinates
-are with period.
-- changed the way selection is done in Geometry Editor; now it should work
-in all cases (although the method used may be computationally intensive,
-because sometimes you have to click twice to make selection if you do it too fast)
-
-21.08.2018
-
-- added Excellon slots support when using G85 command for generation of
-the slots file. Inspired from the work of @mgix. Thanks.
-Routing format support for slots will follow. 
-- minor bug solved: option "Cut over 1st pt" now has same name both in
-Preferences -> Geometry Options and in Selected tab -> Geomety Object.
-Solves #3
-- added option to select Climb or Conventional Milling in Gerber Object options
-Solves #4
-- made "Combine passes" option to be saved as an app preference
-- added Generate Exteriors Geo and Generate Interiors Geo buttons in the
-Gerber Object properties
-- added configuration for the number of steps used for Gerber circular aperture
-linear approximation. The option is in Preferences -> Gerber Options
-- added configuration for the number of steps used for Gcode circular aperture
-linear approximation. The option is in Preferences -> CNCjob Options
-- added configuration for the number of steps used for Geometry circular aperture
-linear approximation. The option is in Preferences -> Geometry Options. It is used 
-on circles/arcs made in Geometry Editor and for other types of geometries generated in 
-the app.
-
-
-17.07.2018
-
-- added the required packages in Requirements.txt file
-- added required packages in setup_ubuntu.sh file
-- added color control over almost all the colors in the application; those
-settings are in Menu -> Edit -> Preferences -> General Tab
-- added configuration of which mouse button to be used when panning (MMB or RMB)
-- fixed bug with missing 'drillz' parameter in function generate_from_excellon_by_tool()
-(credits for finding it goes to Stefan Smith https://bitbucket.org/stefan064/)
-- load Factory defaults in Preferences will load the defaults that are used just after
-first install. Load Defaults option in Preferences will load the User saved Defaults.
-
-03.07.2018
-
-- fixed bug in rotate function that didn't update the bounding box of the
-modified object (rotated) due of not emitting the right signal parameter.
-- removed the Options tab from the Notebook (the left area where is located
-also the Project tab). Replaced it with the Preferences Tab launched with
-Menu -> Edit -> Preferences
-- when FlatCAM is used under MacOS, multiple selection of shapes in Editor
-mode is done using SHIFT key instead of CTRL key due of MacOS interpreting
-CTRL+LMB_click as a RMB click
-- when in Editor, clicking not on a shape, reset the index of selected shapes
-to zero
-- added a new Tab in the Plot Area named Gcode Editor. It allow the user to
-edit the Gcode and then Save it or Print it.
-- added a fix so the 'preamble' Gcode is correctly inserted between the
-comments header and the actual GCODE
-- added Find function in G-Code Editor
-
-
-27.06.2018
-
-- the Plot Area tab is changing name to "Editor Area" when the Editor is
-activated and returns to the "Plot Area" name upon exiting the Editor
-- made the labels shorter in Transform Tool in anticipation of
-Options Tab removal from Notebook and replacing it with Preferences
-- the Excellon Editor is not finished (not even started yet) so the
-Plot Area title should stay "Plot Area" not change to "Editor Area" when
-attempting to edit an Excellon file. Solved.
-- added a header comment block in the generated Gcode with useful
-information's
-- fixed issue that did not allow the Nightly's to be run in
-Windows 7 x64. The reason was an outdated DLL file (freetype.dll) used
-by Vispy python module.
-
-
-25.06.2018
-
-- "New" menu entry in Menu -> File is renamed to "New Project"
-- on "New Project" action, all the Tools are reinitialized so the Tools
-tab will work as expected
-- fixed issue in Film Tool when generating black film
-- fixed Measurement Tool acquiring and releasing the mouse/key events
-- fixed cursor shape is updated on grid_toggle
-- added some infobar messages to show the user when the Editor was
-activated and when it was closed (control returned to App).
-- added thread usage for Film tool; now the App is no longer blocked on
-film generation and there is a visual clue that the App is working
-
-22.06.2018
-
-- added export PNG image functionality and menu entry in
-Menu -> File -> Export PNG ...
-- added a command to set focus on canvas inside the mouve move event
-handler; once the mouse is moved the focus is moved to canvas so the
-shortcuts work immediatly.
-- solved a small bug when using the 'C' key to copy name of the selected
-object to clipboard
-
-- fixed millholes() function and isolate() so now it works even when the
-tool diameter is the same as the hole diameter.
-
-Actually if the passed value to  the buffer() function is zero, I
-artificially add a value of 0.0000001 (FlatCAM has a precision of
-6 decimals so I use a tenth of that value as a pseudo "zero")
-because the value has to be positive. This may have solved for some use
-cases the user complaints that on clearing the areas of copper there is
-still copper leftovers.
-
-- added shortcut "SHIFT+G" to toggle the axis presence. Useful when one
-wants to save a PNG file.
-- changed color of the grid from 'gray' to 'dimgray'
-
-- the selection shape is deleted when the object is deleted
-
-- the plot area is now in a TAB.
-- solved bug that allowed middle button click to create selection
-- fixed issue with main window geometry restore (hopefully).
-- made view toolbar to be hidden by default as it is not really needed
-(we have the functions in menu, zoom is done with mouse wheel, and there
-is also the canvas context menu that holds the functionality)
-- remade the GUIElements.FCInput() and made a GUIElements.FCTab()
-- on visibility plot toogle the selection shape is deleted
-
-- made sure that on panning in Geometry editor, the context menu is not
-displayed
-- disabled App shortcut keys on entry in Geometry Editor so only the
-local shortcut keys are working
-
-- deleted metric units in canvas context menu
-- added protection so object deletion can't be done until Geometry
-Editor session is finished. Solved bug when the shapes on Geometry
-Editor were not transfered to the New_geometry object yet and the
-New_Geometry object is deleted. In this case the drawn shapes are left
-in a intermediary state on canvas.
-
-- added selection shape drawing in Geometry Editor preserving the
-current behavior: click to select, click on canvas clear selection,
-CTRL+click add to selection new shape but remove from selection
-if already selected. Drag LMB from left to right select enclosed
-shapes, drag LMB from right to left select touching shapes. Now the
-selection is made based on
-- added info message to be displayed in infobar, when a object is
-renamed
-
-20.06.2018
-
-- there are two types of mouse drag selection (rectangle selection)
-If there is a rectangle selection from left to right, the color of the
-selection rectangle is blue and the selection is "enclosing" - this
-means that the object to be selected has to be enclosed by the selecting
-blue rectangle shape.
-If there is a rectangle selection fro right to left, the color of the
-selection rectangle is green and the selection is "touching" - this
-means that it's enough to touch with the selecting green rectangle the
-object(s) to be selected so they become selected
-- changed the modifier key required to be pressed when LMB is ckicked
-over canvas in order to copy to clipboard the coordinates of the click,
-from CTRL to SHIFT. CTRL will be used for multiple selection.
-- change the entry names in the canvas context menu
-- disconnected the app mouse event functions while in geometry editor
-since the geometry editor has it's own mouse event functions and there
-was interference between object and geometry items. Exception for the
-mouse release event so the canvas context menu still work.
-- solved a bug that did not update the obj.options after a geometry
-object was edited in geometry editor
-- solved a bug in the signal that saved the position and dimensions of
-the application window.
-- solved a bug in app.on_preferences() that created an error when run
-in Linux
-
-18.06.2018 Update 1
-
-- reverted the 'units' parameter change to 'global_units' due of a bug
-that did not allow saving of the project
-- modified the camlib transform (rotate, mirror, scale etc) functions
-so now they work with Gerber file loaded with 'follow' parameter
-
-18.06.2018
-
-- reworked the Properties context menu option to a Tool that displays
-more informations on the selected object(s)
-- remade the FlatCAM project extension as .FlatPrj
-- rearranged the toolbar menu entries to a more properly order
-- objects can now be selected on canvas, a blue polygon is drawn around
-when selected
-- reworked the Tool Move so it will work with the new canvas selection
-- reworked the Measurement Tool so it will work with the new canvas
-selection
-- canvas selection can now be done by dragging left mouse boutton and
-creating a selection box over the objects
-- when the objects are overlapped on canvas, the mouse click
-selection works in a circular way, selecting the first, then the second,
-then ..., then the last and then again the first and so on.
-- double click on a object on canvas will open the Selected Tab
-- each object store the bounding box coordinates in the options dict
-- the bbox coordinates are updated on the obj options when the object
-is modified by a transform function (rotate, scale etc)
-
-
-15.06.2018
-
-- the selection marker when moving is now a semitransparent Polygon
-with a blue border
-- rectified a small typo in the ToolTip for Excellon Format for
-Diptrace excellon format; from 4:2 to 5:2
-- corrected an error that cause no Gcode could be saved
-
-
-14.06.2018
-
-- more work on the contextual menu
-- added Draw context menu
-- added a new tool that bring together all the transformations, named
-Transformation Tool (Rotate, Skew, Scale, Offset, Flip)
-- added shorcut key 'Q' which toggle the units between IN and MM
-- remade the Move tool, there is now a selection box to show where the
-move is done
-- remade the Measurement tool, there is now a line between the start
-point of measurement and the end point of the measurement.
-- renamed most of the system variables that have a global app effect to
-global_name where name is the parameter (variable)
-
-
-9.06.2018
-
-- reverted to PyQt4. PyQt5 require too much software rewrite
-- added calculators: units_calculator and V-shape Tool calculator
-- solved bug in Join Excellon
-- added right click menu over canvas
-
-6.06.2018 Update
-
-- fixed bug: G-Code could not be saved
-- fixed bug: double clicking a category in Project Tab made the app to
-crash
-- remade the bounds() function to work with nested lists of objects as
-per advice from JP which made the operation less performance taxing.
-- added shortcut Shift+R that is complement to 'R'
-- shorcuts 'R' and 'SHIFT+R' are working now in steps of 90 degrees
-instead of previous 45 degrees.
-- added filters in the open ... FlatCAM projects are saved automatically
-as *.flat, the Gerber files have few categories. So the Excellons and
-G-Code and SVG.
-
-6.06.2018
-
-- remade the transform functions (rotate, flip, skew) so they are now
-working for joined objects, too
-- modified the Skew and Rotate comamands: if they are applied over a
-selection of objects than the origin point will be the center of the
-biggest bounding box. That allow for perfect sync between the selected
-objects
-- started to modify the program so the exceptions are handled correctly
-- solved bug where a crash occur when ObjCollection.setData didn't
-return a bool value
-- work in progress for handling situations when a different file is
-loaded as another (like loading a Gerber file using Open Excellon
- commands.
-- added filters on open_gerber and open_excellon Dialogs. There is still
-the ability to select All Files but this should reduce the cases when
-the user is trying to oprn a file from a wrong place.
-
-4.06.2018
-
-- finished PyQt4 to PyQt4 port on the Vispy variant (there were some changes
-compared with the Matplotlib version for which the port was finished
-some time ago)
-- added Ctrl+S shortcut for the Geometry Editor. When is activated it will
-save de geometry ("update") and return to the main App.
-- modified the Mirror command for the case when multiple objects are
-selected and we want to mirror all together. In this case they should mirror
-around a bounding box to fill all.
-
-3.06.2018
-
-- removed the current drill path optimizations as they are inefficient
-- implemented Google OR-tools drill path optimization in 2 flavors;
-Basic OR-tools TSP algorithm and OR-Tools Metaheuristics Guided Local Path
-- Move tool is moved to Menu -> Edit under the name Move Object
-
-- solved some internal bugs (info command was creating an non-fatal
-error in PyQt, regarding using QPixMaps outside GUI thread
-- reworked camlib number parsing (still had some bugs)
-- working in porting the application from usage of PyQt4 to PyQt4
-- added TclCommands save_sys and list_sys. save_sys is saving all the
-system default parameters and list_sys is listing them by the first
-letters. listsys with no arguments will list all the system parameters.
-
-29.05.2018
-
-- modified the labels for the X,Y and Dx,Dy coordinates
-- modified the menu entries, added more icons
-- added initial work on a Excellon Editor
-- modified the behavior of when clicking on canvas the coordinates were
-copied to cliboard: now it is required to press CTRL key for this to
-happen, and it will only happen just for left mouse button click
-- removed the autocopy of the object name on new object creation
-- remade the Tcl commands drillcncjob and cncjob
-- added fix so the canvas is focused on the start of the program,
-therefore the shortcuts work without the need for doing first a click
-on canvas.
-
-
-
-28.05.2018
-
-- added total drill count column in Excellon Tool Table which displays the
-total number of drills
-- added aliases in panelize Tool (pan and panel should work)
-- modified generate_milling method which had issues from the Python3 port
-(it could not sort the tools due of dict to dict comparison no longer
-possible).
-- modified the 'default' postprocessor in order to include a space
-between the value of Xcoord and the following Y
-- made optional the using of threads for the milling command; by default
-it is OFF (False) because in the current configuration it creates issues
-when it is using threads
-- modified the Panelize function and Tcl command Panelize. It was having
-issues due to multithreading (kept trying to modify a dictionary in
-redraw() method)and automatically selecting the last created object
-(feature introduced by me). I've added a parameter to
-the new_object method, named autoselected (by default it is True) and
-in the panelize method I initialized it with False.
-By initializing the plot parameter with False for the temporary objects,
-I have increased dramatically the  generation speed of the panel because
-now the temporary object are no longer ploted which consumed time.
-- replaced log.warn() with log.warning() in camlib.py. Reason: deprecated
-- fixed the issue that the "Defaults" button was having no effect when
-clicked and Options Combo was in Project Options
-- fixed issue with Tcl Shell loosing focus after each command, therefore
-needing to click in the edit line before we type a new command (borrowed
-from @brainstorm
-- added a header in the postprocessor files mentioning that the GCODE
-files were generated by FlatCAM.
-- modified the number of decimals in some of the line entries to 4.
-- added an alias for the millholes Tcl Command: 'mill'
-
-27.04.2018
-
-- modified the Gerber.scale() function from camlib.py in order to
-allow loading Gerber files with 'follow' parameter in other units
-than the current ones
-- snap_max_entry is disabled when the DRAW toolbar is disabled (previous
-fix didn't work)
-- added drill count column in Excellon Tool Table which displays the
-total number of drills for each tool
-
-- added a new menu entry in Menu -> EDIT named "Join Excellon". It will
-merge a selection of Excellon files into a new Excellon file
-- added menu stubs for other Excellon based actions
-
-- solved bug that was not possible to generate film from joined geometry
-- improved toggle active/inactive of the object through SPACE key. Now
-the command works not only for one object but also for a selection
-
-26.05.2018
-
-- made conversion to Python3
-- added Rtree Indexing drill path optimization
-- added a checkbox in Options Tab -> App Defaults -> Excellon
-Group named Excellon Optim. Type from which it can be selected
-the default optimization type: TS stands for Travelling
-Salesman algorithm and Rtree stands for Rtree Indexing
-- added a checkbox on the Grid Toolbar that when checked
-(default status is checked) whatever value entered in the GridX entry
-will be used instead of the now disabled GridY entry
-- modified the default behavior on when a line_entry is clicked.
-Now, on each click on a line_entry, the content is automatically
-selected.
-- snap_max_entry is disabled when the DRAW toolbar is disabled
-
-24.05.2015
-
-- in Geometry Editor added a initial form of Rotate Geometry command in
-toolbar
-- changed the way the geometry is finished if it requires a key: before
-it was using key 'Space' now it uses 'Enter'
-- added Shortcut for Rotate Geometry to key 'Space'
-- after using a tool in Geometry Editor it automatically defaults to
-'Select Tool'
-
-23.05.2018
-
-Added key shortcut's in FlatCAMApp and in Geometry Editor.
-
-FlatCAMApp shortcut list:
-1      Zoom Fit
-2      Zoom Out
-3      Zoom In
-C      Copy Obj_Name
-E      Edit Geometry (if selected)
-G      Grid On/Off
-M      Move Obj
-
-N      New Geometry
-R      Rotate
-S      Shell Toggle
-V      View Fit
-X      Flip on X_axis
-Y      Flip on Y_axis
-~      Show Shortcut List
-
-Space:   En(Dis)able Obj Plot
-CTRL+A   Select All
-CTRL+C   Copy Obj
-CTRL+E   Open Excellon File
-CTRL+G   Open Gerber File
-CTRL+M   Measurement Tool
-CTRL+O   Open Project
-CTRL+S   Save Project As
-Delete   Delete Obj'''
-
-
-Geometry Editor Key shortcut list:
-A       Add an 'Arc'
-C       Copy Geo Item
-G       Grid Snap On/Off
-K       Corner Snap On/Off
-M       Move Geo Item
-
-N       Add an 'Polygon'
-O       Add a 'Circle'
-P       Add a 'Path'
-R       Add an 'Rectangle'
-S       Select Tool Active
-
-
-~        Show Shortcut List
-Space:   Rotate Geometry
-Enter:   Finish Current Action
-Escape:  Abort Current Action
-Delete:  Delete Obj
-
-22.05.2018
-
-- Added Marlin postprocessor
-- Added a new entry into the Geometry and Excellon Object's UI:
-Feedrate rapid: the purpose is to set a feedrate for the G0
-command that some firmwares like Marlin don't intepret as
-'move with highest speed'
-- FlatCAM was not making the conversion from one type of units to
-another for a lot of parameters. Corrected that.
-- Modified the Marlin Postprocessor so it will generate the required
-GCODE.
-
-21.05.2018
-
-- added new icons for menu entries
-- added shortcuts that work on the Project tab but also over
-Plot. Shorcut list is accesed with shortcut key '~' sau '`'
-- small GUI modification: on each "New File" command it will switch to
-the Project Tab regardless on which tab we were.
-
-- removed the global shear entries and checkbox as they can be
-damaging and it will build effect upon effect, which is not good
-- solved bug in that the Edit -> Shear on X (Y)axis could adjust
-only in integers. Now the angle can be adjusted in float with
-3 decimals.
-- changed the tile of QInputDialog to a more general one
-- changed the "follow" Tcl command to the new format
-- added a new entry in the Menu -> File, to open a Gerber with
-the follow parameter = True
-- added a new checkbox in the Gerber Object Selection Tab that
-when checked it will create a "follow" geometry
-- added a few lines in Mill Holes Tcl command to check if there are
-promises and raise an Tcl error if there are any.
-- started to modify the Export_Svg Tcl command
-
-20.05.2018
-
-- changed the interpretation of the axis for the rotate and skew commands.
-Actually I reversed them to reflect reality.
-- for the rotate command a positive angle now rotates CW. It was reversed.
-- added shortcuts (for outside CANVAS; the CANVAS has it's own set of shortcuts)
-CTRL+C will copy to clipboard the name of the selected object
-CTRL+A will Select All objects
-
-"X" key will flip the selected objects on X axis
-
-"Y" key will flip the selected objects on Y axis
-
-"R" key will rotate CW with a 45 degrees step
-- changed the layout for the top of th Options page. Added a checkbox and entries
-for parameters for skew command. When the checkbox is checked it will save (and
-load at the next startup of the program) the option that at each CNCJob generation
-(be it from Excellon or Geometry) it will perform the Skew command with the 
-parametrs set in the nearby field boxes (Skew X and Skey Y angles).
-It is useful in case the CNC router is not perfectly alligned between the X and Y axis
-
-- added some protection in case the skew command receive a None parameter
-
-- BUG solved: made an UGLY (really UGLY) HACK so now, when there is a panel geometry
-generated from GUI, the project WILL save. I had to create a copy of the generated 
-panel geometry and delete the original panel geometry. This way there is no complain
-from JSON module about circular reference.
-
-Supplimentary:
-- removed the Save buttons previously added on each Group in Application Defaults.
-Replaced them with a single Save button that stays always on top of the Options TAB
-- added settings for defaults for the Grid that are persistent
-- changed the default view at FlatCAM startup: now the origin is in the center of the screen
-
-
-19.05.2018
-
-- last object that is opened (created) is always automatically selected and
-the name of the object is automatically copied to clipboard; useful when
-using the TCL command :)
-
-- added new commands in MENU -> EDIT named: "Copy Object" and
-"Copy Obj as Geom". The first command will duplicate any object (Geometry,
-Gerber, Excellon).
-The second command will duplicate the object as a geometry. For example,
-holes in Excello now are just circles that can be "painted" if one wants it.
-
-- added new Tool named ToolFreeformCutout. It does what it says, it will
-make a board cutout from a "any shape" Gerber or Geometry file
-
-- solved bug in the TCL command "drillcncjob" that always used the endz
-parameter value as the toolchangez parameter value and for the endz value
-used a default value = 1
-
-- added postprocessor name into the TCL command "drillcncjob" parameters
-
-- when adding a new geometry the default name is now: "New_Geometry" instead
-of "New Geometry". TCL commands don't handle the spaces inside the name and
-require adding quotes.
-
-- solved bug in "cncjob" TCL command in which it used multidepth parameter as
-always True regardless of the argument provided
-
-- added a checkbox for Multidepth in the Options Tab -> Application Defaults
-
-
-18.05.2018
-
-- added an "Defaults" button in Excellon Defaults Group; it loads the
-following configuration (Excellon_format_in 2:4, Excellon_format_mm 3:3,
-Excellon_zeros LZ)
-- added Save buttons for each Defaults Group; in the future more 
-parameters will be propagated in the app, for now they are a few
-- added functions for Skew on X axis and for Skew on Y menu stubs.
-Now, clicking on those Menu -> Options -> Transform Object menu entries
-will trigger those functions
-- added a CheckBox button in the Options Tab -> Application Defaults that control
-the behaviour of the TCL shell: checking it will make the TCL shell window visible
-at each start-up, unchecking it the TCL shell window will be hidden until needed
-- Depth/pass parameter from Geometry Object CNC Job is now in the
-defaults and it will keep it's value until changed in the Application
-Defaults.
-
-17.05.2018
-
-- added messages box for the Flip commands to show error in case there
-is no object selected when the command is executed
-- added field entries in the Options TAB - > Application Defaults for the
-following newly introduced parameters: 
-excellon_format_upper_in
-excellon_format_lower_in
-excellon_format_upper_mm
-excellon_format_lower_mm
-
-The ones with upper indicate how many digits are allocated for the units
-and the ones with lower indicate how many digits from coordinates are 
-alocated for the decimals.
-
-[  Eg: Excellon format 2:4 in INCH
-   excellon_format_upper_in = 2
-   excellon_format_lower_in = 4
-where the first 2 digits are for units and the last 4 digits are
-decimals so from a number like 235589 we will get a coordinate 23.5589
-]
-
-- added Radio button in the Options TAB - > Application Defaults for the
-Excellon_zeros parameter
-
-After each change of those parameters the user will have to press 
-"Save defaults" from File menu in order to propagate the new values, or
-wait for the autosave to kick in (each 20sec).
-
-Those parameters can be set in the set_sys TCL command.
-
-15.05.2018
-- modified SetSys TCL command: now it can change units
-- modified SetSys TCL command: now it can set new parameters:
-excellon_format_mm and excellon_format_in. the first one is when the
-excellon units are MM and the second is for when the excellon units are
-in INCH. Those parameters can be set with a number between 1 and 5 and it
-signify how many digits are before coma.
-- added new GUI command in EDIT -> Select All. It will select all
-objects on the first mouse click and on the second will deselect all
-(toggle action)
-- added new GUI commands in Options -> Transform object. Added Rotate selection,
-Flip on X axis of the selection and Flip on Y axis of the selection
-For the Rotate selection command, negative numbers means rotation CCW and
-positive numbers means rotation CW.
-
-- cleaned up a bit the module imports
-- worked on the excellon parsing for the case of trailing zeros.
-If there are more than 6digits in the 
-coordinates, in case that there is no period, now the software will 
-identify the issue and attempt to correct it by dividing the coordinate 
-further by 10 for each additional digit over 6. If the number of digits
-is less than 6 then the software will multiply by 10 the coordinates
-
-14.05.2018
-
-- fixed bug in Geometry CNCJob generation that prevented generating 
-the object
-- added GRBL 1.1 postprocessor and Laser postprocessor (adapted from 
-the work of MARCO A QUEZADA)
-
-
-13.05.2018
-
-- added postprocessing in correct form
-- added the possibility to select an postprocessor for Excellon Object
-- added a new postprocessor, manual_toolchange.py. It allows to change 
-the tools and adjust the drill tip to touch the surface manually, always
-in the X=0, Y=0, Z = toolchangeZ coordinates.
-- fixed drillcncjob TCL command by adding toolchangeZ parameter
-- fixed the posprocessor file template 'default.py' in the toolchange
-command section
-- after I created a feature that the message in infobar is cleared by
-moving mouse on canvas, it generated a bug in TCL shell: everytime 
-mouse was moved it will add a space into the TCL read only section.
-Now this bug is fixed.
-- added an EndZ parameter for the drillcncjob and cncjob TCL commands: it
-will specify at what Z value to park the CNC when job ends
-- the spindle will be turned on after the toolchange and it will be turned off
-just before the final end move.
-
-Previously:
-- added GRID based working of FLATCAM
-- added Set Origin command
-- added FilmTool, PanelizeTool GUI, MoveTool
-- and others
-
-
-24.04.2018
-
-- Remade the Measurement Tool: it now ask for the Start point of the measurement and then for the Stop point. After it will display the measurement until we left click again on the canvas and so on. Previously you clicked the start point and reset the X and Y coords displayed and then you moved the mouse pointer wherever you wanted to measure, but moving the mouse from there you lost the measurement.
-- Added Relative measurement on the main plot
-- Now both the measuring tool and the relative measurement will work only with the left click of the mouse button because middle mouse click and right mouse click are used for panning
-- Renamed the tools files starting with Tool so they are grouped (in the future they may have their own folder like for TCL Commands)
-
-- Commented some shortcut keys and functions for features that are not present anymore or they are planned to be in the future but unfinished (like buffer tool, paint tool)
-- minor corrections regarding PEP8 (Pycharm complains about the m)
-- solved bug in TclCommandsSetSys.py Everytime that the command was executed it complain about the parameter not being in the list (something like this). There was a missing “else:”
-- when using the command “set_sys excellon_zeros” with parameter in lower case (either ‘l’ or ‘t’) now it is always written in the defaults file as capital letter
-
-- solved a bug introduced by me: when apertures macros were detected in Excellon file, FlatCam will complain about missing dictionary key “size”. Now it first check if the aperture is a macro and perform the check for zero value only for apertures with “size” key
-- solved a bug that didn't allowed FC to detect if Excellon file has leading zeros or trailing zeros
-- solved a bug that FC was searching for char ‘%’ that signal end of Excellon header even in commented lines (latest versions of Eagle end the commented line with a ‘%’)
-
-
-============================================
-
-This fork features:
-
-- Added buttons in the menu bar for opening of Gerber and Excellon files;
-- Reduced number of decimals for drill bits to two decimals;
-- Updated make_win32.py so it will work with cx_freeze 5.0.1 
-- Added capability so FlatCAM can now read Gerber files with traces having zero value (aperture size is zero);
-- Added Paint All / Seed based Paint functions from the JP's FlatCAM;
-- Added Excellon move optimization (travelling salesman algorithm) cherry-picked from David Kahler: https://bitbucket.org/dakahler/flatcam
-- Updated make_win32.py so it will work with cx_freeze 5.0.1 Corrected small typo in DblSidedTool.py
-- Added the TCL commands in the new format. Picked from FLATCAM master.
-- Hack to fix the issue with geometry not being updated after a TCL command was executed. Now after each TCL command the plot_all() function is executed and the canvas is refreshed.
-- Added GUI for panelization TCL command
-- Added GUI tool for the panelization TCL command: Changed some ToolTips.
-
-
-============================================
-
-Previously added features by Dennis
-
-- "Clear non-copper" feature, supporting multi-tool work.
-- Groups in Project view.
-- Pan view by dragging in visualizer window with pressed MMB.
-- OpenGL-based visualizer.
-
+You can download all the required wheels files into a folder (e.g D:\my_folder) and install them from Command Prompt like this:
+cd D:\my_folder
+and for each wheel file (*.whl) run:
+D:\my_folder\> pip install --upgrade package_from_requirements.whl
+
+Run FlatCAM beta from the installation folder (e.g D:\FlatCAM_beta) in the Command Prompt with the following command:
+cd D:\FlatCAM_beta
+python FlatCAM.py
+
+2. Linux
+- make sure that Python 3.8 is installed on your OS and that the command: python3 -V confirm it
+- verify that the pip package is installed for your Python installation (e.g 3.8) by running the command pip3 -V. 
+If it is not installed, install it. In Ubuntu-like OS's it is done like this: 
+sudo apt-get install python3-pip 
+or:
+sudo apt-get install python3.8-pip
+- verify that the file setup_ubuntu.sh has Linux line-endings (LF) and that it is executable (chmod +x setup_ubuntu.sh)
+- run the file setup_ubuntu.sh and install all the dependencies with the command: ./setup_ubuntu.sh
+- if the previous command is successful and has no errors, run FlatCAM with the command: python3 FlatCAM.py
+
+3. MacOS
+Instructions from here: https://gist.github.com/natevw/3e6fc929aff358b38c0a#gistcomment-3111878
+
+- create a folder to hold the sources somewhere on your HDD:
+mkdir FlatCAM
+
+- unzip in this folder the sources downloaded from https://bitbucket.org/jpcgt/flatcam/downloads/
+Using commands (e.g using the sources for FlatCAM beta 8.991):
+cd ~/FlatCAM
+wget https://bitbucket.org/jpcgt/flatcam/downloads/FlatCAM_beta_8.991_sources.zip
+unzip FlatCAM_beta_8.991_sources.zip
+cd FlatCAM_beta_8.991_sources
+
+- check if Homebrew is installed:
+xcode-select --install
+ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+
+- install dependencies:
+brew install pyqt
+python3 -m ensurepip
+python3 -m pip install -r requirements.txt
+
+- run FlatCAM
+python3 FlatCAM.py

Plik diff jest za duży
+ 467 - 138
camlib.py


+ 109 - 77
flatcamEditors/FlatCAMExcEditor.py

@@ -124,7 +124,7 @@ class FCDrillAdd(FCShapeTool):
         self.draw_app.app.jump_signal.disconnect()
 
     def clean_up(self):
-        self.draw_app.selected = list()
+        self.draw_app.selected = []
         self.draw_app.tools_table_exc.clearSelection()
         self.draw_app.plot_all()
 
@@ -359,7 +359,7 @@ class FCDrillArray(FCShapeTool):
         self.draw_app.app.jump_signal.disconnect()
 
     def clean_up(self):
-        self.draw_app.selected = list()
+        self.draw_app.selected = []
         self.draw_app.tools_table_exc.clearSelection()
         self.draw_app.plot_all()
 
@@ -398,7 +398,7 @@ class FCSlot(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_slot.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
@@ -538,7 +538,7 @@ class FCSlot(FCShapeTool):
 
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
 
         try:
@@ -562,7 +562,7 @@ class FCSlot(FCShapeTool):
         self.draw_app.app.jump_signal.disconnect()
 
     def clean_up(self):
-        self.draw_app.selected = list()
+        self.draw_app.selected = []
         self.draw_app.tools_table_exc.clearSelection()
         self.draw_app.plot_all()
 
@@ -600,7 +600,7 @@ class FCSlotArray(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_array.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
@@ -677,9 +677,8 @@ class FCSlotArray(FCShapeTool):
                 self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' %
                                               _("The value is not Float. Check for comma instead of dot separator."))
                 return
-        except Exception as e:
-            self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' %
-                                          _("The value is mistyped. Check the value."))
+        except Exception:
+            self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % _("The value is mistyped. Check the value."))
             return
 
         if self.slot_array == 'Linear':
@@ -829,7 +828,7 @@ class FCSlotArray(FCShapeTool):
 
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
 
         # add the point to slots if the diameter is a key in the dict, if not, create it add the drill location
@@ -888,7 +887,7 @@ class FCSlotArray(FCShapeTool):
         self.draw_app.app.jump_signal.disconnect()
 
     def clean_up(self):
-        self.draw_app.selected = list()
+        self.draw_app.selected = []
         self.draw_app.tools_table_exc.clearSelection()
         self.draw_app.plot_all()
 
@@ -1022,8 +1021,7 @@ class FCDrillResize(FCShapeTool):
                             self.geometry.append(DrawToolShape(new_poly))
                         else:
                             # unexpected geometry so we cancel
-                            self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' %
-                                                          _("Cancelled."))
+                            self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % _("Cancelled."))
                             return
 
                         # remove the geometry with the old size
@@ -1091,8 +1089,7 @@ class FCDrillResize(FCShapeTool):
                     except KeyError:
                         # if the exception happen here then we are not dealing with slots neither
                         # therefore something else is not OK so we return
-                        self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' %
-                                                      _("Cancelled."))
+                        self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % _("Cancelled."))
                         return
 
             # this simple hack is used so we can delete form self.draw_app.selected but
@@ -1128,7 +1125,7 @@ class FCDrillResize(FCShapeTool):
         self.draw_app.select_tool("drill_select")
 
     def clean_up(self):
-        self.draw_app.selected = list()
+        self.draw_app.selected = []
         self.draw_app.tools_table_exc.clearSelection()
         self.draw_app.plot_all()
 
@@ -1268,7 +1265,7 @@ class FCDrillMove(FCShapeTool):
             return DrawToolUtilityShape(ss_el)
 
     def clean_up(self):
-        self.draw_app.selected = list()
+        self.draw_app.selected = []
         self.draw_app.tools_table_exc.clearSelection()
         self.draw_app.plot_all()
 
@@ -1323,7 +1320,7 @@ class FCDrillCopy(FCDrillMove):
         self.draw_app.app.jump_signal.disconnect()
 
     def clean_up(self):
-        self.draw_app.selected = list()
+        self.draw_app.selected = []
         self.draw_app.tools_table_exc.clearSelection()
         self.draw_app.plot_all()
 
@@ -1334,16 +1331,16 @@ class FCDrillCopy(FCDrillMove):
 
 
 class FCDrillSelect(DrawTool):
-    def __init__(self, exc_editor_app):
-        DrawTool.__init__(self, exc_editor_app)
+    def __init__(self, draw_app):
+        DrawTool.__init__(self, draw_app)
         self.name = 'drill_select'
 
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
 
-        self.exc_editor_app = exc_editor_app
+        self.exc_editor_app = draw_app
         self.storage = self.exc_editor_app.storage_dict
         # self.selected = self.exc_editor_app.selected
 
@@ -1368,7 +1365,7 @@ class FCDrillSelect(DrawTool):
         else:
             mod_key = None
 
-        if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+        if mod_key == self.exc_editor_app.app.defaults["global_mselect_key"]:
             pass
         else:
             self.exc_editor_app.selected = []
@@ -1379,8 +1376,10 @@ class FCDrillSelect(DrawTool):
 
         try:
             for storage in self.exc_editor_app.storage_dict:
-                for sh in self.exc_editor_app.storage_dict[storage].get_objects():
-                    self.sel_storage.insert(sh)
+                # for sh in self.exc_editor_app.storage_dict[storage].get_objects():
+                #     self.sel_storage.insert(sh)
+                _, st_closest_shape = self.exc_editor_app.storage_dict[storage].nearest(pos)
+                self.sel_storage.insert(st_closest_shape)
 
             _, closest_shape = self.sel_storage.nearest(pos)
 
@@ -1417,37 +1416,41 @@ class FCDrillSelect(DrawTool):
             else:
                 mod_key = None
 
-            if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+            if mod_key == self.exc_editor_app.app.defaults["global_mselect_key"]:
                 if closest_shape in self.exc_editor_app.selected:
                     self.exc_editor_app.selected.remove(closest_shape)
                 else:
                     self.exc_editor_app.selected.append(closest_shape)
             else:
-                self.draw_app.selected = []
-                self.draw_app.selected.append(closest_shape)
+                self.exc_editor_app.selected = []
+                self.exc_editor_app.selected.append(closest_shape)
 
             # select the diameter of the selected shape in the tool table
             try:
-                self.draw_app.tools_table_exc.cellPressed.disconnect()
+                self.exc_editor_app.tools_table_exc.cellPressed.disconnect()
             except (TypeError, AttributeError):
                 pass
 
-            self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
+            # if mod_key == self.exc_editor_app.app.defaults["global_mselect_key"]:
+            #     self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
+            self.sel_tools.clear()
+
             for shape_s in self.exc_editor_app.selected:
                 for storage in self.exc_editor_app.storage_dict:
                     if shape_s in self.exc_editor_app.storage_dict[storage].get_objects():
                         self.sel_tools.add(storage)
 
+            self.exc_editor_app.tools_table_exc.clearSelection()
             for storage in self.sel_tools:
-                for k, v in self.draw_app.tool2tooldia.items():
+                for k, v in self.exc_editor_app.tool2tooldia.items():
                     if v == storage:
                         self.exc_editor_app.tools_table_exc.selectRow(int(k) - 1)
-                        self.draw_app.last_tool_selected = int(k)
+                        self.exc_editor_app.last_tool_selected = int(k)
                         break
 
-            self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+            # self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
 
-            self.draw_app.tools_table_exc.cellPressed.connect(self.draw_app.on_row_selected)
+            self.exc_editor_app.tools_table_exc.cellPressed.connect(self.exc_editor_app.on_row_selected)
 
         # delete whatever is in selection storage, there is no longer need for those shapes
         self.sel_storage = FlatCAMExcEditor.make_storage()
@@ -1733,7 +1736,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.linear_box.addLayout(self.linear_form)
 
         # Linear Drill Array direction
-        self.drill_axis_label = QtWidgets.QLabel('%s:'% _('Direction'))
+        self.drill_axis_label = QtWidgets.QLabel('%s:' % _('Direction'))
         self.drill_axis_label.setToolTip(
             _("Direction on which the linear array is oriented:\n"
               "- 'X' - horizontal axis \n"
@@ -2031,22 +2034,14 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         # ## Toolbar events and properties
         self.tools_exc = {
-            "drill_select": {"button": self.app.ui.select_drill_btn,
-                             "constructor": FCDrillSelect},
-            "drill_add": {"button": self.app.ui.add_drill_btn,
-                          "constructor": FCDrillAdd},
-            "drill_array": {"button": self.app.ui.add_drill_array_btn,
-                            "constructor": FCDrillArray},
-            "slot_add": {"button": self.app.ui.add_slot_btn,
-                         "constructor": FCSlot},
-            "slot_array": {"button": self.app.ui.add_slot_array_btn,
-                                "constructor": FCSlotArray},
-            "drill_resize": {"button": self.app.ui.resize_drill_btn,
-                             "constructor": FCDrillResize},
-            "drill_copy": {"button": self.app.ui.copy_drill_btn,
-                           "constructor": FCDrillCopy},
-            "drill_move": {"button": self.app.ui.move_drill_btn,
-                           "constructor": FCDrillMove},
+            "drill_select": {"button": self.app.ui.select_drill_btn, "constructor": FCDrillSelect},
+            "drill_add": {"button": self.app.ui.add_drill_btn, "constructor": FCDrillAdd},
+            "drill_array": {"button": self.app.ui.add_drill_array_btn, "constructor": FCDrillArray},
+            "slot_add": {"button": self.app.ui.add_slot_btn, "constructor": FCSlot},
+            "slot_array": {"button": self.app.ui.add_slot_array_btn, "constructor": FCSlotArray},
+            "drill_resize": {"button": self.app.ui.resize_drill_btn, "constructor": FCDrillResize},
+            "drill_copy": {"button": self.app.ui.copy_drill_btn, "constructor": FCDrillCopy},
+            "drill_move": {"button": self.app.ui.move_drill_btn, "constructor": FCDrillMove},
         }
 
         # ## Data
@@ -2068,7 +2063,6 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.new_drills = []
         self.new_tools = {}
         self.new_slots = []
-        self.new_tool_offset = {}
 
         # dictionary to store the tool_row and diameters in Tool_table
         # it will be updated everytime self.build_ui() is called
@@ -2180,6 +2174,43 @@ class FlatCAMExcEditor(QtCore.QObject):
             if option in self.app.options:
                 self.options[option] = self.app.options[option]
 
+        self.data_defaults = {
+            "plot": self.app.defaults["excellon_plot"],
+            "solid": self.app.defaults["excellon_solid"],
+
+            "operation": self.app.defaults["excellon_operation"],
+            "milling_type": self.app.defaults["excellon_milling_type"],
+
+            "milling_dia": self.app.defaults["excellon_milling_dia"],
+
+            "cutz": self.app.defaults["excellon_cutz"],
+            "multidepth": self.app.defaults["excellon_multidepth"],
+            "depthperpass": self.app.defaults["excellon_depthperpass"],
+            "travelz": self.app.defaults["excellon_travelz"],
+            "feedrate": self.app.defaults["geometry_feedrate"],
+            "feedrate_z": self.app.defaults["excellon_feedrate_z"],
+            "feedrate_rapid": self.app.defaults["excellon_feedrate_rapid"],
+            "tooldia": self.app.defaults["excellon_tooldia"],
+            "slot_tooldia": self.app.defaults["excellon_slot_tooldia"],
+            "toolchange": self.app.defaults["excellon_toolchange"],
+            "toolchangez": self.app.defaults["excellon_toolchangez"],
+            "toolchangexy": self.app.defaults["excellon_toolchangexy"],
+            "extracut": self.app.defaults["geometry_extracut"],
+            "extracut_length": self.app.defaults["geometry_extracut_length"],
+            "endz": self.app.defaults["excellon_endz"],
+            "endxy": self.app.defaults["excellon_endxy"],
+            "startz": self.app.defaults["excellon_startz"],
+            "offset": self.app.defaults["excellon_offset"],
+            "spindlespeed": self.app.defaults["excellon_spindlespeed"],
+            "dwell": self.app.defaults["excellon_dwell"],
+            "dwelltime": self.app.defaults["excellon_dwelltime"],
+            "ppname_e": self.app.defaults["excellon_ppname_e"],
+            "ppname_g": self.app.defaults["geometry_ppname_g"],
+            "z_pdepth": self.app.defaults["excellon_z_pdepth"],
+            "feedrate_probe": self.app.defaults["excellon_feedrate_probe"],
+            "optimization_type": self.app.defaults["excellon_optimization_type"]
+        }
+
         self.rtree_exc_index = rtindex.Index()
         # flag to show if the object was modified
         self.is_modified = False
@@ -2537,9 +2568,8 @@ class FlatCAMExcEditor(QtCore.QObject):
             # each time a tool diameter is edited or added
             self.olddia_newdia[tool_dia] = tool_dia
         else:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Tool already in the original or actual tool list.\n"
-                                 "Save and reedit Excellon if you need to add this tool. "))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Tool already in the original or actual tool list.\n" 
+                                                          "Save and reedit Excellon if you need to add this tool. "))
             return
 
         # since we add a new tool, we update also the initial state of the tool_table through it's dictionary
@@ -2579,16 +2609,12 @@ class FlatCAMExcEditor(QtCore.QObject):
                         deleted_tool_dia_list.append(float('%.*f' % (self.decimals, dd)))
                 else:
                     deleted_tool_dia_list.append(float('%.*f' % (self.decimals, dia)))
-        except Exception as e:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Select a tool in Tool Table"))
+        except Exception:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Select a tool in Tool Table"))
             return
 
         for deleted_tool_dia in deleted_tool_dia_list:
 
-            # delete de tool offset
-            self.exc_obj.tool_offset.pop(float(deleted_tool_dia), None)
-
             # delete the storage used for that tool
             storage_elem = FlatCAMGeoEditor.make_storage()
             self.storage_dict[deleted_tool_dia] = storage_elem
@@ -2789,7 +2815,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.new_drills = []
         self.new_tools = {}
         self.new_slots = []
-        self.new_tool_offset = {}
+
         self.olddia_newdia = {}
 
         self.shapes.enabled = True
@@ -2832,7 +2858,7 @@ class FlatCAMExcEditor(QtCore.QObject):
     def deactivate(self):
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
 
         # adjust the status of the menu entries related to the editor
@@ -2856,7 +2882,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                 self.app.ui.corner_snap_btn.setEnabled(False)
                 self.app.ui.snap_magnet.setVisible(False)
                 self.app.ui.corner_snap_btn.setVisible(False)
-            elif layout == 'compact':
+            else:
                 # self.app.ui.exc_edit_toolbar.setVisible(True)
 
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
@@ -2974,7 +3000,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         except (TypeError, AttributeError):
             pass
 
-        self.app.ui.popmenu_copy.triggered.connect(self.app.on_copy_object)
+        self.app.ui.popmenu_copy.triggered.connect(self.app.on_copy_command)
         self.app.ui.popmenu_delete.triggered.connect(self.app.on_delete)
         self.app.ui.popmenu_move.triggered.connect(self.app.obj_move)
 
@@ -3262,7 +3288,6 @@ class FlatCAMExcEditor(QtCore.QObject):
                     self.edited_obj_name += "_1"
             else:
                 self.edited_obj_name += "_edit"
-        self.new_tool_offset = self.exc_obj.tool_offset
 
         self.app.worker_task.emit({'fcn': self.new_edited_excellon,
                                    'params': [self.edited_obj_name,
@@ -3270,6 +3295,8 @@ class FlatCAMExcEditor(QtCore.QObject):
                                               self.new_slots,
                                               self.new_tools]})
 
+        return self.edited_obj_name
+
     def update_options(self, obj):
         try:
             if not obj.options:
@@ -3308,9 +3335,14 @@ class FlatCAMExcEditor(QtCore.QObject):
             excellon_obj.drills = deepcopy(new_drills)
             excellon_obj.tools = deepcopy(new_tools)
             excellon_obj.slots = deepcopy(new_slots)
-            excellon_obj.tool_offset = self.new_tool_offset
+
             excellon_obj.options['name'] = outname
 
+            # add a 'data' dict for each tool with the default values
+            for tool in excellon_obj.tools:
+                excellon_obj.tools[tool]['data'] = {}
+                excellon_obj.tools[tool]['data'].update(deepcopy(self.data_defaults))
+
             try:
                 excellon_obj.create_geometry()
             except KeyError:
@@ -3348,7 +3380,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.app.log.debug("on_tool_select('%s')" % tool)
 
-        if self.last_tool_selected is None and current_tool is not 'drill_select':
+        if self.last_tool_selected is None and current_tool != 'drill_select':
             # self.draw_app.select_tool('drill_select')
             self.complete = True
             current_tool = 'drill_select'
@@ -3423,7 +3455,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.pos = self.canvas.translate_coords(event_pos)
 
-        if self.app.grid_status() == True:
+        if self.app.grid_status():
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
         else:
             self.pos = (self.pos[0], self.pos[1])
@@ -3568,7 +3600,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         pos_canvas = self.canvas.translate_coords(event_pos)
 
-        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[0], pos_canvas[1])
@@ -3580,7 +3612,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                 if self.app.ui.popMenu.mouse_is_panning is False:
                     try:
                         QtGui.QGuiApplication.restoreOverrideCursor()
-                    except Exception as e:
+                    except Exception:
                         pass
                     if self.active_tool.complete is False and not isinstance(self.active_tool, FCDrillSelect):
                         self.active_tool.complete = True
@@ -3659,7 +3691,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             for storage in self.storage_dict:
                 for obj in self.storage_dict[storage].get_objects():
                     if (sel_type is True and poly_selection.contains(obj.geo)) or \
-                        (sel_type is False and poly_selection.intersects(obj.geo)):
+                            (sel_type is False and poly_selection.intersects(obj.geo)):
 
                         if obj in self.selected:
                             # remove the shape object from the selected shapes storage
@@ -3679,7 +3711,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         try:
             self.tools_table_exc.cellPressed.disconnect()
-        except Exception as e:
+        except Exception:
             pass
 
         # first deselect all rows (tools) in the Tools Table
@@ -3756,7 +3788,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             return
 
         # ## Snap coordinates
-        if self.app.grid_status() == True:
+        if self.app.grid_status():
             x, y = self.app.geo_editor.snap(x, y)
 
             # Update cursor
@@ -3773,12 +3805,12 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         if self.pos is None:
             self.pos = (0, 0)
-        dx = x - self.pos[0]
-        dy = y - self.pos[1]
+        self.app.dx = x - self.pos[0]
+        self.app.dy = y - self.pos[1]
 
         # update the reference position label in the infobar since the APP mouse event handlers are disconnected
         self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
 
         # ## Utility geometry (animated)
         self.update_utility_geometry(data=(x, y))

Plik diff jest za duży
+ 347 - 144
flatcamEditors/FlatCAMGeoEditor.py


+ 246 - 203
flatcamEditors/FlatCAMGrbEditor.py

@@ -288,14 +288,14 @@ class FCPad(FCShapeTool):
 
         ap_type = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['type']
         if ap_type == 'C':
-            new_geo_el = dict()
+            new_geo_el = {}
 
             center = Point([point_x, point_y])
             new_geo_el['solid'] = center.buffer(self.radius)
             new_geo_el['follow'] = center
             return new_geo_el
         elif ap_type == 'R':
-            new_geo_el = dict()
+            new_geo_el = {}
 
             p1 = (point_x - self.half_width, point_y - self.half_height)
             p2 = (point_x + self.half_width, point_y - self.half_height)
@@ -307,7 +307,7 @@ class FCPad(FCShapeTool):
             return new_geo_el
         elif ap_type == 'O':
             geo = []
-            new_geo_el = dict()
+            new_geo_el = {}
 
             if self.half_height > self.half_width:
                 p1 = (point_x - self.half_width, point_y - self.half_height + self.half_width)
@@ -423,7 +423,7 @@ class FCPadArray(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_array.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
@@ -515,9 +515,8 @@ class FCPadArray(FCShapeTool):
                 self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' %
                                               _("The value is not Float. Check for comma instead of dot separator."))
                 return
-        except Exception as e:
-            self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' %
-                                          _("The value is mistyped. Check the value."))
+        except Exception:
+            self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % _("The value is mistyped. Check the value."))
             return
 
         if self.pad_array == 'Linear':
@@ -545,7 +544,7 @@ class FCPadArray(FCShapeTool):
                     )
 
                 if static is None or static is False:
-                    new_geo_el = dict()
+                    new_geo_el = {}
 
                     if 'solid' in geo_el:
                         new_geo_el['solid'] = affinity.translate(
@@ -602,14 +601,14 @@ class FCPadArray(FCShapeTool):
 
         ap_type = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['type']
         if ap_type == 'C':
-            new_geo_el = dict()
+            new_geo_el = {}
 
             center = Point([point_x, point_y])
             new_geo_el['solid'] = center.buffer(self.radius)
             new_geo_el['follow'] = center
             return new_geo_el
         elif ap_type == 'R':
-            new_geo_el = dict()
+            new_geo_el = {}
 
             p1 = (point_x - self.half_width, point_y - self.half_height)
             p2 = (point_x + self.half_width, point_y - self.half_height)
@@ -620,7 +619,7 @@ class FCPadArray(FCShapeTool):
             return new_geo_el
         elif ap_type == 'O':
             geo = []
-            new_geo_el = dict()
+            new_geo_el = {}
 
             if self.half_height > self.half_width:
                 p1 = (point_x - self.half_width, point_y - self.half_height + self.half_width)
@@ -812,7 +811,7 @@ class FCPoligonize(FCShapeTool):
                     except KeyError:
                         self.draw_app.on_aperture_add(apid='0')
                         current_storage = self.draw_app.storage_dict['0']['geometry']
-                new_el = dict()
+                new_el = {}
                 new_el['solid'] = geo
                 new_el['follow'] = geo.exterior
                 self.draw_app.on_grb_shape_complete(current_storage, specific_shape=DrawToolShape(deepcopy(new_el)))
@@ -827,7 +826,7 @@ class FCPoligonize(FCShapeTool):
                     self.draw_app.on_aperture_add(apid='0')
                     current_storage = self.draw_app.storage_dict['0']['geometry']
 
-            new_el = dict()
+            new_el = {}
             new_el['solid'] = fused_geo
             new_el['follow'] = fused_geo.exterior
             self.draw_app.on_grb_shape_complete(current_storage, specific_shape=DrawToolShape(deepcopy(new_el)))
@@ -915,7 +914,7 @@ class FCRegion(FCShapeTool):
         self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
 
     def utility_geometry(self, data=None):
-        new_geo_el = dict()
+        new_geo_el = {}
 
         x = data[0]
         y = data[1]
@@ -983,7 +982,7 @@ class FCRegion(FCShapeTool):
                     self.inter_point = data
 
             self.temp_points.append(data)
-            new_geo_el = dict()
+            new_geo_el = {}
 
             if len(self.temp_points) > 1:
                 try:
@@ -1049,7 +1048,7 @@ class FCRegion(FCShapeTool):
 
                         self.temp_points.append(self.inter_point)
             self.temp_points.append(data)
-            new_geo_el = dict()
+            new_geo_el = {}
 
             new_geo_el['solid'] = LinearRing(self.temp_points).buffer(self.buf_val,
                                                                       resolution=int(self.steps_per_circle / 4),
@@ -1070,7 +1069,7 @@ class FCRegion(FCShapeTool):
             else:
                 self.draw_app.last_aperture_selected = '0'
 
-            new_geo_el = dict()
+            new_geo_el = {}
 
             new_geo_el['solid'] = Polygon(self.points).buffer(self.buf_val,
                                                               resolution=int(self.steps_per_circle / 4),
@@ -1183,7 +1182,7 @@ class FCTrack(FCRegion):
         self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
 
     def make(self):
-        new_geo_el = dict()
+        new_geo_el = {}
         if len(self.temp_points) == 1:
             new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
                                                                  resolution=int(self.steps_per_circle / 4))
@@ -1219,7 +1218,7 @@ class FCTrack(FCRegion):
         except IndexError:
             self.points.append(point)
 
-        new_geo_el = dict()
+        new_geo_el = {}
 
         if len(self.temp_points) == 1:
             new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
@@ -1242,7 +1241,7 @@ class FCTrack(FCRegion):
 
     def utility_geometry(self, data=None):
         self.update_grid_info()
-        new_geo_el = dict()
+        new_geo_el = {}
 
         if len(self.points) == 0:
             new_geo_el['solid'] = Point(data).buffer(self.buf_val,
@@ -1427,10 +1426,10 @@ class FCDisc(FCShapeTool):
         if '0' in self.draw_app.storage_dict:
             self.storage_obj = self.draw_app.storage_dict['0']['geometry']
         else:
-            self.draw_app.storage_dict['0'] = dict()
+            self.draw_app.storage_dict['0'] = {}
             self.draw_app.storage_dict['0']['type'] = 'C'
             self.draw_app.storage_dict['0']['size'] = 0.0
-            self.draw_app.storage_dict['0']['geometry'] = list()
+            self.draw_app.storage_dict['0']['geometry'] = []
             self.storage_obj = self.draw_app.storage_dict['0']['geometry']
 
         self.draw_app.app.inform.emit(_("Click on Center point ..."))
@@ -1453,7 +1452,7 @@ class FCDisc(FCShapeTool):
         return ""
 
     def utility_geometry(self, data=None):
-        new_geo_el = dict()
+        new_geo_el = {}
         if len(self.points) == 1:
             p1 = self.points[0]
             p2 = data
@@ -1464,7 +1463,7 @@ class FCDisc(FCShapeTool):
         return None
 
     def make(self):
-        new_geo_el = dict()
+        new_geo_el = {}
 
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
@@ -1530,10 +1529,10 @@ class FCSemiDisc(FCShapeTool):
         if '0' in self.draw_app.storage_dict:
             self.storage_obj = self.draw_app.storage_dict['0']['geometry']
         else:
-            self.draw_app.storage_dict['0'] = dict()
+            self.draw_app.storage_dict['0'] = {}
             self.draw_app.storage_dict['0']['type'] = 'C'
             self.draw_app.storage_dict['0']['size'] = 0.0
-            self.draw_app.storage_dict['0']['geometry'] = list()
+            self.draw_app.storage_dict['0']['geometry'] = []
             self.storage_obj = self.draw_app.storage_dict['0']['geometry']
 
         self.steps_per_circ = self.draw_app.app.defaults["gerber_circle_steps"]
@@ -1592,10 +1591,10 @@ class FCSemiDisc(FCShapeTool):
                 return _('Mode: Center -> Start -> Stop. Click on Center point ...')
 
     def utility_geometry(self, data=None):
-        new_geo_el = dict()
-        new_geo_el_pt1 = dict()
-        new_geo_el_pt2 = dict()
-        new_geo_el_pt3 = dict()
+        new_geo_el = {}
+        new_geo_el_pt1 = {}
+        new_geo_el_pt2 = {}
+        new_geo_el_pt3 = {}
 
         if len(self.points) == 1:  # Show the radius
             center = self.points[0]
@@ -1681,7 +1680,7 @@ class FCSemiDisc(FCShapeTool):
 
     def make(self):
         self.draw_app.current_storage = self.storage_obj
-        new_geo_el = dict()
+        new_geo_el = {}
 
         if self.mode == 'c12':
             center = self.points[0]
@@ -2031,7 +2030,7 @@ class FCApertureMove(FCShapeTool):
             for select_shape in self.draw_app.get_selected():
                 if select_shape in self.current_storage:
                     geometric_data = select_shape.geo
-                    new_geo_el = dict()
+                    new_geo_el = {}
                     if 'solid' in geometric_data:
                         new_geo_el['solid'] = affinity.translate(geometric_data['solid'], xoff=dx, yoff=dy)
                     if 'follow' in geometric_data:
@@ -2084,7 +2083,7 @@ class FCApertureMove(FCShapeTool):
 
         if len(self.draw_app.get_selected()) <= self.sel_limit:
             for geom in self.draw_app.get_selected():
-                new_geo_el = dict()
+                new_geo_el = {}
                 if 'solid' in geom.geo:
                     new_geo_el['solid'] = affinity.translate(geom.geo['solid'], xoff=dx, yoff=dy)
                 if 'follow' in geom.geo:
@@ -2094,7 +2093,7 @@ class FCApertureMove(FCShapeTool):
                 geo_list.append(deepcopy(new_geo_el))
             return DrawToolUtilityShape(geo_list)
         else:
-            ss_el = dict()
+            ss_el = {}
             ss_el['solid'] = affinity.translate(self.selection_shape, xoff=dx, yoff=dy)
             return DrawToolUtilityShape(ss_el)
 
@@ -2115,7 +2114,7 @@ class FCApertureCopy(FCApertureMove):
             for select_shape in self.draw_app.get_selected():
                 if select_shape in self.current_storage:
                     geometric_data = select_shape.geo
-                    new_geo_el = dict()
+                    new_geo_el = {}
                     if 'solid' in geometric_data:
                         new_geo_el['solid'] = affinity.translate(geometric_data['solid'], xoff=dx, yoff=dy)
                     if 'follow' in geometric_data:
@@ -2274,7 +2273,7 @@ class FCEraser(FCShapeTool):
         dy = data[1] - self.origin[1]
 
         for geom in self.draw_app.get_selected():
-            new_geo_el = dict()
+            new_geo_el = {}
             if 'solid' in geom.geo:
                 new_geo_el['solid'] = affinity.translate(geom.geo['solid'], xoff=dx, yoff=dy)
             if 'follow' in geom.geo:
@@ -2300,10 +2299,10 @@ class FCApertureSelect(DrawTool):
 
         # since FCApertureSelect tool is activated whenever a tool is exited I place here the reinitialization of the
         # bending modes using in FCRegion and FCTrack
-        self.draw_app.bend_mode = 1
+        self.grb_editor_app.bend_mode = 1
 
         # here store the selected apertures
-        self.sel_aperture = set()
+        self.sel_aperture = []
 
         try:
             self.grb_editor_app.apertures_table.clearSelection()
@@ -2332,7 +2331,7 @@ class FCApertureSelect(DrawTool):
         else:
             mod_key = None
 
-        if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+        if mod_key == self.grb_editor_app.app.defaults["global_mselect_key"]:
             pass
         else:
             self.grb_editor_app.selected = []
@@ -2348,46 +2347,53 @@ class FCApertureSelect(DrawTool):
         else:
             mod_key = None
 
+        if mod_key != self.grb_editor_app.app.defaults["global_mselect_key"]:
+            self.grb_editor_app.selected.clear()
+            self.sel_aperture.clear()
+
         for storage in self.grb_editor_app.storage_dict:
             try:
-                for geo_el in self.grb_editor_app.storage_dict[storage]['geometry']:
-                    if 'solid' in geo_el.geo:
-                        geometric_data = geo_el.geo['solid']
+                for shape_stored in self.grb_editor_app.storage_dict[storage]['geometry']:
+                    if 'solid' in shape_stored.geo:
+                        geometric_data = shape_stored.geo['solid']
                         if Point(point).within(geometric_data):
-                            if mod_key == self.grb_editor_app.app.defaults["global_mselect_key"]:
-                                if geo_el in self.draw_app.selected:
-                                    self.draw_app.selected.remove(geo_el)
-                                    self.sel_aperture.remove(storage)
-                                else:
-                                    # add the object to the selected shapes
-                                    self.draw_app.selected.append(geo_el)
-                                    self.sel_aperture.add(storage)
+                            if shape_stored in self.grb_editor_app.selected:
+                                self.grb_editor_app.selected.remove(shape_stored)
                             else:
-                                self.draw_app.selected.append(geo_el)
-                                self.sel_aperture.add(storage)
+                                # add the object to the selected shapes
+                                self.grb_editor_app.selected.append(shape_stored)
             except KeyError:
                 pass
 
         # select the aperture in the Apertures Table that is associated with the selected shape
+        self.sel_aperture.clear()
+
+        self.grb_editor_app.apertures_table.clearSelection()
         try:
-            self.draw_app.apertures_table.cellPressed.disconnect()
+            self.grb_editor_app.apertures_table.cellPressed.disconnect()
         except Exception as e:
             log.debug("FlatCAMGrbEditor.FCApertureSelect.click_release() --> %s" % str(e))
 
-        self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
+        for shape_s in self.grb_editor_app.selected:
+            for storage in self.grb_editor_app.storage_dict:
+                if shape_s in self.grb_editor_app.storage_dict[storage]['geometry']:
+                    self.sel_aperture.append(storage)
+
+        # self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
         for aper in self.sel_aperture:
             for row in range(self.grb_editor_app.apertures_table.rowCount()):
                 if str(aper) == self.grb_editor_app.apertures_table.item(row, 1).text():
-                    self.grb_editor_app.apertures_table.selectRow(row)
-                    self.draw_app.last_aperture_selected = aper
-        self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+                    if not self.grb_editor_app.apertures_table.item(row, 0).isSelected():
+                        self.grb_editor_app.apertures_table.selectRow(row)
+                        self.grb_editor_app.last_aperture_selected = aper
+        # self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
 
-        self.draw_app.apertures_table.cellPressed.connect(self.draw_app.on_row_selected)
+        self.grb_editor_app.apertures_table.cellPressed.connect(self.grb_editor_app.on_row_selected)
 
         return ""
 
     def clean_up(self):
-        self.draw_app.plot_all()
+        self.grb_editor_app.plot_all()
 
 
 class FCTransform(FCShapeTool):
@@ -2915,30 +2921,30 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # # ## Data
         self.active_tool = None
 
-        self.storage_dict = dict()
-        self.current_storage = list()
+        self.storage_dict = {}
+        self.current_storage = []
 
-        self.sorted_apid = list()
+        self.sorted_apid = []
 
-        self.new_apertures = dict()
-        self.new_aperture_macros = dict()
+        self.new_apertures = {}
+        self.new_aperture_macros = {}
 
         # store here the plot promises, if empty the delayed plot will be activated
-        self.grb_plot_promises = list()
+        self.grb_plot_promises = []
 
         # dictionary to store the tool_row and aperture codes in Tool_table
         # it will be updated everytime self.build_ui() is called
-        self.olddia_newdia = dict()
+        self.olddia_newdia = {}
 
-        self.tool2tooldia = dict()
+        self.tool2tooldia = {}
 
         # this will store the value for the last selected tool, for use after clicking on canvas when the selection
         # is cleared but as a side effect also the selected tool is cleared
         self.last_aperture_selected = None
-        self.utility = list()
+        self.utility = []
 
         # this will store the polygons marked by mark are to be perhaps deleted
-        self.geo_to_delete = list()
+        self.geo_to_delete = []
 
         # this will flag if the Editor "tools" are launched from key shortcuts (True) or from menu toolbar (False)
         self.launched_from_shortcuts = False
@@ -2950,7 +2956,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.apdim_lbl.hide()
         self.apdim_entry.hide()
         self.gerber_obj = None
-        self.gerber_obj_options = dict()
+        self.gerber_obj_options = {}
 
         # VisPy Visuals
         if self.app.is_legacy is False:
@@ -3033,11 +3039,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.pool = self.app.pool
 
         # Multiprocessing results
-        self.results = list()
+        self.results = []
 
         # A QTimer
         self.plot_thread = None
 
+        # a QThread for the edit process
+        self.thread = QtCore.QThread()
+
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
 
@@ -3093,12 +3102,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.conversion_factor = 1
 
+        self.apertures_row = 0
+
+        self.complete = True
+
         self.set_ui()
         log.debug("Initialization of the FlatCAM Gerber Editor is finished ...")
 
     def pool_recreated(self, pool):
         self.shapes.pool = pool
         self.tool_shape.pool = pool
+        self.pool = pool
 
     def set_ui(self):
         # updated units
@@ -3321,8 +3335,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
                 self.storage_dict[ap_id]['geometry'] = []
 
-                # self.olddia_newdia dict keeps the evidence on current aperture codes as keys and gets updated on values
-                # each time a aperture code is edited or added
+                # self.olddia_newdia dict keeps the evidence on current aperture codes as keys and
+                # gets updated on values each time a aperture code is edited or added
                 self.olddia_newdia[ap_id] = ap_id
         else:
             if ap_id not in self.olddia_newdia:
@@ -3423,7 +3437,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
                 # I've added this flag_del variable because dictionary don't like
                 # having keys deleted while iterating through them
-                flag_del = list()
+                flag_del = []
                 for deleted_tool in self.tool2tooldia:
                     if self.tool2tooldia[deleted_tool] == deleted_aperture:
                         flag_del.append(deleted_tool)
@@ -3493,7 +3507,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             geometry = []
             for geo_el in self.storage_dict[dia_changed]:
                 geometric_data = geo_el.geo
-                new_geo_el = dict()
+                new_geo_el = {}
                 if 'solid' in geometric_data:
                     new_geo_el['solid'] = deepcopy(affinity.scale(geometric_data['solid'],
                                                                   xfact=factor, yfact=factor))
@@ -3612,7 +3626,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 self.app.ui.corner_snap_btn.setEnabled(False)
                 self.app.ui.snap_magnet.setVisible(False)
                 self.app.ui.corner_snap_btn.setVisible(False)
-            elif layout == 'compact':
+            else:
                 # self.app.ui.exc_edit_toolbar.setVisible(True)
 
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
@@ -3742,7 +3756,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         except (TypeError, AttributeError):
             pass
 
-        self.app.ui.popmenu_copy.triggered.connect(self.app.on_copy_object)
+        self.app.ui.popmenu_copy.triggered.connect(self.app.on_copy_command)
         self.app.ui.popmenu_delete.triggered.connect(self.app.on_delete)
         self.app.ui.popmenu_move.triggered.connect(self.app.obj_move)
 
@@ -3920,101 +3934,131 @@ class FlatCAMGrbEditor(QtCore.QObject):
         #     # and add the first aperture to have something to play with
         #     self.on_aperture_add('10')
 
-        def worker_job(app_obj):
-            with app_obj.app.proc_container.new('%s ...' % _("Loading Gerber into Editor")):
-                # ############################################################# ##
-                # APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY
-                # ############################################################# ##
-
-                # list of clear geos that are to be applied to the entire file
-                global_clear_geo = []
-
-                # create one big geometry made out of all 'negative' (clear) polygons
-                for apid 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 'clear' in elem:
-                                global_clear_geo.append(elem['clear'])
-                log.warning("Found %d clear polygons." % len(global_clear_geo))
-
-                global_clear_geo = MultiPolygon(global_clear_geo)
-                if isinstance(global_clear_geo, Polygon):
-                    global_clear_geo = list(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
-                for apid in app_obj.gerber_obj.apertures:
-                    temp_solid_geometry = []
-                    if 'geometry' in app_obj.gerber_obj.apertures[apid]:
-                        # for elem in self.gerber_obj.apertures[apid]['geometry']:
-                        #     if 'solid' in elem:
-                        #         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
-                        #             if clear_geo.within(solid_geo):
-                        #                 solid_geo = solid_geo.difference(clear_geo)
-                        #         try:
-                        #             for poly in solid_geo:
-                        #                 new_elem = dict()
-                        #
-                        #                 new_elem['solid'] = poly
-                        #                 if 'clear' in elem:
-                        #                     new_elem['clear'] = poly
-                        #                 if 'follow' in elem:
-                        #                     new_elem['follow'] = poly
-                        #                 temp_elem.append(deepcopy(new_elem))
-                        #         except TypeError:
-                        #             new_elem = dict()
-                        #             new_elem['solid'] = solid_geo
-                        #             if 'clear' in elem:
-                        #                 new_elem['clear'] = solid_geo
-                        #             if 'follow' in elem:
-                        #                 new_elem['follow'] = solid_geo
-                        #             temp_elem.append(deepcopy(new_elem))
-                        for elem in app_obj.gerber_obj.apertures[apid]['geometry']:
-                            new_elem = dict()
-                            if 'solid' in elem:
-                                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
-                                    if clear_geo.within(solid_geo):
-                                        solid_geo = solid_geo.difference(clear_geo)
-
-                                new_elem['solid'] = solid_geo
-                            if 'clear' in elem:
-                                new_elem['clear'] = elem['clear']
-                            if 'follow' in elem:
-                                new_elem['follow'] = elem['follow']
-                            temp_solid_geometry.append(deepcopy(new_elem))
-
-                        app_obj.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_solid_geometry)
-                log.warning("Polygon difference done for %d apertures." % len(app_obj.gerber_obj.apertures))
-
-                # Loading the Geometry into Editor Storage
-                for ap_id, ap_dict in app_obj.gerber_obj.apertures.items():
-                    app_obj.results.append(app_obj.pool.apply_async(app_obj.add_apertures, args=(ap_id, ap_dict)))
-
-                output = list()
-                for p in app_obj.results:
-                    output.append(p.get())
-
-                for elem in output:
-                    app_obj.storage_dict[elem[0]] = deepcopy(elem[1])
-
-                app_obj.mp_finished.emit(output)
-
-        self.app.worker_task.emit({'fcn': worker_job, 'params': [self]})
+        # self.app.worker_task.emit({'fcn': worker_job, 'params': [self]})
+
+        class Execute_Edit(QtCore.QObject):
+
+            start = QtCore.pyqtSignal(str)
+
+            def __init__(self, app):
+                super(Execute_Edit, self).__init__()
+                self.app = app
+                self.start.connect(self.run)
+
+            @staticmethod
+            def worker_job(app_obj):
+                with app_obj.app.proc_container.new('%s ...' % _("Loading Gerber into Editor")):
+                    # ###############################################################
+                    # APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY
+                    # ###############################################################
+
+                    # list of clear geos that are to be applied to the entire file
+                    global_clear_geo = []
+
+                    # create one big geometry made out of all 'negative' (clear) polygons
+                    for apid 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 'clear' in elem:
+                                    global_clear_geo.append(elem['clear'])
+                    log.warning("Found %d clear polygons." % len(global_clear_geo))
+
+                    if global_clear_geo:
+                        global_clear_geo = MultiPolygon(global_clear_geo)
+                        if isinstance(global_clear_geo, Polygon):
+                            global_clear_geo = list(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
+                    for ap_id in app_obj.gerber_obj.apertures:
+                        temp_solid_geometry = []
+                        if 'geometry' in app_obj.gerber_obj.apertures[ap_id]:
+                            # for elem in self.gerber_obj.apertures[apid]['geometry']:
+                            #     if 'solid' in elem:
+                            #         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
+                            #             if clear_geo.within(solid_geo):
+                            #                 solid_geo = solid_geo.difference(clear_geo)
+                            #         try:
+                            #             for poly in solid_geo:
+                            #                 new_elem = {}
+                            #
+                            #                 new_elem['solid'] = poly
+                            #                 if 'clear' in elem:
+                            #                     new_elem['clear'] = poly
+                            #                 if 'follow' in elem:
+                            #                     new_elem['follow'] = poly
+                            #                 temp_elem.append(deepcopy(new_elem))
+                            #         except TypeError:
+                            #             new_elem = {}
+                            #             new_elem['solid'] = solid_geo
+                            #             if 'clear' in elem:
+                            #                 new_elem['clear'] = solid_geo
+                            #             if 'follow' in elem:
+                            #                 new_elem['follow'] = solid_geo
+                            #             temp_elem.append(deepcopy(new_elem))
+                            for elem in app_obj.gerber_obj.apertures[ap_id]['geometry']:
+                                new_elem = {}
+                                if 'solid' in elem:
+                                    solid_geo = elem['solid']
+                                    if not global_clear_geo or global_clear_geo.is_empty:
+                                        pass
+                                    else:
+                                        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
+                                            if clear_geo.within(solid_geo):
+                                                solid_geo = solid_geo.difference(clear_geo)
+
+                                    new_elem['solid'] = solid_geo
+                                if 'clear' in elem:
+                                    new_elem['clear'] = elem['clear']
+                                if 'follow' in elem:
+                                    new_elem['follow'] = elem['follow']
+                                temp_solid_geometry.append(deepcopy(new_elem))
+
+                            app_obj.gerber_obj.apertures[ap_id]['geometry'] = deepcopy(temp_solid_geometry)
+
+                    log.warning("Polygon difference done for %d apertures." % len(app_obj.gerber_obj.apertures))
+
+                    try:
+                        # Loading the Geometry into Editor Storage
+                        for ap_id, ap_dict in app_obj.gerber_obj.apertures.items():
+                            app_obj.results.append(
+                                app_obj.pool.apply_async(app_obj.add_apertures, args=(ap_id, ap_dict))
+                            )
+                    except Exception as ee:
+                        log.debug(
+                            "FlatCAMGrbEditor.edit_fcgerber.worker_job() Adding processes to pool --> %s" % str(ee))
+                        traceback.print_exc()
+
+                    output = []
+                    for p in app_obj.results:
+                        output.append(p.get())
+
+                    for elem in output:
+                        app_obj.storage_dict[elem[0]] = deepcopy(elem[1])
+
+                    app_obj.mp_finished.emit(output)
+
+            def run(self):
+                self.worker_job(self.app)
+
+        self.thread.start(QtCore.QThread.NormalPriority)
+
+        executable_edit = Execute_Edit(app=self)
+        executable_edit.moveToThread(self.thread)
+        executable_edit.start.emit("Started")
 
     @staticmethod
     def add_apertures(aperture_id, aperture_dict):
-        storage_elem = list()
-        storage_dict = dict()
+        storage_elem = []
+        storage_dict = {}
 
         for k, v in list(aperture_dict.items()):
             try:
@@ -4073,7 +4117,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
     def update_options(obj):
         try:
             if not obj.options:
-                obj.options = dict()
+                obj.options = {}
                 obj.options['xmin'] = 0
                 obj.options['ymin'] = 0
                 obj.options['xmax'] = 0
@@ -4082,7 +4126,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             else:
                 return False
         except AttributeError:
-            obj.options = dict()
+            obj.options = {}
             return True
 
     def new_edited_gerber(self, outname, aperture_storage):
@@ -4101,7 +4145,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         out_name = outname
         storage_dict = aperture_storage
 
-        local_storage_dict = dict()
+        local_storage_dict = {}
         for aperture in storage_dict:
             if 'geometry' in storage_dict[aperture]:
                 # add aperture only if it has geometry
@@ -4122,7 +4166,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         grb_obj.apertures[storage_apid][k] = []
                         for geo_el in val:
                             geometric_data = geo_el.geo
-                            new_geo_el = dict()
+                            new_geo_el = {}
                             if 'solid' in geometric_data:
                                 new_geo_el['solid'] = geometric_data['solid']
                                 poly_buffer.append(deepcopy(new_geo_el['solid']))
@@ -4182,7 +4226,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             except KeyError:
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("There are no Aperture definitions in the file. Aborting Gerber creation."))
-            except Exception as e:
+            except Exception:
                 msg = '[ERROR] %s' % \
                       _("An internal error has occurred. See shell.\n")
                 msg += traceback.format_exc()
@@ -4197,12 +4241,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
             except Exception as e:
                 log.error("Error on Edited object creation: %s" % str(e))
                 # make sure to clean the previous results
-                self.results = list()
+                self.results = []
                 return
 
-            self.app.inform.emit('[success] %s' %  _("Done. Gerber editing finished."))
+            self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
             # make sure to clean the previous results
-            self.results = list()
+            self.results = []
 
     def on_tool_select(self, tool):
         """
@@ -4214,12 +4258,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.app.log.debug("on_tool_select('%s')" % tool)
 
-        if self.last_aperture_selected is None and current_tool is not 'select':
+        if self.last_aperture_selected is None and current_tool != 'select':
             # self.draw_app.select_tool('select')
             self.complete = True
             current_tool = 'select'
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Cancelled. No aperture is selected"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No aperture is selected"))
 
         # This is to make the group behave as radio group
         if current_tool in self.tools_gerber:
@@ -4355,7 +4398,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.pos = self.canvas.translate_coords(event_pos)
 
-        if self.app.grid_status() == True:
+        if self.app.grid_status():
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
         else:
             self.pos = (self.pos[0], self.pos[1])
@@ -4371,7 +4414,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 # If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard
                 if modifiers == QtCore.Qt.ShiftModifier:
                     self.app.clipboard.setText(
-                        self.app.defaults["global_point_clipboard_format"] % (self.pos[0], self.pos[1])
+                        self.app.defaults["global_point_clipboard_format"] %
+                        (self.decimals, self.pos[0], self.decimals, self.pos[1])
                     )
                     self.app.inform.emit('[success] %s' %
                                          _("Coordinates copied to clipboard."))
@@ -4421,7 +4465,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             right_button = 3
 
         pos_canvas = self.canvas.translate_coords(event_pos)
-        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[0], pos_canvas[1])
@@ -4584,7 +4628,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             return
 
         # # ## Snap coordinates
-        if self.app.grid_status() == True:
+        if self.app.grid_status():
             x, y = self.app.geo_editor.snap(x, y)
 
             # Update cursor
@@ -4603,12 +4647,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         if self.pos is None:
             self.pos = (0, 0)
-        dx = x - self.pos[0]
-        dy = y - self.pos[1]
+        self.app.dx = x - self.pos[0]
+        self.app.dy = y - self.pos[1]
 
         # update the reference position label in the infobar since the APP mouse event handlers are disconnected
         self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: " 
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
 
         self.update_utility_geometry(data=(x, y))
 
@@ -4714,7 +4758,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         try:
             self.shapes.add(shape=geometry.geo, color=color, face_color=color, layer=0, tolerance=self.tolerance)
-        except AttributeError as e:
+        except AttributeError:
             if type(geometry) == Point:
                 return
             if len(color) == 9:
@@ -4911,14 +4955,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         def buffer_recursion(geom_el, selection):
             if type(geom_el) == list:
-                geoms = list()
+                geoms = []
                 for local_geom in geom_el:
                     geoms.append(buffer_recursion(local_geom, selection=selection))
                 return geoms
             else:
                 if geom_el in selection:
                     geometric_data = geom_el.geo
-                    buffered_geom_el = dict()
+                    buffered_geom_el = {}
                     if 'solid' in geometric_data:
                         buffered_geom_el['solid'] = geometric_data['solid'].buffer(buff_value, join_style=join_style)
                     if 'follow' in geometric_data:
@@ -4967,14 +5011,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         def scale_recursion(geom_el, selection):
             if type(geom_el) == list:
-                geoms = list()
+                geoms = []
                 for local_geom in geom_el:
                     geoms.append(scale_recursion(local_geom, selection=selection))
                 return geoms
             else:
                 if geom_el in selection:
                     geometric_data = geom_el.geo
-                    scaled_geom_el = dict()
+                    scaled_geom_el = {}
                     if 'solid' in geometric_data:
                         scaled_geom_el['solid'] = affinity.scale(
                             geometric_data['solid'], scale_factor, scale_factor, origin='center'
@@ -5028,12 +5072,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         area = geo_el.geo['solid'].area
                         try:
                             upper_threshold_val = self.ma_upper_threshold_entry.get_value()
-                        except Exception as e:
+                        except Exception:
                             return
 
                         try:
                             lower_threshold_val = self.ma_lower_threshold_entry.get_value()
-                        except Exception as e:
+                        except Exception:
                             lower_threshold_val = 0.0
 
                         if float(upper_threshold_val) > area > float(lower_threshold_val):
@@ -5493,7 +5537,7 @@ class TransformEditorTool(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Transform Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+T', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
 
     def set_tool_ui(self):
         # Initialize form
@@ -5554,8 +5598,7 @@ class TransformEditorTool(FlatCAMTool):
 
     def template(self):
         if not self.fcdraw.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Transformation cancelled. No shape selected."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
             return
 
         self.draw_app.select_tool("select")
@@ -5826,7 +5869,7 @@ class TransformEditorTool(FlatCAMTool):
                 # execute mirroring
                 for sel_el_shape in elem_list:
                     sel_el = sel_el_shape.geo
-                    if axis is 'X':
+                    if axis == 'X':
                         if 'solid' in sel_el:
                             sel_el['solid'] = affinity.scale(sel_el['solid'], xfact=1, yfact=-1, origin=(px, py))
                         if 'follow' in sel_el:
@@ -5835,7 +5878,7 @@ class TransformEditorTool(FlatCAMTool):
                             sel_el['clear'] = affinity.scale(sel_el['clear'], xfact=1, yfact=-1, origin=(px, py))
                         self.app.inform.emit('[success] %s...' %
                                              _('Flip on the Y axis done'))
-                    elif axis is 'Y':
+                    elif axis == 'Y':
                         if 'solid' in sel_el:
                             sel_el['solid'] = affinity.scale(sel_el['solid'], xfact=-1, yfact=1, origin=(px, py))
                         if 'follow' in sel_el:
@@ -5883,14 +5926,14 @@ class TransformEditorTool(FlatCAMTool):
 
                     for sel_el_shape in elem_list:
                         sel_el = sel_el_shape.geo
-                        if axis is 'X':
+                        if axis == 'X':
                             if 'solid' in sel_el:
                                 sel_el['solid'] = affinity.skew(sel_el['solid'], num, 0, origin=(xminimal, yminimal))
                             if 'follow' in sel_el:
                                 sel_el['follow'] = affinity.skew(sel_el['follow'], num, 0, origin=(xminimal, yminimal))
                             if 'clear' in sel_el:
                                 sel_el['clear'] = affinity.skew(sel_el['clear'], num, 0, origin=(xminimal, yminimal))
-                        elif axis is 'Y':
+                        elif axis == 'Y':
                             if 'solid' in sel_el:
                                 sel_el['solid'] = affinity.skew(sel_el['solid'], 0, num, origin=(xminimal, yminimal))
                             if 'follow' in sel_el:
@@ -5990,14 +6033,14 @@ class TransformEditorTool(FlatCAMTool):
                 try:
                     for sel_el_shape in elem_list:
                         sel_el = sel_el_shape.geo
-                        if axis is 'X':
+                        if axis == 'X':
                             if 'solid' in sel_el:
                                 sel_el['solid'] = affinity.translate(sel_el['solid'], num, 0)
                             if 'follow' in sel_el:
                                 sel_el['follow'] = affinity.translate(sel_el['follow'], num, 0)
                             if 'clear' in sel_el:
                                 sel_el['clear'] = affinity.translate(sel_el['clear'], num, 0)
-                        elif axis is 'Y':
+                        elif axis == 'Y':
                             if 'solid' in sel_el:
                                 sel_el['solid'] = affinity.translate(sel_el['solid'], 0, num)
                             if 'follow' in sel_el:

+ 11 - 9
flatcamEditors/FlatCAMTextEditor.py

@@ -5,14 +5,14 @@
 # MIT Licence                                              #
 # ##########################################################
 
-from flatcamGUI.GUIElements import *
-from PyQt5 import QtPrintSupport
+from flatcamGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
+from PyQt5 import QtPrintSupport, QtWidgets, QtCore, QtGui
 
 from reportlab.platypus import SimpleDocTemplate, Paragraph
 from reportlab.lib.styles import getSampleStyleSheet
 from reportlab.lib.units import inch, mm
 
-from io import StringIO
+# from io import StringIO
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -29,6 +29,8 @@ class TextEditor(QtWidgets.QWidget):
         super().__init__()
 
         self.app = app
+        self.plain_text = plain_text
+
         self.setSizePolicy(
             QtWidgets.QSizePolicy.MinimumExpanding,
             QtWidgets.QSizePolicy.MinimumExpanding
@@ -45,7 +47,7 @@ class TextEditor(QtWidgets.QWidget):
         self.work_editor_layout.setContentsMargins(2, 2, 2, 2)
         self.t_frame.setLayout(self.work_editor_layout)
 
-        if plain_text:
+        if self.plain_text:
             self.editor_class = FCTextAreaLineNumber()
             self.code_editor = self.editor_class.edit
 
@@ -209,16 +211,16 @@ class TextEditor(QtWidgets.QWidget):
                     _filter_ = "FlatConfig Files (*.FlatConfig);;PDF Files (*.pdf);;All Files (*.*)"
 
         try:
-            filename = str(QtWidgets.QFileDialog.getSaveFileName(
+            filename = str(FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Code ..."),
                 directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
                 filter=_filter_
             )[0])
         except TypeError:
-            filename = str(QtWidgets.QFileDialog.getSaveFileName(caption=_("Export Code ..."), filter=_filter_)[0])
+            filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), filter=_filter_)[0])
 
         if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Code cancelled."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
             return
         else:
             try:
@@ -234,7 +236,7 @@ class TextEditor(QtWidgets.QWidget):
 
                     styles = getSampleStyleSheet()
                     styleN = styles['Normal']
-                    styleH = styles['Heading1']
+                    # styleH = styles['Heading1']
                     story = []
 
                     if self.app.defaults['units'].lower() == 'mm':
@@ -313,7 +315,7 @@ class TextEditor(QtWidgets.QWidget):
                     if qc.hasSelection():
                         qc.insertText(new)
                 else:
-                    self.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
+                    self.code_editor.moveCursor(QtGui.QTextCursor.Start)
                     break
             # Mark end of undo block
             cursor.endEditBlock()

Plik diff jest za duży
+ 265 - 167
flatcamGUI/FlatCAMGUI.py


+ 289 - 24
flatcamGUI/GUIElements.py

@@ -152,6 +152,124 @@ class RadioSet(QtWidgets.QWidget):
 #             wgt.show()
 
 
+class FCTree(QtWidgets.QTreeWidget):
+    resize_sig = QtCore.pyqtSignal()
+
+    def __init__(self, parent=None, columns=2, header_hidden=True, extended_sel=False, protected_column=None):
+        super(FCTree, self).__init__(parent)
+
+        self.setColumnCount(columns)
+        self.setHeaderHidden(header_hidden)
+        self.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
+        self.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Expanding)
+
+        palette = QtGui.QPalette()
+        palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight,
+                         palette.color(QtGui.QPalette.Active, QtGui.QPalette.Highlight))
+
+        # make inactive rows text some color as active; may be useful in the future
+        # palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.HighlightedText,
+        #                  palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
+        self.setPalette(palette)
+
+        if extended_sel:
+            self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+
+        self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+
+        self.protected_column = protected_column
+        self.itemDoubleClicked.connect(self.on_double_click)
+        self.header().sectionDoubleClicked.connect(self.on_header_double_click)
+        self.resize_sig.connect(self.on_resize)
+
+    def on_double_click(self, item, column):
+        # from here: https://stackoverflow.com/questions/2801959/making-only-one-column-of-a-qtreewidgetitem-editable
+        tmp_flags = item.flags()
+        if self.is_editable(column):
+            item.setFlags(tmp_flags | QtCore.Qt.ItemIsEditable)
+        elif tmp_flags & QtCore.Qt.ItemIsEditable:
+            item.setFlags(tmp_flags ^ QtCore.Qt.ItemIsEditable)
+
+    def on_header_double_click(self, column):
+        header = self.header()
+        header.setSectionResizeMode(column, QtWidgets.QHeaderView.ResizeToContents)
+        width = header.sectionSize(column)
+        header.setSectionResizeMode(column, QtWidgets.QHeaderView.Interactive)
+        header.resizeSection(column, width)
+
+    def is_editable(self, tested_col):
+        try:
+            ret_val = False if tested_col in self.protected_column else True
+        except TypeError:
+            ret_val = False
+        return ret_val
+
+    def addParent(self, parent, title, expanded=False, color=None, font=None):
+        item = QtWidgets.QTreeWidgetItem(parent, [title])
+        item.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.ShowIndicator)
+        item.setExpanded(expanded)
+        if color is not None:
+            # item.setTextColor(0, color) # PyQt4
+            item.setForeground(0, QtGui.QBrush(color))
+        if font is not None:
+            item.setFont(0, font)
+        return item
+
+    def addParentEditable(self, parent, title, color=None, font=None, font_items=None, editable=False):
+        item = QtWidgets.QTreeWidgetItem(parent)
+        item.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.DontShowIndicator)
+        if editable:
+            item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
+
+        item.setFlags(item.flags() | QtCore.Qt.ItemIsSelectable)
+
+        for t in range(len(title)):
+            item.setText(t, title[t])
+
+        if color is not None:
+            # item.setTextColor(0, color) # PyQt4
+            item.setForeground(0, QtGui.QBrush(color))
+
+        if font and font_items:
+            try:
+                for fi in font_items:
+                    item.setFont(fi, font)
+            except TypeError:
+                item.setFont(font_items, font)
+        elif font:
+            item.setFont(0, font)
+        return item
+
+    def addChild(self, parent, title, column1=None, font=None, font_items=None, editable=False):
+        item = QtWidgets.QTreeWidgetItem(parent)
+        if editable:
+            item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
+
+        item.setText(0, str(title[0]))
+        if column1 is not None:
+            item.setText(1, str(title[1]))
+        if font and font_items:
+            try:
+                for fi in font_items:
+                    item.setFont(fi, font)
+            except TypeError:
+                item.setFont(font_items, font)
+
+    def resizeEvent(self, event):
+        """ Resize all sections to content and user interactive """
+
+        super(FCTree, self).resizeEvent(event)
+        self.on_resize()
+
+    def on_resize(self):
+        header = self.header()
+        for column in range(header.count()):
+            header.setSectionResizeMode(column, QtWidgets.QHeaderView.ResizeToContents)
+            width = header.sectionSize(column)
+            header.setSectionResizeMode(column, QtWidgets.QHeaderView.Interactive)
+            header.resizeSection(column, width)
+
+
 class LengthEntry(QtWidgets.QLineEdit):
     def __init__(self, output_units='IN', decimals=None, parent=None):
         super(LengthEntry, self).__init__(parent)
@@ -201,7 +319,7 @@ class LengthEntry(QtWidgets.QLineEdit):
             units = raw[-2:]
             units = self.scales[self.output_units][units.upper()]
             value = raw[:-2]
-            return float(eval(value))*  units
+            return float(eval(value)) * units
         except IndexError:
             value = raw
             return float(eval(value))
@@ -233,7 +351,7 @@ class FloatEntry(QtWidgets.QLineEdit):
 
     def mousePressEvent(self, e, Parent=None):
         super(FloatEntry, self).mousePressEvent(e)  # required to deselect on 2e click
-        if self.readyToEdit == True:
+        if self.readyToEdit is True:
             self.selectAll()
             self.readyToEdit = False
 
@@ -258,7 +376,7 @@ class FloatEntry(QtWidgets.QLineEdit):
             evaled = eval(raw)
             return float(evaled)
         except Exception as e:
-            if raw is not '':
+            if raw != '':
                 log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
             return None
 
@@ -304,7 +422,7 @@ class FloatEntry2(QtWidgets.QLineEdit):
             evaled = eval(raw)
             return float(evaled)
         except Exception as e:
-            if raw is not '':
+            if raw != '':
                 log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
             return None
 
@@ -407,6 +525,8 @@ class FCEntry(QtWidgets.QLineEdit):
         decimal_digits = decimals if decimals is not None else self.decimals
         if type(val) is float:
             self.setText('%.*f' % (decimal_digits, val))
+        elif val is None:
+            self.setText('')
         else:
             self.setText(str(val))
 
@@ -482,7 +602,7 @@ class EvalEntry(QtWidgets.QLineEdit):
         try:
             evaled = eval(raw)
         except Exception as e:
-            if raw is not '':
+            if raw != '':
                 log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
             return None
         return evaled
@@ -522,7 +642,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
         try:
             evaled = eval(raw)
         except Exception as e:
-            if raw is not '':
+            if raw != '':
                 log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
             return None
         return evaled
@@ -538,12 +658,16 @@ class EvalEntry2(QtWidgets.QLineEdit):
 class FCSpinner(QtWidgets.QSpinBox):
 
     returnPressed = QtCore.pyqtSignal()
+    confirmation_signal = QtCore.pyqtSignal(bool, float, float)
 
-    def __init__(self, suffix=None, alignment=None, parent=None):
+    def __init__(self, suffix=None, alignment=None, parent=None, callback=None):
         super(FCSpinner, self).__init__(parent)
         self.readyToEdit = True
 
         self.editingFinished.connect(self.on_edit_finished)
+        if callback:
+            self.confirmation_signal.connect(callback)
+
         self.lineEdit().installEventFilter(self)
 
         if suffix:
@@ -602,6 +726,14 @@ class FCSpinner(QtWidgets.QSpinBox):
             self.readyToEdit = True
             self.prev_readyToEdit = True
 
+    def valueFromText(self, text):
+        txt = text.strip('%%')
+        try:
+            ret_val = int(txt)
+        except ValueError:
+            ret_val = 0
+        return ret_val
+
     def get_value(self):
         return int(self.value())
 
@@ -613,8 +745,30 @@ class FCSpinner(QtWidgets.QSpinBox):
             return
         self.setValue(k)
 
+    def validate(self, p_str, p_int):
+        text = p_str
+
+        min_val = self.minimum()
+        max_val = self.maximum()
+        try:
+            if int(text) < min_val or int(text) > max_val:
+                self.confirmation_signal.emit(False, min_val, max_val)
+                return QtGui.QValidator.Intermediate, text, p_int
+        except ValueError:
+            pass
+
+        self.confirmation_signal.emit(True, min_val, max_val)
+        return QtGui.QValidator.Acceptable, p_str, p_int
+
     def set_range(self, min_val, max_val):
+        self.blockSignals(True)
         self.setRange(min_val, max_val)
+        self.blockSignals(False)
+
+    def set_step(self, p_int):
+        self.blockSignals(True)
+        self.setSingleStep(p_int)
+        self.blockSignals(False)
 
     # def sizeHint(self):
     #     default_hint_size = super(FCSpinner, self).sizeHint()
@@ -624,12 +778,23 @@ class FCSpinner(QtWidgets.QSpinBox):
 class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
 
     returnPressed = QtCore.pyqtSignal()
+    confirmation_signal = QtCore.pyqtSignal(bool, float, float)
 
-    def __init__(self, suffix=None, alignment=None, parent=None):
+    def __init__(self, suffix=None, alignment=None, parent=None, callback=None):
+        """
+
+        :param suffix:      a char added to the end of the value in the LineEdit; like a '%' or '$' etc
+        :param alignment:   the value is aligned to left or right
+        :param parent:
+        :param callback:    called when the entered value is outside limits; the min and max value will be passed to it
+        """
         super(FCDoubleSpinner, self).__init__(parent)
         self.readyToEdit = True
 
         self.editingFinished.connect(self.on_edit_finished)
+        if callback:
+            self.confirmation_signal.connect(callback)
+
         self.lineEdit().installEventFilter(self)
 
         # by default don't allow the minus sign to be entered as the default for QDoubleSpinBox is the positive range
@@ -690,20 +855,26 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
 
     def valueFromText(self, p_str):
         text = p_str.replace(',', '.')
+        text = text.strip('%%')
         try:
             ret_val = float(text)
         except ValueError:
             ret_val = 0.0
-
         return ret_val
 
     def validate(self, p_str, p_int):
         text = p_str.replace(',', '.')
+
+        min_val = self.minimum()
+        max_val = self.maximum()
         try:
-            if float(text) < self.minimum() or float(text) > self.maximum():
+            if float(text) < min_val or float(text) > max_val:
+                self.confirmation_signal.emit(False, min_val, max_val)
                 return QtGui.QValidator.Intermediate, text, p_int
         except ValueError:
             pass
+
+        self.confirmation_signal.emit(True, min_val, max_val)
         return QtGui.QValidator.Acceptable, p_str, p_int
 
     def get_value(self):
@@ -735,6 +906,10 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
 
         self.setRange(min_val, max_val)
 
+    # def sizeHint(self):
+    #     default_hint_size = super(FCDoubleSpinner, self).sizeHint()
+    #     return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
+
 
 class FCCheckBox(QtWidgets.QCheckBox):
     def __init__(self, label='', parent=None):
@@ -1139,6 +1314,9 @@ class FCComboBox(QtWidgets.QComboBox):
         self.view.viewport().installEventFilter(self)
         self.view.setContextMenuPolicy(Qt.CustomContextMenu)
 
+        self._set_last = False
+        self._obj_type = None
+
         # the callback() will be called on customcontextmenu event and will be be passed 2 parameters:
         # pos = mouse right click click position
         # self = is the combobox object itself
@@ -1160,6 +1338,29 @@ class FCComboBox(QtWidgets.QComboBox):
     def set_value(self, val):
         self.setCurrentIndex(self.findText(str(val)))
 
+    @property
+    def is_last(self):
+        return self._set_last
+
+    @is_last.setter
+    def is_last(self, val):
+        self._set_last = val
+        if self._set_last is True:
+            self.model().rowsInserted.connect(self.on_model_changed)
+        self.setCurrentIndex(1)
+
+    @property
+    def obj_type(self):
+        return self._obj_type
+
+    @obj_type.setter
+    def obj_type(self, val):
+        self._obj_type = val
+
+    def on_model_changed(self, parent, first, last):
+        if self.model().data(parent, QtCore.Qt.DisplayRole) == self.obj_type:
+            self.setCurrentIndex(first)
+
 
 class FCInputDialog(QtWidgets.QInputDialog):
     def __init__(self, parent=None, ok=False, val=None, title=None, text=None, min=None, max=None, decimals=None,
@@ -1216,9 +1417,19 @@ class FCButton(QtWidgets.QPushButton):
 
 
 class FCLabel(QtWidgets.QLabel):
+
+    clicked = QtCore.pyqtSignal(bool)
+
     def __init__(self, parent=None):
         super(FCLabel, self).__init__(parent)
 
+        # for the usage of this label as a clickable label, to know that current state
+        self.clicked_state = False
+
+    def mousePressEvent(self, event):
+        self.clicked_state = not self.clicked_state
+        self.clicked.emit(self.clicked_state)
+
     def get_value(self):
         return self.text()
 
@@ -1230,10 +1441,12 @@ class FCMenu(QtWidgets.QMenu):
     def __init__(self):
         super().__init__()
         self.mouse_is_panning = False
+        self.popup_active = False
 
     def popup(self, pos, action=None):
-        self.mouse_is_panning = False
         super().popup(pos)
+        self.mouse_is_panning = False
+        self.popup_active = True
 
 
 class FCTab(QtWidgets.QTabWidget):
@@ -1290,7 +1503,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
         self.protect_by_name = protect_by_name if isinstance(protect_by_name, list) else None
 
         # Close all detached tabs if the application is closed explicitly
-        QtWidgets.qApp.aboutToQuit.connect(self.closeDetachedTabs) # @UndefinedVariable
+        QtWidgets.qApp.aboutToQuit.connect(self.closeDetachedTabs)  # @UndefinedVariable
 
         # used by the property self.useOldIndex(param)
         self.use_old_index = None
@@ -1770,7 +1983,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
                 self.dragInitiated = True
 
             # If the current movement is a drag initiated by the left button
-            if ((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated and self.can_be_dragged:
+            if (event.buttons() & QtCore.Qt.LeftButton) and self.dragInitiated and self.can_be_dragged:
 
                 # Stop the move event
                 finishMoveEvent = QtGui.QMouseEvent(
@@ -1885,7 +2098,7 @@ class FCDetachableTab2(FCDetachableTab):
 class VerticalScrollArea(QtWidgets.QScrollArea):
     """
     This widget extends QtGui.QScrollArea to make a vertical-only
-    scroll area that also expands horizontally to accomodate
+    scroll area that also expands horizontally to accommodate
     its contents.
     """
     def __init__(self, parent=None):
@@ -2009,6 +2222,10 @@ class FCTable(QtWidgets.QTableWidget):
         palette = QtGui.QPalette()
         palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight,
                          palette.color(QtGui.QPalette.Active, QtGui.QPalette.Highlight))
+
+        # make inactive rows text some color as active; may be useful in the future
+        # palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.HighlightedText,
+        #                  palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
         self.setPalette(palette)
 
         if drag_drop:
@@ -2022,7 +2239,7 @@ class FCTable(QtWidgets.QTableWidget):
             self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
             self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
 
-        self.rows_not_for_drag_and_drop = list()
+        self.rows_not_for_drag_and_drop = []
         if protected_rows:
             try:
                 for r in protected_rows:
@@ -2030,7 +2247,7 @@ class FCTable(QtWidgets.QTableWidget):
             except TypeError:
                 self.rows_not_for_drag_and_drop = [protected_rows]
 
-        self.rows_to_move = list()
+        self.rows_to_move = []
 
     def sizeHint(self):
         default_hint_size = super(FCTable, self).sizeHint()
@@ -2086,7 +2303,7 @@ class FCTable(QtWidgets.QTableWidget):
     #         # ]
     #         self.rows_to_move[:] = []
     #         for row_index in rows:
-    #             row_items = list()
+    #             row_items = []
     #             for column_index in range(self.columnCount()):
     #                 r_item = self.item(row_index, column_index)
     #                 w_item = self.cellWidget(row_index, column_index)
@@ -2167,7 +2384,7 @@ class FCTable(QtWidgets.QTableWidget):
             for _ in range(len(rows)):
                 self.insertRow(targetRow)
 
-            rowMapping = dict()  # Src row to target row.
+            rowMapping = {}  # Src row to target row.
             for idx, row in enumerate(rows):
                 if row < targetRow:
                     rowMapping[row] = targetRow + idx
@@ -2274,7 +2491,7 @@ class Dialog_box(QtWidgets.QWidget):
 
 
 class DialogBoxRadio(QtWidgets.QDialog):
-    def __init__(self, title=None, label=None, icon=None, initial_text=None, reference='abs'):
+    def __init__(self, title=None, label=None, icon=None, initial_text=None, reference='abs', parent=None):
         """
 
         :param title: string with the window title
@@ -2318,8 +2535,10 @@ class DialogBoxRadio(QtWidgets.QDialog):
               "If the reference is Relative then the Jump will be at the (x,y) distance\n"
               "from the current mouse location point.")
         )
-        self.lineEdit = EvalEntry()
+        self.lineEdit = EvalEntry(self)
         self.lineEdit.setText(str(self.location).replace('(', '').replace(')', ''))
+        self.lineEdit.selectAll()
+        self.lineEdit.setFocus()
         self.form.addRow(self.loc_label, self.lineEdit)
 
         self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
@@ -2341,27 +2560,49 @@ class DialogBoxRadio(QtWidgets.QDialog):
 
 class _BrowserTextEdit(QTextEdit):
 
-    def __init__(self, version):
+    def __init__(self, version, app=None):
         QTextEdit.__init__(self)
         self.menu = None
         self.version = version
+        self.app = app
 
     def contextMenuEvent(self, event):
         self.menu = self.createStandardContextMenu(event.pos())
-        clear_action = QAction("Clear", self)
+
+        if self.app:
+            save_action = QAction(_("Save Log"), self)
+            self.menu.addAction(save_action)
+            save_action.triggered.connect(lambda: self.save_log(app=self.app))
+
+        clear_action = QAction(_("Clear"), self)
         clear_action.setShortcut(QKeySequence(Qt.Key_Delete))   # it's not working, the shortcut
         self.menu.addAction(clear_action)
         clear_action.triggered.connect(self.clear)
+
+        if self.app:
+            close_action = QAction(_("Close"), self)
+            self.menu.addAction(close_action)
+            close_action.triggered.connect(lambda: self.app.ui.shell_dock.hide())
+
         self.menu.exec_(event.globalPos())
 
     def clear(self):
         QTextEdit.clear(self)
-        text = "FlatCAM %s - Type >help< to get started\n\n" % self.version
+
+        text = "!FlatCAM %s? - %s" % (self.version, _("Type >help< to get started"))
         text = html.escape(text)
-        text = text.replace('\n', '<br/>')
+        # hack so I can make text bold because the escape method will replace the '<' and '>' signs with html code
+        text = text.replace('!', '<b>')
+        text = text.replace('?', '</b>')
+        text += '<br><br>'
         self.moveCursor(QTextCursor.End)
         self.insertHtml(text)
 
+    def save_log(self, app):
+        html_content = self.toHtml()
+        txt_content = self.toPlainText()
+        app.save_to_file(content_to_save=html_content, txt_content=txt_content)
+
 
 class _ExpandableTextEdit(QTextEdit):
     """
@@ -2652,6 +2893,30 @@ class FCTextAreaLineNumber(QtWidgets.QFrame):
         self.edit.setLineWrapMode(mode)
 
 
+class FCFileSaveDialog(QtWidgets.QFileDialog):
+
+    def __init__(self, *args):
+        super(FCFileSaveDialog, self).__init__(*args)
+
+    @staticmethod
+    def get_saved_filename(parent=None, caption='', directory='', filter='', initialFilter=''):
+        filename, _filter = QtWidgets.QFileDialog.getSaveFileName(parent=parent, caption=caption,
+                                                                  directory=directory, filter=filter,
+                                                                  initialFilter=initialFilter)
+
+        filename = str(filename)
+        if filename == '':
+            return filename, _filter
+
+        extension = '.' + _filter.strip(')').rpartition('.')[2]
+
+        if filename.endswith(extension) or extension == '.*':
+            return filename, _filter
+        else:
+            filename += extension
+            return filename, _filter
+
+
 def rreplace(s, old, new, occurrence):
     """
     Credits go here:

Plik diff jest za duży
+ 487 - 223
flatcamGUI/ObjectUI.py


+ 1 - 1
flatcamGUI/PlotCanvas.py

@@ -62,7 +62,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
         # self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None
         self.workspace_line = None
 
-        self.pagesize_dict = dict()
+        self.pagesize_dict = {}
         self.pagesize_dict.update(
             {
                 'A0': (841, 1189),

+ 131 - 84
flatcamGUI/PlotCanvasLegacy.py

@@ -14,7 +14,7 @@ from PyQt5.QtCore import pyqtSignal
 # Used for solid polygons in Matplotlib
 from descartes.patch import PolygonPatch
 
-from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString
+from shapely.geometry import Polygon, LineString, LinearRing
 
 import FlatCAMApp
 
@@ -158,7 +158,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         # self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None
         self.workspace_line = None
 
-        self.pagesize_dict = dict()
+        self.pagesize_dict = {}
         self.pagesize_dict.update(
             {
                 'A0': (841, 1189),
@@ -219,7 +219,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         self.container = container
 
         # Plots go onto a single matplotlib.figure
-        self.figure = Figure(dpi=50)  # TODO: dpi needed?
+        self.figure = Figure(dpi=50)
         self.figure.patch.set_visible(True)
         self.figure.set_facecolor(theme_color)
 
@@ -254,7 +254,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         # self.canvas.set_can_focus(True)  # For key press
 
         # Attach to parent
-        # self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
+        # self.container.attach(self.canvas, 0, 0, 600, 400)
         self.container.addWidget(self.canvas)  # Qt
 
         # Copy a bitmap of the canvas for quick animation.
@@ -430,7 +430,7 @@ class PlotCanvasLegacy(QtCore.QObject):
                     # Pointer (snapped)
                     # The size of the cursor is multiplied by 1.65 because that value made the cursor similar with the
                     # one in the OpenGL(3D) graphic engine
-                    pointer_size = int(float(self.app.defaults["global_cursor_size"] ) * 1.65)
+                    pointer_size = int(float(self.app.defaults["global_cursor_size"]) * 1.65)
                     elements = self.axes.plot(x, y, '+', color=color, ms=pointer_size,
                                               mew=self.app.defaults["global_cursor_width"], animated=True)
                     for el in elements:
@@ -946,21 +946,23 @@ class ShapeCollectionLegacy:
     hold the collection of shapes into a dict self._shapes.
     This handles the shapes redraw on canvas.
     """
-    def __init__(self, obj, app, name=None, annotation_job=None):
+    def __init__(self, obj, app, name=None, annotation_job=None, linewidth=1):
         """
 
-        :param obj: this is the object to which the shapes collection is attached and for
+        :param obj:             This is the object to which the shapes collection is attached and for
         which it will have to draw shapes
-        :param app: this is the FLatCAM.App usually, needed because we have to access attributes there
-        :param name: this is the name given to the Matplotlib axes; it needs to be unique due of Matplotlib requurements
-        :param annotation_job: make this True if the job needed is just for annotation
+        :param app:             This is the FLatCAM.App usually, needed because we have to access attributes there
+        :param name:            This is the name given to the Matplotlib axes; it needs to be unique due of
+        Matplotlib requurements
+        :param annotation_job:  Make this True if the job needed is just for annotation
+        :param linewidth:       THe width of the line (outline where is the case)
         """
         self.obj = obj
         self.app = app
         self.annotation_job = annotation_job
 
-        self._shapes = dict()
-        self.shape_dict = dict()
+        self._shapes = {}
+        self.shape_dict = {}
         self.shape_id = 0
 
         self._color = None
@@ -974,6 +976,8 @@ class ShapeCollectionLegacy:
         self._obj = None
         self._gcode_parsed = None
 
+        self._linewidth = linewidth
+
         if name is None:
             axes_name = self.obj.options['name']
         else:
@@ -1005,14 +1009,21 @@ class ShapeCollectionLegacy:
         :return:
         """
         self._color = color if color is not None else "#006E20"
-        self._face_color = face_color if face_color is not None else "#BBF268"
+        # self._face_color = face_color if face_color is not None else "#BBF268"
+        self._face_color = face_color
+
+        if linewidth is None:
+            line_width = self._linewidth
+        else:
+            line_width = linewidth
 
         if len(self._color) > 7:
             self._color = self._color[:7]
 
-        if len(self._face_color) > 7:
-            self._face_color = self._face_color[:7]
-            # self._alpha = int(self._face_color[-2:], 16) / 255
+        if self._face_color is not None:
+            if len(self._face_color) > 7:
+                self._face_color = self._face_color[:7]
+                # self._alpha = int(self._face_color[-2:], 16) / 255
 
         self._alpha = 0.75
 
@@ -1037,7 +1048,7 @@ class ShapeCollectionLegacy:
                 self.shape_dict.update({
                     'color': self._color,
                     'face_color': self._face_color,
-                    'linewidth': linewidth,
+                    'linewidth': line_width,
                     'alpha': self._alpha,
                     'shape': sh
                 })
@@ -1050,7 +1061,7 @@ class ShapeCollectionLegacy:
             self.shape_dict.update({
                 'color': self._color,
                 'face_color': self._face_color,
-                'linewidth': linewidth,
+                'linewidth': line_width,
                 'alpha': self._alpha,
                 'shape': shape
             })
@@ -1112,36 +1123,50 @@ class ShapeCollectionLegacy:
                 if obj_type == 'excellon':
                     # Plot excellon (All polygons?)
                     if self.obj.options["solid"] and isinstance(local_shapes[element]['shape'], Polygon):
-                        patch = PolygonPatch(local_shapes[element]['shape'],
-                                             facecolor="#C40000",
-                                             edgecolor="#750000",
-                                             alpha=local_shapes[element]['alpha'],
-                                             zorder=3)
-                        self.axes.add_patch(patch)
+                        try:
+                            patch = PolygonPatch(local_shapes[element]['shape'],
+                                                 facecolor="#C40000",
+                                                 edgecolor="#750000",
+                                                 alpha=local_shapes[element]['alpha'],
+                                                 zorder=3,
+                                                 linewidth=local_shapes[element]['linewidth']
+                                                 )
+                            self.axes.add_patch(patch)
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() excellon poly --> %s" % str(e))
                     else:
-                        x, y = local_shapes[element]['shape'].exterior.coords.xy
-                        self.axes.plot(x, y, 'r-')
-                        for ints in local_shapes[element]['shape'].interiors:
-                            x, y = ints.coords.xy
-                            self.axes.plot(x, y, 'o-')
+                        try:
+                            x, y = local_shapes[element]['shape'].exterior.coords.xy
+                            self.axes.plot(x, y, 'r-', linewidth=local_shapes[element]['linewidth'])
+                            for ints in local_shapes[element]['shape'].interiors:
+                                x, y = ints.coords.xy
+                                self.axes.plot(x, y, 'o-', linewidth=local_shapes[element]['linewidth'])
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() excellon no poly --> %s" % str(e))
                 elif obj_type == 'geometry':
                     if type(local_shapes[element]['shape']) == Polygon:
-                        x, y = local_shapes[element]['shape'].exterior.coords.xy
-                        self.axes.plot(x, y, local_shapes[element]['color'],
-                                       linestyle='-',
-                                       linewidth=local_shapes[element]['linewidth'])
-                        for ints in local_shapes[element]['shape'].interiors:
-                            x, y = ints.coords.xy
+                        try:
+                            x, y = local_shapes[element]['shape'].exterior.coords.xy
                             self.axes.plot(x, y, local_shapes[element]['color'],
                                            linestyle='-',
                                            linewidth=local_shapes[element]['linewidth'])
+                            for ints in local_shapes[element]['shape'].interiors:
+                                x, y = ints.coords.xy
+                                self.axes.plot(x, y, local_shapes[element]['color'],
+                                               linestyle='-',
+                                               linewidth=local_shapes[element]['linewidth'])
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() geometry poly --> %s" % str(e))
                     elif type(local_shapes[element]['shape']) == LineString or \
                             type(local_shapes[element]['shape']) == LinearRing:
 
-                        x, y = local_shapes[element]['shape'].coords.xy
-                        self.axes.plot(x, y, local_shapes[element]['color'],
-                                       linestyle='-',
-                                       linewidth=local_shapes[element]['linewidth'])
+                        try:
+                            x, y = local_shapes[element]['shape'].coords.xy
+                            self.axes.plot(x, y, local_shapes[element]['color'],
+                                           linestyle='-',
+                                           linewidth=local_shapes[element]['linewidth'])
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() geometry no poly --> %s" % str(e))
                 elif obj_type == 'gerber':
                     if self.obj.options["multicolored"]:
                         linespec = '-'
@@ -1161,47 +1186,60 @@ class ShapeCollectionLegacy:
                                                  facecolor=gerber_fill_color,
                                                  edgecolor=gerber_outline_color,
                                                  alpha=local_shapes[element]['alpha'],
-                                                 zorder=2)
+                                                 zorder=2,
+                                                 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))
                         except Exception as e:
-                            FlatCAMApp.App.log.debug("PlotCanvasLegacy.ShepeCollectionLegacy.redraw() --> %s" % str(e))
+                            FlatCAMApp.App.log.debug(
+                                "PlotCanvasLegacy.ShepeCollectionLegacy.redraw() gerber 'solid' --> %s" % str(e))
                     else:
-                        x, y = local_shapes[element]['shape'].exterior.xy
-                        self.axes.plot(x, y, linespec)
-                        for ints in local_shapes[element]['shape'].interiors:
-                            x, y = ints.coords.xy
-                            self.axes.plot(x, y, linespec)
+                        try:
+                            x, y = local_shapes[element]['shape'].exterior.xy
+                            self.axes.plot(x, y, linespec, linewidth=local_shapes[element]['linewidth'])
+                            for ints in local_shapes[element]['shape'].interiors:
+                                x, y = ints.coords.xy
+                                self.axes.plot(x, y, linespec, linewidth=local_shapes[element]['linewidth'])
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() gerber no 'solid' --> %s" % str(e))
                 elif obj_type == 'cncjob':
 
                     if local_shapes[element]['face_color'] is None:
-                        linespec = '--'
-                        linecolor = local_shapes[element]['color']
-                        # if geo['kind'][0] == 'C':
-                        #     linespec = 'k-'
-                        x, y = local_shapes[element]['shape'].coords.xy
-                        self.axes.plot(x, y, linespec, color=linecolor)
+                        try:
+                            linespec = '--'
+                            linecolor = local_shapes[element]['color']
+                            # if geo['kind'][0] == 'C':
+                            #     linespec = 'k-'
+                            x, y = local_shapes[element]['shape'].coords.xy
+                            self.axes.plot(x, y, linespec, color=linecolor,
+                                           linewidth=local_shapes[element]['linewidth'])
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() cncjob with face_color --> %s" % str(e))
                     else:
-                        path_num += 1
-                        if self.obj.ui.annotation_cb.get_value():
-                            if isinstance(local_shapes[element]['shape'], Polygon):
-                                self.axes.annotate(
-                                    str(path_num),
-                                    xy=local_shapes[element]['shape'].exterior.coords[0],
-                                    xycoords='data', fontsize=20)
-                            else:
-                                self.axes.annotate(
-                                    str(path_num),
-                                    xy=local_shapes[element]['shape'].coords[0],
-                                    xycoords='data', fontsize=20)
-
-                        patch = PolygonPatch(local_shapes[element]['shape'],
-                                             facecolor=local_shapes[element]['face_color'],
-                                             edgecolor=local_shapes[element]['color'],
-                                             alpha=local_shapes[element]['alpha'], zorder=2)
-                        self.axes.add_patch(patch)
+                        try:
+                            path_num += 1
+                            if self.obj.ui.annotation_cb.get_value():
+                                if isinstance(local_shapes[element]['shape'], Polygon):
+                                    self.axes.annotate(
+                                        str(path_num),
+                                        xy=local_shapes[element]['shape'].exterior.coords[0],
+                                        xycoords='data', fontsize=20)
+                                else:
+                                    self.axes.annotate(
+                                        str(path_num),
+                                        xy=local_shapes[element]['shape'].coords[0],
+                                        xycoords='data', fontsize=20)
+
+                            patch = PolygonPatch(local_shapes[element]['shape'],
+                                                 facecolor=local_shapes[element]['face_color'],
+                                                 edgecolor=local_shapes[element]['color'],
+                                                 alpha=local_shapes[element]['alpha'], zorder=2,
+                                                 linewidth=local_shapes[element]['linewidth'])
+                            self.axes.add_patch(patch)
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() cncjob no face_color --> %s" % str(e))
                 elif obj_type == 'utility':
                     # not a FlatCAM object, must be utility
                     if local_shapes[element]['face_color']:
@@ -1210,26 +1248,35 @@ class ShapeCollectionLegacy:
                                                  facecolor=local_shapes[element]['face_color'],
                                                  edgecolor=local_shapes[element]['color'],
                                                  alpha=local_shapes[element]['alpha'],
-                                                 zorder=2)
+                                                 zorder=2,
+                                                 linewidth=local_shapes[element]['linewidth'])
 
                             self.axes.add_patch(patch)
                         except Exception as e:
-                            log.debug("ShapeCollectionLegacy.redraw() --> %s" % str(e))
+                            log.debug("ShapeCollectionLegacy.redraw() utility poly with face_color --> %s" % str(e))
                     else:
                         if isinstance(local_shapes[element]['shape'], Polygon):
-                            ext_shape = local_shapes[element]['shape'].exterior
-                            if ext_shape is not None:
-                                x, y = ext_shape.xy
-                                self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
-                            for ints in local_shapes[element]['shape'].interiors:
-                                if ints is not None:
-                                    x, y = ints.coords.xy
-                                    self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                            try:
+                                ext_shape = local_shapes[element]['shape'].exterior
+                                if ext_shape is not None:
+                                    x, y = ext_shape.xy
+                                    self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-',
+                                                   linewidth=local_shapes[element]['linewidth'])
+                                for ints in local_shapes[element]['shape'].interiors:
+                                    if ints is not None:
+                                        x, y = ints.coords.xy
+                                        self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-',
+                                                       linewidth=local_shapes[element]['linewidth'])
+                            except Exception as e:
+                                log.debug("ShapeCollectionLegacy.redraw() utility poly no face_color --> %s" % str(e))
                         else:
-                            if local_shapes[element]['shape'] is not None:
-                                x, y = local_shapes[element]['shape'].coords.xy
-                                self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
-
+                            try:
+                                if local_shapes[element]['shape'] is not None:
+                                    x, y = local_shapes[element]['shape'].coords.xy
+                                    self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-',
+                                                   linewidth=local_shapes[element]['linewidth'])
+                            except Exception as e:
+                                log.debug("ShapeCollectionLegacy.redraw() utility lines no face_color --> %s" % str(e))
         self.app.plotcanvas.auto_adjust_axes()
 
     def set(self, text, pos, visible=True, font_size=16, color=None):

Plik diff jest za duży
+ 414 - 119
flatcamGUI/PreferencesUI.py


+ 2 - 21
flatcamGUI/VisPyCanvas.py

@@ -24,24 +24,16 @@ black = Color("#000000")
 class VisPyCanvas(scene.SceneCanvas):
 
     def __init__(self, config=None):
-        print("vp_1")
-        try:
-            # scene.SceneCanvas.__init__(self, keys=None, config=config)
-            super().__init__(config=config, keys=None)
-        except Exception as e:
-            print("VisPyCanvas.__init__() -> %s" % str(e))
-
-        print("vp_2")
+        # scene.SceneCanvas.__init__(self, keys=None, config=config)
+        super().__init__(config=config, keys=None)
 
         self.unfreeze()
-        print("vp_3")
 
         settings = QSettings("Open Source", "FlatCAM")
         if settings.contains("axis_font_size"):
             a_fsize = settings.value('axis_font_size', type=int)
         else:
             a_fsize = 8
-        print("vp_4")
 
         if settings.contains("theme"):
             theme = settings.value('theme', type=str)
@@ -59,8 +51,6 @@ class VisPyCanvas(scene.SceneCanvas):
             # back_color = Color('#272822') # darker
             # back_color = Color('#3c3f41') # lighter
 
-        print("vp_5")
-
         self.central_widget.bgcolor = back_color
         self.central_widget.border_color = back_color
 
@@ -70,8 +60,6 @@ class VisPyCanvas(scene.SceneCanvas):
         top_padding = self.grid_widget.add_widget(row=0, col=0, col_span=2)
         top_padding.height_max = 0
 
-        print("vp_6")
-
         self.yaxis = scene.AxisWidget(
             orientation='left', axis_color=tick_color, text_color=tick_color, font_size=a_fsize, axis_width=1
         )
@@ -89,13 +77,9 @@ class VisPyCanvas(scene.SceneCanvas):
         # right_padding.width_max = 24
         right_padding.width_max = 0
 
-        print("vp_7")
-
         view = self.grid_widget.add_view(row=1, col=1, border_color=tick_color, bgcolor=theme_color)
         view.camera = Camera(aspect=1, rect=(-25, -25, 150, 150))
 
-        print("vp_8")
-
         # Following function was removed from 'prepare_draw()' of 'Grid' class by patch,
         # it is necessary to call manually
         self.grid_widget._update_child_widget_dim()
@@ -118,10 +102,7 @@ class VisPyCanvas(scene.SceneCanvas):
         else:
             self.grid = scene.GridLines(parent=self.view.scene, color='#dededeff')
 
-        print("vp_9")
-
         self.grid.set_gl_state(depth_test=False)
-        print("vp_10")
 
         self.freeze()
 

+ 8 - 5
flatcamGUI/VisPyVisuals.py

@@ -193,10 +193,10 @@ class ShapeGroup(object):
 
 class ShapeCollectionVisual(CompoundVisual):
 
-    def __init__(self, line_width=1, triangulation='vispy', layers=3, pool=None, **kwargs):
+    def __init__(self, linewidth=1, triangulation='vispy', layers=3, pool=None, **kwargs):
         """
         Represents collection of shapes to draw on VisPy scene
-        :param line_width: float
+        :param linewidth: float
             Width of lines/edges
         :param triangulation: str
             Triangulation method used for polygons translation
@@ -223,7 +223,7 @@ class ShapeCollectionVisual(CompoundVisual):
         # self._lines = [LineVisual(antialias=True) for _ in range(0, layers)]
         self._lines = [FlatCAMLineVisual(antialias=True) for _ in range(0, layers)]
 
-        self._line_width = line_width
+        self._line_width = linewidth
         self._triangulation = triangulation
 
         visuals_ = [self._lines[i // 2] if i % 2 else self._meshes[i // 2] for i in range(0, layers * 2)]
@@ -262,7 +262,7 @@ class ShapeCollectionVisual(CompoundVisual):
         :param tolerance: float
             Geometry simplifying tolerance
         :param linewidth: int
-            Not used, for compatibility
+            Width of the line
         :return: int
             Index of shape
         """
@@ -276,6 +276,9 @@ class ShapeCollectionVisual(CompoundVisual):
         self.data[key] = {'geometry': shape, 'color': color, 'alpha': alpha, 'face_color': face_color,
                           'visible': visible, 'layer': layer, 'tolerance': tolerance}
 
+        if linewidth:
+            self._line_width = linewidth
+
         # Add data to process pool if pool exists
         try:
             self.results[key] = self.pool.map_async(_update_shape_buffers, [self.data[key]])
@@ -459,7 +462,7 @@ class ShapeCollectionVisual(CompoundVisual):
         self.update_lock.acquire(True)
 
         # Merge shapes buffers
-        for data in list(self.data.values()):
+        for data in self.data.values():
             if data['visible'] and 'line_pts' in data:
                 try:
                     line_pts[data['layer']] += data['line_pts']

+ 84 - 70
flatcamParsers/ParseExcellon.py

@@ -43,6 +43,7 @@ class Excellon(Geometry):
     ================  ====================================
     C                 Diameter of the tool
     solid_geometry    Geometry list for each tool
+    data              dictionary which holds the options for each tool
     Others            Not supported (Ignored).
     ================  ====================================
 
@@ -94,11 +95,11 @@ class Excellon(Geometry):
         Geometry.__init__(self, geo_steps_per_circle=int(geo_steps_per_circle))
 
         # dictionary to store tools, see above for description
-        self.tools = dict()
+        self.tools = {}
         # list to store the drills, see above for description
-        self.drills = list()
+        self.drills = []
         # self.slots (list) to store the slots; each is a dictionary
-        self.slots = list()
+        self.slots = []
 
         self.source_file = ''
 
@@ -109,8 +110,8 @@ class Excellon(Geometry):
         self.match_routing_start = None
         self.match_routing_stop = None
 
-        self.num_tools = list()  # List for keeping the tools sorted
-        self.index_per_tool = dict()  # Dictionary to store the indexed points for each tool
+        self.num_tools = []  # List for keeping the tools sorted
+        self.index_per_tool = {}  # Dictionary to store the indexed points for each tool
 
         # ## IN|MM -> Units are inherited from Geometry
         self.units = self.app.defaults['units']
@@ -617,82 +618,92 @@ class Excellon(Geometry):
                     match = self.coordsnoperiod_re.search(eline)
                     if match:
                         matchr = self.repeat_re.search(eline)
-                        if matchr:
+                        if matchr:  # if we have a repeat command
                             repeat = int(matchr.group(1))
 
-                        try:
-                            x = self.parse_number(match.group(1))
-                            repeating_x = current_x
-                            current_x = x
-                        except TypeError:
-                            x = current_x
-                            repeating_x = 0
-                        except Exception:
-                            return
+                            if match.group(1):
+                                repeating_x = self.parse_number(match.group(1))
+                            else:
+                                repeating_x = 0
 
-                        try:
-                            y = self.parse_number(match.group(2))
-                            repeating_y = current_y
-                            current_y = y
-                        except TypeError:
-                            y = current_y
-                            repeating_y = 0
-                        except Exception:
-                            return
+                            if match.group(2):
+                                repeating_y = self.parse_number(match.group(2))
+                            else:
+                                repeating_y = 0
 
-                        if x is None or y is None:
-                            log.error("Missing coordinates")
+                            coordx = current_x
+                            coordy = current_y
+
+                            while repeat > 0:
+                                if repeating_x:
+                                    coordx += repeating_x
+                                if repeating_y:
+                                    coordy += repeating_y
+                                self.drills.append({'point': Point((coordx, coordy)), 'tool': current_tool})
+
+                                repeat -= 1
+                            current_x = coordx
+                            current_y = coordy
                             continue
 
-                        # ## Excellon Routing parse
-                        if len(re.findall("G00", eline)) > 0:
-                            self.match_routing_start = 'G00'
+                        else:   # those are normal coordinates
+                            try:
+                                x = self.parse_number(match.group(1))
+                                current_x = x
+                            except TypeError:
+                                x = current_x
+                            except Exception:
+                                return
 
-                            # signal that there are milling slots operations
-                            self.defaults['excellon_drills'] = False
+                            try:
+                                y = self.parse_number(match.group(2))
+                                current_y = y
+                            except TypeError:
+                                y = current_y
+                            except Exception:
+                                return
 
-                            self.routing_flag = 0
-                            slot_start_x = x
-                            slot_start_y = y
-                            continue
+                            if x is None or y is None:
+                                log.error("Missing coordinates")
+                                continue
 
-                        if self.routing_flag == 0:
-                            if len(re.findall("G01", eline)) > 0:
-                                self.match_routing_stop = 'G01'
+                            # ## Excellon Routing parse
+                            if len(re.findall("G00", eline)) > 0:
+                                self.match_routing_start = 'G00'
 
                                 # signal that there are milling slots operations
                                 self.defaults['excellon_drills'] = False
 
-                                self.routing_flag = 1
-                                slot_stop_x = x
-                                slot_stop_y = y
-                                self.slots.append(
-                                    {
-                                        'start': Point(slot_start_x, slot_start_y),
-                                        'stop': Point(slot_stop_x, slot_stop_y),
-                                        'tool': current_tool
-                                    }
-                                )
+                                self.routing_flag = 0
+                                slot_start_x = x
+                                slot_start_y = y
                                 continue
 
-                        if self.match_routing_start is None and self.match_routing_stop is None:
-                            if repeat == 0:
+                            if self.routing_flag == 0:
+                                if len(re.findall("G01", eline)) > 0:
+                                    self.match_routing_stop = 'G01'
+
+                                    # signal that there are milling slots operations
+                                    self.defaults['excellon_drills'] = False
+
+                                    self.routing_flag = 1
+                                    slot_stop_x = x
+                                    slot_stop_y = y
+                                    self.slots.append(
+                                        {
+                                            'start': Point(slot_start_x, slot_start_y),
+                                            'stop': Point(slot_stop_x, slot_stop_y),
+                                            'tool': current_tool
+                                        }
+                                    )
+                                    continue
+
+                            if self.match_routing_start is None and self.match_routing_stop is None:
                                 # signal that there are drill operations
                                 self.defaults['excellon_drills'] = True
                                 self.drills.append({'point': Point((x, y)), 'tool': current_tool})
-                            else:
-                                coordx = x
-                                coordy = y
-                                while repeat > 0:
-                                    if repeating_x:
-                                        coordx = (repeat * x) + repeating_x
-                                    if repeating_y:
-                                        coordy = (repeat * y) + repeating_y
-                                    self.drills.append({'point': Point((coordx, coordy)), 'tool': current_tool})
-                                    repeat -= 1
-                            repeating_x = repeating_y = 0
-                            # log.debug("{:15} {:8} {:8}".format(eline, x, y))
-                            continue
+                                # log.debug("{:15} {:8} {:8}".format(eline, x, y))
+                                continue
 
                     # ## Coordinates with period: Use literally. # ##
                     match = self.coordsperiod_re.search(eline)
@@ -961,14 +972,12 @@ class Excellon(Geometry):
         try:
             # clear the solid_geometry in self.tools
             for tool in self.tools:
-                try:
-                    self.tools[tool]['solid_geometry'][:] = []
-                except KeyError:
-                    self.tools[tool]['solid_geometry'] = []
+                self.tools[tool]['solid_geometry'] = []
+                self.tools[tool]['data'] = {}
 
             for drill in self.drills:
                 # poly = drill['point'].buffer(self.tools[drill['tool']]["C"]/2.0)
-                if drill['tool'] is '':
+                if drill['tool'] == '':
                     self.app.inform.emit('[WARNING] %s' %
                                          _("Excellon.create_geometry() -> a drill location was skipped "
                                            "due of not having a tool associated.\n"
@@ -979,7 +988,10 @@ class Excellon(Geometry):
                 tooldia = self.tools[drill['tool']]['C']
                 poly = drill['point'].buffer(tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
                 self.solid_geometry.append(poly)
-                self.tools[drill['tool']]['solid_geometry'].append(poly)
+
+                tool_in_drills = drill['tool']
+                self.tools[tool_in_drills]['solid_geometry'].append(poly)
+                self.tools[tool_in_drills]['data'] = deepcopy(self.default_data)
 
             for slot in self.slots:
                 slot_tooldia = self.tools[slot['tool']]['C']
@@ -989,8 +1001,10 @@ class Excellon(Geometry):
                 lines_string = LineString([start, stop])
                 poly = lines_string.buffer(slot_tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
                 self.solid_geometry.append(poly)
-                self.tools[slot['tool']]['solid_geometry'].append(poly)
 
+                tool_in_slots = slot['tool']
+                self.tools[tool_in_slots]['solid_geometry'].append(poly)
+                self.tools[tool_in_slots]['data'] = deepcopy(self.default_data)
         except Exception as e:
             log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry() -> "
                       "Excellon geometry creation failed due of ERROR: %s" % str(e))

+ 130 - 58
flatcamParsers/ParseGerber.py

@@ -1,4 +1,4 @@
-
+from PyQt5 import QtWidgets
 from camlib import Geometry, arc, arc_angle, ApertureMacro
 import FlatCAMApp
 
@@ -394,10 +394,10 @@ class Gerber(Geometry):
         current_operation_code = None
 
         # Current coordinates
-        current_x = None
-        current_y = None
-        previous_x = None
-        previous_y = None
+        current_x = 0
+        current_y = 0
+        previous_x = 0
+        previous_y = 0
 
         current_d = None
 
@@ -456,13 +456,18 @@ class Gerber(Geometry):
                     new_polarity = match.group(1)
                     # log.info("Polarity CHANGE, LPC = %s, poly_buff = %s" % (self.is_lpc, poly_buffer))
                     self.is_lpc = True if new_polarity == 'C' else False
-                    if len(path) > 1 and current_polarity != new_polarity:
+                    try:
+                        path_length = len(path)
+                    except TypeError:
+                        path_length = 1
+
+                    if path_length > 1 and current_polarity != new_polarity:
 
                         # finish the current path and add it to the storage
                         # --- Buffered ----
                         width = self.apertures[last_path_aperture]["size"]
 
-                        geo_dict = dict()
+                        geo_dict = {}
                         geo_f = LineString(path)
                         if not geo_f.is_empty:
                             follow_buffer.append(geo_f)
@@ -481,7 +486,7 @@ class Gerber(Geometry):
                                 geo_dict['solid'] = geo_s
 
                         if last_path_aperture not in self.apertures:
-                            self.apertures[last_path_aperture] = dict()
+                            self.apertures[last_path_aperture] = {}
                         if 'geometry' not in self.apertures[last_path_aperture]:
                             self.apertures[last_path_aperture]['geometry'] = []
                         self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -491,7 +496,12 @@ class Gerber(Geometry):
                     # --- Apply buffer ---
                     # If added for testing of bug #83
                     # TODO: Remove when bug fixed
-                    if len(poly_buffer) > 0:
+                    try:
+                        buff_length = len(poly_buffer)
+                    except TypeError:
+                        buff_length = 1
+
+                    if buff_length > 0:
                         if current_polarity == 'D':
                             self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
 
@@ -595,6 +605,7 @@ class Gerber(Geometry):
                 match = self.units_re.search(gline)
                 if match:
                     obs_gerber_units = {'0': 'IN', '1': 'MM'}[match.group(1)]
+                    self.units = obs_gerber_units
                     log.warning("Gerber obsolete units found = %s" % obs_gerber_units)
                     # Changed for issue #80
                     # self.convert_units({'0': 'IN', '1': 'MM'}[match.group(1)])
@@ -666,7 +677,7 @@ class Gerber(Geometry):
                         # --- Buffered ---
                         try:
                             # log.debug("Bare op-code %d." % current_operation_code)
-                            geo_dict = dict()
+                            geo_dict = {}
                             flash = self.create_flash_geometry(
                                 Point(current_x, current_y), self.apertures[current_aperture],
                                 self.steps_per_circle)
@@ -684,7 +695,7 @@ class Gerber(Geometry):
                                     geo_dict['solid'] = flash
 
                                 if current_aperture not in self.apertures:
-                                    self.apertures[current_aperture] = dict()
+                                    self.apertures[current_aperture] = {}
                                 if 'geometry' not in self.apertures[current_aperture]:
                                     self.apertures[current_aperture]['geometry'] = []
                                 self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -707,18 +718,23 @@ class Gerber(Geometry):
                     # so it can be processed by FlatCAM.
                     # But first test to see if the aperture type is "aperture macro". In that case
                     # we should not test for "size" key as it does not exist in this case.
-                    if self.apertures[current_aperture]["type"] is not "AM":
+                    if self.apertures[current_aperture]["type"] != "AM":
                         if self.apertures[current_aperture]["size"] == 0:
                             self.apertures[current_aperture]["size"] = 1e-12
                     # log.debug(self.apertures[current_aperture])
 
                     # Take care of the current path with the previous tool
-                    if len(path) > 1:
+                    try:
+                        path_length = len(path)
+                    except TypeError:
+                        path_length = 1
+
+                    if path_length > 1:
                         if self.apertures[last_path_aperture]["type"] == 'R':
                             # do nothing because 'R' type moving aperture is none at once
                             pass
                         else:
-                            geo_dict = dict()
+                            geo_dict = {}
                             geo_f = LineString(path)
                             if not geo_f.is_empty:
                                 follow_buffer.append(geo_f)
@@ -738,7 +754,7 @@ class Gerber(Geometry):
                                     geo_dict['solid'] = geo_s
 
                             if last_path_aperture not in self.apertures:
-                                self.apertures[last_path_aperture] = dict()
+                                self.apertures[last_path_aperture] = {}
                             if 'geometry' not in self.apertures[last_path_aperture]:
                                 self.apertures[last_path_aperture]['geometry'] = []
                             self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -750,10 +766,15 @@ class Gerber(Geometry):
                 # ################  G36* - Begin region   ########################
                 # ################################################################
                 if self.regionon_re.search(gline):
-                    if len(path) > 1:
+                    try:
+                        path_length = len(path)
+                    except TypeError:
+                        path_length = 1
+
+                    if path_length > 1:
                         # Take care of what is left in the path
 
-                        geo_dict = dict()
+                        geo_dict = {}
                         geo_f = LineString(path)
                         if not geo_f.is_empty:
                             follow_buffer.append(geo_f)
@@ -773,7 +794,7 @@ class Gerber(Geometry):
                                 geo_dict['solid'] = geo_s
 
                         if last_path_aperture not in self.apertures:
-                            self.apertures[last_path_aperture] = dict()
+                            self.apertures[last_path_aperture] = {}
                         if 'geometry' not in self.apertures[last_path_aperture]:
                             self.apertures[last_path_aperture]['geometry'] = []
                         self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -793,14 +814,19 @@ class Gerber(Geometry):
                         self.apertures['0'] = {}
                         self.apertures['0']['type'] = 'REG'
                         self.apertures['0']['size'] = 0.0
-                        self.apertures['0']['geometry'] = list()
+                        self.apertures['0']['geometry'] = []
 
                     # if D02 happened before G37 we now have a path with 1 element only; we have to add the current
                     # geo to the poly_buffer otherwise we loose it
                     if current_operation_code == 2:
-                        if len(path) == 1:
+                        try:
+                            path_length = len(path)
+                        except TypeError:
+                            path_length = 1
+
+                        if path_length == 1:
                             # this means that the geometry was prepared previously and we just need to add it
-                            geo_dict = dict()
+                            geo_dict = {}
                             if geo_f:
                                 if not geo_f.is_empty:
                                     follow_buffer.append(geo_f)
@@ -824,7 +850,12 @@ class Gerber(Geometry):
                     # Only one path defines region?
                     # This can happen if D02 happened before G37 and
                     # is not and error.
-                    if len(path) < 3:
+                    try:
+                        path_length = len(path)
+                    except TypeError:
+                        path_length = 1
+
+                    if path_length < 3:
                         # print "ERROR: Path contains less than 3 points:"
                         # path = [[current_x, current_y]]
                         continue
@@ -832,9 +863,10 @@ class Gerber(Geometry):
                     # For regions we may ignore an aperture that is None
 
                     # --- Buffered ---
-                    geo_dict = dict()
+                    geo_dict = {}
                     if current_aperture in self.apertures:
-                        buff_value = float(self.apertures[current_aperture]['size']) / 2.0
+                        # the following line breaks loading of Circuit Studio Gerber files
+                        # buff_value = float(self.apertures[current_aperture]['size']) / 2.0
                         # region_geo = Polygon(path).buffer(buff_value, int(self.steps_per_circle))
                         region_geo = Polygon(path)  # Sprint Layout Gerbers with ground fill are crashed with above
                     else:
@@ -933,7 +965,7 @@ class Gerber(Geometry):
                                         maxy = max(path[0][1], path[1][1]) + height / 2
                                         log.debug("Coords: %s - %s - %s - %s" % (minx, miny, maxx, maxy))
 
-                                        geo_dict = dict()
+                                        geo_dict = {}
                                         geo_f = Point([current_x, current_y])
                                         follow_buffer.append(geo_f)
                                         geo_dict['follow'] = geo_f
@@ -950,7 +982,7 @@ class Gerber(Geometry):
                                             geo_dict['solid'] = geo_s
 
                                         if current_aperture not in self.apertures:
-                                            self.apertures[current_aperture] = dict()
+                                            self.apertures[current_aperture] = {}
                                         if 'geometry' not in self.apertures[current_aperture]:
                                             self.apertures[current_aperture]['geometry'] = []
                                         self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -972,10 +1004,15 @@ class Gerber(Geometry):
                                                  _("GERBER file might be CORRUPT. Check the file !!!"))
 
                     elif current_operation_code == 2:
-                        if len(path) > 1:
+                        try:
+                            path_length = len(path)
+                        except TypeError:
+                            path_length = 1
+
+                        if path_length > 1:
                             geo_s = None
 
-                            geo_dict = dict()
+                            geo_dict = {}
                             # --- BUFFERED ---
                             # this treats the case when we are storing geometry as paths only
                             if making_region:
@@ -1052,7 +1089,7 @@ class Gerber(Geometry):
                                     geo_dict['solid'] = geo_s
 
                             if last_path_aperture not in self.apertures:
-                                self.apertures[last_path_aperture] = dict()
+                                self.apertures[last_path_aperture] = {}
                             if 'geometry' not in self.apertures[last_path_aperture]:
                                 self.apertures[last_path_aperture]['geometry'] = []
                             self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -1071,9 +1108,14 @@ class Gerber(Geometry):
                     elif current_operation_code == 3:
 
                         # Create path draw so far.
-                        if len(path) > 1:
+                        try:
+                            path_length = len(path)
+                        except TypeError:
+                            path_length = 1
+
+                        if path_length > 1:
                             # --- Buffered ----
-                            geo_dict = dict()
+                            geo_dict = {}
 
                             # this treats the case when we are storing geometry as paths
                             geo_f = LineString(path)
@@ -1114,7 +1156,7 @@ class Gerber(Geometry):
                                         geo_dict['solid'] = geo_s
 
                             if last_path_aperture not in self.apertures:
-                                self.apertures[last_path_aperture] = dict()
+                                self.apertures[last_path_aperture] = {}
                             if 'geometry' not in self.apertures[last_path_aperture]:
                                 self.apertures[last_path_aperture]['geometry'] = []
                             self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -1125,7 +1167,7 @@ class Gerber(Geometry):
                         # --- BUFFERED ---
                         # Draw the flash
                         # this treats the case when we are storing geometry as paths
-                        geo_dict = dict()
+                        geo_dict = {}
                         geo_flash = Point([linear_x, linear_y])
                         follow_buffer.append(geo_flash)
                         geo_dict['follow'] = geo_flash
@@ -1148,7 +1190,7 @@ class Gerber(Geometry):
                                 geo_dict['solid'] = flash
 
                         if current_aperture not in self.apertures:
-                            self.apertures[current_aperture] = dict()
+                            self.apertures[current_aperture] = {}
                         if 'geometry' not in self.apertures[current_aperture]:
                             self.apertures[current_aperture]['geometry'] = []
                         self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -1227,8 +1269,13 @@ class Gerber(Geometry):
                     # Nothing created! Pen Up.
                     if current_operation_code == 2:
                         log.warning("Arc with D2. (%d)" % line_num)
-                        if len(path) > 1:
-                            geo_dict = dict()
+                        try:
+                            path_length = len(path)
+                        except TypeError:
+                            path_length = 1
+
+                        if path_length > 1:
+                            geo_dict = {}
 
                             if last_path_aperture is None:
                                 log.warning("No aperture defined for curent path. (%d)" % line_num)
@@ -1256,7 +1303,7 @@ class Gerber(Geometry):
                                     geo_dict['solid'] = buffered
 
                             if last_path_aperture not in self.apertures:
-                                self.apertures[last_path_aperture] = dict()
+                                self.apertures[last_path_aperture] = {}
                             if 'geometry' not in self.apertures[last_path_aperture]:
                                 self.apertures[last_path_aperture]['geometry'] = []
                             self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -1369,8 +1416,15 @@ class Gerber(Geometry):
                 # ######### Line did not match any pattern. Warn user.  ##########
                 # ################################################################
                 log.warning("Line ignored (%d): %s" % (line_num, gline))
+                # provide the app with a way to process the GUI events when in a blocking loop
+                QtWidgets.QApplication.processEvents()
+
+            try:
+                path_length = len(path)
+            except TypeError:
+                path_length = 1
 
-            if len(path) > 1:
+            if path_length > 1:
                 # In case that G01 (moving) aperture is rectangular, there is no need to still create
                 # another geo since we already created a shapely box using the start and end coordinates found in
                 # path variable. We do it only for other apertures than 'R' type
@@ -1380,7 +1434,7 @@ class Gerber(Geometry):
                     # EOF, create shapely LineString if something still in path
                     # ## --- Buffered ---
 
-                    geo_dict = dict()
+                    geo_dict = {}
                     # this treats the case when we are storing geometry as paths
                     geo_f = LineString(path)
                     if not geo_f.is_empty:
@@ -1402,7 +1456,7 @@ class Gerber(Geometry):
                             geo_dict['solid'] = geo_s
 
                     if last_path_aperture not in self.apertures:
-                        self.apertures[last_path_aperture] = dict()
+                        self.apertures[last_path_aperture] = {}
                     if 'geometry' not in self.apertures[last_path_aperture]:
                         self.apertures[last_path_aperture]['geometry'] = []
                     self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
@@ -1412,13 +1466,26 @@ class Gerber(Geometry):
             self.follow_geometry = follow_buffer
 
             # this treats the case when we are storing geometry as solids
+            try:
+                buff_length = len(poly_buffer)
+            except TypeError:
+                buff_length = 1
+
+            try:
+                sol_geo_length = len(self.solid_geometry)
+            except TypeError:
+                sol_geo_length = 1
 
-            if len(poly_buffer) == 0 and len(self.solid_geometry) == 0:
-                log.error("Object is not Gerber file or empty. Aborting Object creation.")
+            try:
+                if buff_length == 0 and sol_geo_length in [0, 1]:
+                    log.error("Object is not Gerber file or empty. Aborting Object creation.")
+                    return 'fail'
+            except TypeError as e:
+                log.error("Object is not Gerber file or empty. Aborting Object creation. %s" % str(e))
                 return 'fail'
 
-            log.warning("Joining %d polygons." % len(poly_buffer))
-            self.app.inform.emit('%s: %d.' % (_("Gerber processing. Joining polygons"), len(poly_buffer)))
+            log.warning("Joining %d polygons." % buff_length)
+            self.app.inform.emit('%s: %d.' % (_("Gerber processing. Joining polygons"), buff_length))
 
             if self.use_buffer_for_union:
                 log.debug("Union by buffer...")
@@ -1460,7 +1527,7 @@ class Gerber(Geometry):
                 # it use a filled bounding box polygon to which add clear polygons (negative) to isolate the copper
                 # features
                 if self.app.defaults['gerber_extra_buffering']:
-                    candidate_geo = list()
+                    candidate_geo = []
                     try:
                         for p in self.solid_geometry:
                             candidate_geo.append(p.buffer(-0.0000001))
@@ -1712,7 +1779,7 @@ class Gerber(Geometry):
 
         # Add to object
         if self.solid_geometry is None:
-            self.solid_geometry = list()
+            self.solid_geometry = []
 
         # if type(self.solid_geometry) == list:
         #     if type(geos) == list:
@@ -1724,8 +1791,13 @@ class Gerber(Geometry):
 
         if type(geos) == list:
             # HACK for importing QRCODE exported by FlatCAM
-            if len(geos) == 1:
-                geo_qrcode = list()
+            try:
+                geos_length = len(geos)
+            except TypeError:
+                geos_length = 1
+
+            if geos_length == 1:
+                geo_qrcode = []
                 geo_qrcode.append(Polygon(geos[0].exterior))
                 for i_el in geos[0].interiors:
                     geo_qrcode.append(Polygon(i_el).buffer(0))
@@ -1752,13 +1824,13 @@ class Gerber(Geometry):
             self.solid_geometry = [self.solid_geometry]
 
         if '0' not in self.apertures:
-            self.apertures['0'] = dict()
+            self.apertures['0'] = {}
             self.apertures['0']['type'] = 'REG'
             self.apertures['0']['size'] = 0.0
-            self.apertures['0']['geometry'] = list()
+            self.apertures['0']['geometry'] = []
 
         for pol in self.solid_geometry:
-            new_el = dict()
+            new_el = {}
             new_el['solid'] = pol
             new_el['follow'] = pol.exterior
             self.apertures['0']['geometry'].append(deepcopy(new_el))
@@ -1847,10 +1919,10 @@ class Gerber(Geometry):
         # we need to scale the geometry stored in the Gerber apertures, too
         try:
             for apid in self.apertures:
-                new_geometry = list()
+                new_geometry = []
                 if 'geometry' in self.apertures[apid]:
                     for geo_el in self.apertures[apid]['geometry']:
-                        new_geo_el = dict()
+                        new_geo_el = {}
                         if 'solid' in geo_el:
                             new_geo_el['solid'] = scale_geom(geo_el['solid'])
                         if 'follow' in geo_el:
@@ -2243,10 +2315,10 @@ class Gerber(Geometry):
             # we need to buffer the geometry stored in the Gerber apertures, too
             try:
                 for apid in self.apertures:
-                    new_geometry = list()
+                    new_geometry = []
                     if 'geometry' in self.apertures[apid]:
                         for geo_el in self.apertures[apid]['geometry']:
-                            new_geo_el = dict()
+                            new_geo_el = {}
                             if 'solid' in geo_el:
                                 new_geo_el['solid'] = buffer_geom(geo_el['solid'])
                             if 'follow' in geo_el:
@@ -2294,10 +2366,10 @@ class Gerber(Geometry):
                     except KeyError:
                         pass
 
-                    new_geometry = list()
+                    new_geometry = []
                     if 'geometry' in self.apertures[apid]:
                         for geo_el in self.apertures[apid]['geometry']:
-                            new_geo_el = dict()
+                            new_geo_el = {}
                             if 'follow' in geo_el:
                                 new_geo_el['follow'] = geo_el['follow']
                                 size = float(self.apertures[apid]['size'])
@@ -2335,7 +2407,7 @@ class Gerber(Geometry):
                 return 'fail'
 
             # make the new solid_geometry
-            new_solid_geo = list()
+            new_solid_geo = []
             for apid in self.apertures:
                 if 'geometry' in self.apertures[apid]:
                     new_solid_geo += [geo_el['solid'] for geo_el in self.apertures[apid]['geometry']]

+ 6 - 4
flatcamParsers/ParseHPGL2.py

@@ -49,9 +49,9 @@ class HPGL2:
         self.units = 'MM'
 
         # storage for the tools
-        self.tools = dict()
+        self.tools = {}
 
-        self.default_data = dict()
+        self.default_data = {}
         self.default_data.update({
             "name": '_ncc',
             "plot": self.app.defaults["geometry_plot"],
@@ -72,6 +72,8 @@ class HPGL2:
             "toolchange": self.app.defaults["geometry_toolchange"],
             "toolchangez": self.app.defaults["geometry_toolchangez"],
             "endz": self.app.defaults["geometry_endz"],
+            "endxy": self.app.defaults["geometry_endxy"],
+
             "spindlespeed": self.app.defaults["geometry_spindlespeed"],
             "toolchangexy": self.app.defaults["geometry_toolchangexy"],
             "startz": self.app.defaults["geometry_startz"],
@@ -151,7 +153,7 @@ class HPGL2:
         """
 
         # Coordinates of the current path, each is [x, y]
-        path = list()
+        path = []
 
         geo_buffer = []
 
@@ -207,7 +209,7 @@ class HPGL2:
                     match = self.sp_re.search(gline)
                     if match:
                         tool = match.group(1)
-                        # self.tools[tool] = dict()
+                        # self.tools[tool] = {}
                         self.tools.update({
                             tool: {
                                 'tooldia': float('%.*f' %

+ 33 - 6
flatcamParsers/ParseSVG.py

@@ -21,7 +21,7 @@
 
 # import xml.etree.ElementTree as ET
 from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path
-from svg.path.path import Move
+from svg.path.path import Move, Close
 from shapely.geometry import LineString, LinearRing, MultiLineString
 from shapely.affinity import skew, affine_transform, rotate
 import numpy as np
@@ -69,6 +69,7 @@ def path2shapely(path, object_type, res=1.0):
     geometry = []
     geo_element = None
     rings = []
+    closed = False
 
     for component in path:
         # Line
@@ -88,7 +89,8 @@ def path2shapely(path, object_type, res=1.0):
 
             # How many points to use in the discrete representation.
             length = component.length(res / 10.0)
-            steps = int(length / res + 0.5)
+            # steps = int(length / res + 0.5)
+            steps = int(length) * 2
 
             # solve error when step is below 1,
             # it may cause other problems, but LineString needs at least  two points
@@ -109,11 +111,29 @@ def path2shapely(path, object_type, res=1.0):
 
         # Move
         if isinstance(component, Move):
+            if not points:
+                continue
+            else:
+                rings.append(points)
+                if closed is False:
+                    points = []
+                else:
+                    closed = False
+                    start = component.start
+                    x, y = start.real, start.imag
+                    points = [(x, y)]
+            continue
+
+        closed = False
+
+        # Close
+        if isinstance(component, Close):
             if not points:
                 continue
             else:
                 rings.append(points)
                 points = []
+                closed = True
             continue
         log.warning("I don't know what this is: %s" % str(component))
         continue
@@ -122,8 +142,12 @@ def path2shapely(path, object_type, res=1.0):
 
     if points:
         rings.append(points)
+    try:
+        rings = MultiLineString(rings)
+    except Exception as e:
+        log.debug("ParseSVG.path2shapely() MString --> %s" % str(e))
+        return None
 
-    rings = MultiLineString(rings)
     if len(rings) > 0:
         if len(rings) == 1 and not isinstance(rings, MultiLineString):
             # Polygons are closed and require more than 2 points
@@ -135,11 +159,14 @@ def path2shapely(path, object_type, res=1.0):
             try:
                 geo_element = Polygon(rings[0], rings[1:])
             except Exception:
-                coords = list()
+                coords = []
                 for line in rings:
                     coords.append(line.coords[0])
                     coords.append(line.coords[1])
-                geo_element = Polygon(coords)
+                try:
+                    geo_element = Polygon(coords)
+                except Exception:
+                    geo_element = LineString(coords)
         geometry.append(geo_element)
     return geometry
 
@@ -305,7 +332,7 @@ def getsvggeo(node, object_type, root=None):
         root = node
 
     kind = re.search('(?:\{.*\})?(.*)$', node.tag).group(1)
-    geo = list()
+    geo = []
 
     # Recurse
     if len(node) > 0:

+ 495 - 0
flatcamTools/ToolAlignObjects.py

@@ -0,0 +1,495 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# File Author: Marius Adrian Stanciu (c)                   #
+# Date: 1/13/2020                                          #
+# MIT Licence                                              #
+# ##########################################################
+
+from PyQt5 import QtWidgets, QtGui, QtCore
+from FlatCAMTool import FlatCAMTool
+
+from flatcamGUI.GUIElements import FCComboBox, RadioSet
+
+import math
+
+from shapely.geometry import Point
+from shapely.affinity import translate
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+import logging
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+log = logging.getLogger('base')
+
+
+class AlignObjects(FlatCAMTool):
+
+    toolName = _("Align Objects")
+
+    def __init__(self, app):
+        FlatCAMTool.__init__(self, app)
+
+        self.app = app
+        self.decimals = app.decimals
+
+        self.canvas = self.app.plotcanvas
+
+        # ## Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                        QLabel
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(title_label)
+
+        self.layout.addWidget(QtWidgets.QLabel(''))
+
+        # Form Layout
+        grid0 = QtWidgets.QGridLayout()
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+        self.layout.addLayout(grid0)
+
+        self.aligned_label = QtWidgets.QLabel('<b>%s:</b>' % _("MOVING object"))
+        grid0.addWidget(self.aligned_label, 0, 0, 1, 2)
+
+        self.aligned_label.setToolTip(
+            _("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.")
+        )
+
+        # Type of object to be aligned
+        self.type_obj_radio = RadioSet([
+            {"label": _("Gerber"), "value": "grb"},
+            {"label": _("Excellon"), "value": "exc"},
+        ], orientation='vertical', stretch=False)
+
+        grid0.addWidget(self.type_obj_radio, 3, 0, 1, 2)
+
+        # Object to be aligned
+        self.object_combo = FCComboBox()
+        self.object_combo.setModel(self.app.collection)
+        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.object_combo.is_last = True
+
+        self.object_combo.setToolTip(
+            _("Object to be aligned.")
+        )
+
+        grid0.addWidget(self.object_combo, 4, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 5, 0, 1, 2)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 6, 0, 1, 2)
+
+        self.aligned_label = QtWidgets.QLabel('<b>%s:</b>' % _("TARGET object"))
+        self.aligned_label.setToolTip(
+            _("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.")
+        )
+        grid0.addWidget(self.aligned_label, 7, 0, 1, 2)
+
+        # Type of object to be aligned to = aligner
+        self.type_aligner_obj_radio = RadioSet([
+            {"label": _("Gerber"), "value": "grb"},
+            {"label": _("Excellon"), "value": "exc"},
+        ], orientation='vertical', stretch=False)
+
+        grid0.addWidget(self.type_aligner_obj_radio, 8, 0, 1, 2)
+
+        # Object to be aligned to = aligner
+        self.aligner_object_combo = FCComboBox()
+        self.aligner_object_combo.setModel(self.app.collection)
+        self.aligner_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.aligner_object_combo.is_last = True
+
+        self.aligner_object_combo.setToolTip(
+            _("Object to be aligned to. Aligner.")
+        )
+
+        grid0.addWidget(self.aligner_object_combo, 9, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 10, 0, 1, 2)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 11, 0, 1, 2)
+
+        # Alignment Type
+        self.a_type_lbl = QtWidgets.QLabel('<b>%s:</b>' % _("Alignment Type"))
+        self.a_type_lbl.setToolTip(
+            _("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")
+        )
+        self.a_type_radio = RadioSet(
+            [
+                {'label': _('Single Point'), 'value': 'sp'},
+                {'label': _('Dual Point'), 'value': 'dp'}
+            ],
+            orientation='vertical',
+            stretch=False
+        )
+
+        grid0.addWidget(self.a_type_lbl, 12, 0, 1, 2)
+        grid0.addWidget(self.a_type_radio, 13, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 14, 0, 1, 2)
+
+        # Buttons
+        self.align_object_button = QtWidgets.QPushButton(_("Align Object"))
+        self.align_object_button.setToolTip(
+            _("Align the specified object to the aligner object.\n"
+              "If only one point is used then it assumes translation.\n"
+              "If tho points are used it assume translation and rotation.")
+        )
+        self.align_object_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.align_object_button)
+
+        self.layout.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.reset_button)
+
+        # Signals
+        self.align_object_button.clicked.connect(self.on_align)
+        self.type_obj_radio.activated_custom.connect(self.on_type_obj_changed)
+        self.type_aligner_obj_radio.activated_custom.connect(self.on_type_aligner_changed)
+        self.reset_button.clicked.connect(self.set_tool_ui)
+
+        self.mr = None
+
+        # if the mouse events are connected to a local method set this True
+        self.local_connected = False
+
+        # store the status of the grid
+        self.grid_status_memory = None
+
+        self.aligned_obj = None
+        self.aligner_obj = None
+
+        # this is one of the objects: self.aligned_obj or self.aligner_obj
+        self.target_obj = None
+
+        # here store the alignment points
+        self.clicked_points = []
+
+        self.align_type = None
+
+        # old colors of objects involved in the alignment
+        self.aligner_old_fill_color = None
+        self.aligner_old_line_color = None
+        self.aligned_old_fill_color = None
+        self.aligned_old_line_color = None
+
+    def run(self, toggle=True):
+        self.app.report_usage("ToolAlignObjects()")
+
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
+
+        self.app.ui.notebook.setTabText(2, _("Align Tool"))
+
+    def install(self, icon=None, separator=None, **kwargs):
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+A', **kwargs)
+
+    def set_tool_ui(self):
+        self.reset_fields()
+
+        self.clicked_points = []
+        self.target_obj = None
+        self.aligned_obj = None
+        self.aligner_obj = None
+
+        self.aligner_old_fill_color = None
+        self.aligner_old_line_color = None
+        self.aligned_old_fill_color = None
+        self.aligned_old_line_color = None
+
+        self.a_type_radio.set_value(self.app.defaults["tools_align_objects_align_type"])
+        self.type_obj_radio.set_value('grb')
+        self.type_aligner_obj_radio.set_value('grb')
+
+        if self.local_connected is True:
+            self.disconnect_cal_events()
+
+    def on_type_obj_changed(self, val):
+        obj_type = {'grb': 0, 'exc': 1}[val]
+        self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.object_combo.setCurrentIndex(0)
+        self.object_combo.obj_type = {'grb': "Gerber", 'exc': "Excellon"}[val]
+
+    def on_type_aligner_changed(self, val):
+        obj_type = {'grb': 0, 'exc': 1}[val]
+        self.aligner_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.aligner_object_combo.setCurrentIndex(0)
+        self.aligner_object_combo.obj_type = {'grb': "Gerber", 'exc': "Excellon"}[val]
+
+    def on_align(self):
+        self.app.delete_selection_shape()
+
+        obj_sel_index = self.object_combo.currentIndex()
+        obj_model_index = self.app.collection.index(obj_sel_index, 0, self.object_combo.rootModelIndex())
+        try:
+            self.aligned_obj = obj_model_index.internalPointer().obj
+        except AttributeError:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no aligned FlatCAM object selected..."))
+            return
+
+        aligner_obj_sel_index = self.aligner_object_combo.currentIndex()
+        aligner_obj_model_index = self.app.collection.index(
+            aligner_obj_sel_index, 0, self.aligner_object_combo.rootModelIndex())
+
+        try:
+            self.aligner_obj = aligner_obj_model_index.internalPointer().obj
+        except AttributeError:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no aligner FlatCAM object selected..."))
+            return
+
+        self.align_type = self.a_type_radio.get_value()
+
+        # disengage the grid snapping since it will be hard to find the drills or pads on grid
+        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.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
+
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+        else:
+            self.canvas.graph_event_disconnect(self.app.mr)
+
+        self.local_connected = True
+
+        self.aligner_old_fill_color = self.aligner_obj.fill_color
+        self.aligner_old_line_color = self.aligner_obj.outline_color
+        self.aligned_old_fill_color = self.aligned_obj.fill_color
+        self.aligned_old_line_color = self.aligned_obj.outline_color
+
+        self.app.inform.emit('%s: %s' % (_("First Point"), _("Click on the START point.")))
+        self.target_obj = self.aligned_obj
+        self.set_color()
+
+    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
+
+        pos_canvas = self.canvas.translate_coords(event_pos)
+
+        if event.button == 1:
+            click_pt = Point([pos_canvas[0], pos_canvas[1]])
+
+            if self.app.selection_type is not None:
+                # delete previous selection shape
+                self.app.delete_selection_shape()
+                self.app.selection_type = None
+            else:
+                if self.target_obj.kind.lower() == 'excellon':
+                    for tool, tool_dict in self.target_obj.tools.items():
+                        for geo in tool_dict['solid_geometry']:
+                            if click_pt.within(geo):
+                                center_pt = geo.centroid
+                                self.clicked_points.append(
+                                    [
+                                        float('%.*f' % (self.decimals, center_pt.x)),
+                                        float('%.*f' % (self.decimals, center_pt.y))
+                                    ]
+                                )
+                                self.check_points()
+                elif self.target_obj.kind.lower() == 'gerber':
+                    for apid, apid_val in self.target_obj.apertures.items():
+                        for geo_el in apid_val['geometry']:
+                            if 'solid' in geo_el:
+                                if click_pt.within(geo_el['solid']):
+                                    if isinstance(geo_el['follow'], Point):
+                                        center_pt = geo_el['solid'].centroid
+                                        self.clicked_points.append(
+                                            [
+                                                float('%.*f' % (self.decimals, center_pt.x)),
+                                                float('%.*f' % (self.decimals, center_pt.y))
+                                            ]
+                                        )
+                                        self.check_points()
+
+        elif event.button == right_button and self.app.event_is_dragging is False:
+            self.reset_color()
+            self.clicked_points = []
+            self.disconnect_cal_events()
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request."))
+
+    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.")))
+            self.target_obj = self.aligner_obj
+            self.reset_color()
+            self.set_color()
+
+        if len(self.clicked_points) == 2:
+            if self.align_type == 'sp':
+                self.align_translate()
+                self.app.inform.emit('[success] %s' % _("Done."))
+                self.app.plot_all()
+
+                self.disconnect_cal_events()
+                return
+            else:
+                self.app.inform.emit('%s: %s. %s' % (
+                    _("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.")))
+            self.target_obj = self.aligner_obj
+            self.reset_color()
+            self.set_color()
+
+        if len(self.clicked_points) == 4:
+            self.align_translate()
+            self.align_rotate()
+            self.app.inform.emit('[success] %s' % _("Done."))
+
+            self.disconnect_cal_events()
+            self.app.plot_all()
+
+    def align_translate(self):
+        dx = self.clicked_points[1][0] - self.clicked_points[0][0]
+        dy = self.clicked_points[1][1] - self.clicked_points[0][1]
+
+        self.aligned_obj.offset((dx, dy))
+
+        # Update the object bounding box options
+        a, b, c, d = self.aligned_obj.bounds()
+        self.aligned_obj.options['xmin'] = a
+        self.aligned_obj.options['ymin'] = b
+        self.aligned_obj.options['xmax'] = c
+        self.aligned_obj.options['ymax'] = d
+
+    def align_rotate(self):
+        dx = self.clicked_points[1][0] - self.clicked_points[0][0]
+        dy = self.clicked_points[1][1] - self.clicked_points[0][1]
+
+        test_rotation_pt = translate(Point(self.clicked_points[2]), xoff=dx, yoff=dy)
+        new_start = (test_rotation_pt.x, test_rotation_pt.y)
+        new_dest = self.clicked_points[3]
+
+        origin_pt = self.clicked_points[1]
+
+        dxd = new_dest[0] - origin_pt[0]
+        dyd = new_dest[1] - origin_pt[1]
+
+        dxs = new_start[0] - origin_pt[0]
+        dys = new_start[1] - origin_pt[1]
+
+        rotation_not_needed = (abs(new_start[0] - new_dest[0]) <= (10 ** -self.decimals)) or \
+                              (abs(new_start[1] - new_dest[1]) <= (10 ** -self.decimals))
+        if rotation_not_needed is False:
+            # calculate rotation angle
+            angle_dest = math.degrees(math.atan(dyd / dxd))
+            angle_start = math.degrees(math.atan(dys / dxs))
+            angle = angle_dest - angle_start
+            self.aligned_obj.rotate(angle=angle, point=origin_pt)
+
+    def disconnect_cal_events(self):
+        # restore the Grid snapping if it was active before
+        if self.grid_status_memory is True:
+            self.app.ui.grid_snap_btn.trigger()
+
+        self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mr)
+
+        self.local_connected = False
+
+        self.aligner_old_fill_color = None
+        self.aligner_old_line_color = None
+        self.aligned_old_fill_color = None
+        self.aligned_old_line_color = None
+
+    def set_color(self):
+        new_color = "#15678abf"
+        new_line_color = new_color
+        self.target_obj.shapes.redraw(
+            update_colors=(new_color, new_line_color)
+        )
+
+    def reset_color(self):
+        self.aligned_obj.shapes.redraw(
+            update_colors=(self.aligned_old_fill_color, self.aligned_old_line_color)
+        )
+
+        self.aligner_obj.shapes.redraw(
+            update_colors=(self.aligner_old_fill_color, self.aligner_old_line_color)
+        )
+
+    def reset_fields(self):
+        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.aligner_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 26 - 12
flatcamTools/ToolCalculators.py

@@ -92,7 +92,7 @@ class ToolCalculator(FlatCAMTool):
         self.layout.addLayout(form_layout)
 
         self.tipDia_label = QtWidgets.QLabel('%s:' % _("Tip Diameter"))
-        self.tipDia_entry = FCDoubleSpinner()
+        self.tipDia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.tipDia_entry.set_precision(self.decimals)
         self.tipDia_entry.set_range(0.0, 9999.9999)
         self.tipDia_entry.setSingleStep(0.1)
@@ -103,16 +103,16 @@ class ToolCalculator(FlatCAMTool):
               "It is specified by manufacturer.")
         )
         self.tipAngle_label = QtWidgets.QLabel('%s:' % _("Tip Angle"))
-        self.tipAngle_entry = FCSpinner()
+        self.tipAngle_entry = FCSpinner(callback=self.confirmation_message_int)
         self.tipAngle_entry.set_range(0,180)
-        self.tipAngle_entry.setSingleStep(5)
+        self.tipAngle_entry.set_step(5)
 
         # self.tipAngle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.tipAngle_label.setToolTip(_("This is the angle of the tip of the tool.\n"
                                          "It is specified by manufacturer."))
 
         self.cutDepth_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
-        self.cutDepth_entry = FCDoubleSpinner()
+        self.cutDepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.cutDepth_entry.set_range(-9999.9999, 9999.9999)
         self.cutDepth_entry.set_precision(self.decimals)
 
@@ -121,7 +121,7 @@ class ToolCalculator(FlatCAMTool):
                                          "In the CNCJob is the CutZ parameter."))
 
         self.effectiveToolDia_label = QtWidgets.QLabel('%s:' % _("Tool Diameter"))
-        self.effectiveToolDia_entry = FCDoubleSpinner()
+        self.effectiveToolDia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.effectiveToolDia_entry.set_precision(self.decimals)
 
         # self.effectiveToolDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
@@ -165,7 +165,7 @@ class ToolCalculator(FlatCAMTool):
         self.layout.addLayout(plate_form_layout)
 
         self.pcblengthlabel = QtWidgets.QLabel('%s:' % _("Board Length"))
-        self.pcblength_entry = FCDoubleSpinner()
+        self.pcblength_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.pcblength_entry.set_precision(self.decimals)
         self.pcblength_entry.set_range(0.0, 9999.9999)
 
@@ -173,7 +173,7 @@ class ToolCalculator(FlatCAMTool):
         self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
 
         self.pcbwidthlabel = QtWidgets.QLabel('%s:' % _("Board Width"))
-        self.pcbwidth_entry = FCDoubleSpinner()
+        self.pcbwidth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.pcbwidth_entry.set_precision(self.decimals)
         self.pcbwidth_entry.set_range(0.0, 9999.9999)
 
@@ -181,7 +181,7 @@ class ToolCalculator(FlatCAMTool):
         self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
 
         self.cdensity_label = QtWidgets.QLabel('%s:' % _("Current Density"))
-        self.cdensity_entry = FCDoubleSpinner()
+        self.cdensity_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.cdensity_entry.set_precision(self.decimals)
         self.cdensity_entry.set_range(0.0, 9999.9999)
         self.cdensity_entry.setSingleStep(0.1)
@@ -191,7 +191,7 @@ class ToolCalculator(FlatCAMTool):
                                          "In Amps per Square Feet ASF."))
 
         self.growth_label = QtWidgets.QLabel('%s:' % _("Copper Growth"))
-        self.growth_entry = FCDoubleSpinner()
+        self.growth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.growth_entry.set_precision(self.decimals)
         self.growth_entry.set_range(0.0, 9999.9999)
         self.growth_entry.setSingleStep(0.01)
@@ -203,7 +203,7 @@ class ToolCalculator(FlatCAMTool):
         # self.growth_entry.setEnabled(False)
 
         self.cvaluelabel = QtWidgets.QLabel('%s:' % _("Current Value"))
-        self.cvalue_entry = FCDoubleSpinner()
+        self.cvalue_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.cvalue_entry.set_precision(self.decimals)
         self.cvalue_entry.set_range(0.0, 9999.9999)
         self.cvalue_entry.setSingleStep(0.1)
@@ -214,7 +214,7 @@ class ToolCalculator(FlatCAMTool):
         self.cvalue_entry.setReadOnly(True)
 
         self.timelabel = QtWidgets.QLabel('%s:' % _("Time"))
-        self.time_entry = FCDoubleSpinner()
+        self.time_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.time_entry.set_precision(self.decimals)
         self.time_entry.set_range(0.0, 9999.9999)
         self.time_entry.setSingleStep(0.1)
@@ -242,6 +242,19 @@ class ToolCalculator(FlatCAMTool):
 
         self.layout.addStretch()
 
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.reset_button)
+
         self.units = ''
 
         # ## Signals
@@ -255,6 +268,7 @@ class ToolCalculator(FlatCAMTool):
         self.inch_entry.editingFinished.connect(self.on_calculate_mm_units)
 
         self.calculate_plate_button.clicked.connect(self.on_calculate_eplate)
+        self.reset_button.clicked.connect(self.set_tool_ui)
 
     def run(self, toggle=True):
         self.app.report_usage("ToolCalculators()")
@@ -285,7 +299,7 @@ class ToolCalculator(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Calc. Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+C', **kwargs)
 
     def set_tool_ui(self):
         self.units = self.app.defaults['units'].upper()

+ 41 - 27
flatcamTools/ToolCalibration.py

@@ -76,7 +76,7 @@ class ToolCalibration(FlatCAMTool):
             _("Height (Z) for travelling between the points.")
         )
 
-        self.travelz_entry = FCDoubleSpinner()
+        self.travelz_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.travelz_entry.set_range(-9999.9999, 9999.9999)
         self.travelz_entry.set_precision(self.decimals)
         self.travelz_entry.setSingleStep(0.1)
@@ -90,7 +90,7 @@ class ToolCalibration(FlatCAMTool):
             _("Height (Z) for checking the point.")
         )
 
-        self.verz_entry = FCDoubleSpinner()
+        self.verz_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.verz_entry.set_range(-9999.9999, 9999.9999)
         self.verz_entry.set_precision(self.decimals)
         self.verz_entry.setSingleStep(0.1)
@@ -113,7 +113,7 @@ class ToolCalibration(FlatCAMTool):
             _("Height (Z) for mounting the verification probe.")
         )
 
-        self.toolchangez_entry = FCDoubleSpinner()
+        self.toolchangez_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.toolchangez_entry.set_range(0.0000, 9999.9999)
         self.toolchangez_entry.set_precision(self.decimals)
         self.toolchangez_entry.setSingleStep(0.1)
@@ -195,7 +195,6 @@ class ToolCalibration(FlatCAMTool):
         self.obj_type_combo = FCComboBox()
         self.obj_type_combo.addItem(_("Gerber"))
         self.obj_type_combo.addItem(_("Excellon"))
-        self.obj_type_combo.setCurrentIndex(1)
 
         self.obj_type_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.obj_type_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
@@ -206,7 +205,7 @@ class ToolCalibration(FlatCAMTool):
         self.object_combo = FCComboBox()
         self.object_combo.setModel(self.app.collection)
         self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.object_combo.setCurrentIndex(1)
+        self.object_combo.is_last = True
 
         self.object_label = QtWidgets.QLabel("%s:" % _("Source object selection"))
         self.object_label.setToolTip(
@@ -263,8 +262,6 @@ class ToolCalibration(FlatCAMTool):
         self.bottom_left_coordy_found = EvalEntry()
         self.points_table.setCellWidget(row, 3, self.bottom_left_coordy_found)
 
-        self.bottom_left_coordx_found.set_value(_("Origin"))
-        self.bottom_left_coordy_found.set_value(_("Origin"))
         self.bottom_left_coordx_found.setDisabled(True)
         self.bottom_left_coordy_found.setDisabled(True)
         row += 1
@@ -471,7 +468,7 @@ class ToolCalibration(FlatCAMTool):
         self.scalex_label.setToolTip(
             _("Factor for Scale action over X axis.")
         )
-        self.scalex_entry = FCDoubleSpinner()
+        self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.scalex_entry.set_range(0, 9999.9999)
         self.scalex_entry.set_precision(self.decimals)
         self.scalex_entry.setSingleStep(0.1)
@@ -483,7 +480,7 @@ class ToolCalibration(FlatCAMTool):
         self.scaley_label.setToolTip(
             _("Factor for Scale action over Y axis.")
         )
-        self.scaley_entry = FCDoubleSpinner()
+        self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.scaley_entry.set_range(0, 9999.9999)
         self.scaley_entry.set_precision(self.decimals)
         self.scaley_entry.setSingleStep(0.1)
@@ -508,7 +505,7 @@ class ToolCalibration(FlatCAMTool):
             _("Angle for Skew action, in degrees.\n"
               "Float number between -360 and 359.")
         )
-        self.skewx_entry = FCDoubleSpinner()
+        self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.skewx_entry.set_range(-360, 360)
         self.skewx_entry.set_precision(self.decimals)
         self.skewx_entry.setSingleStep(0.1)
@@ -521,7 +518,7 @@ class ToolCalibration(FlatCAMTool):
             _("Angle for Skew action, in degrees.\n"
               "Float number between -360 and 359.")
         )
-        self.skewy_entry = FCDoubleSpinner()
+        self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.skewy_entry.set_range(-360, 360)
         self.skewy_entry.set_precision(self.decimals)
         self.skewy_entry.setSingleStep(0.1)
@@ -552,7 +549,7 @@ class ToolCalibration(FlatCAMTool):
         # self.fin_scalex_label.setToolTip(
         #     _("Final factor for Scale action over X axis.")
         # )
-        # self.fin_scalex_entry = FCDoubleSpinner()
+        # self.fin_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.fin_scalex_entry.set_range(0, 9999.9999)
         # self.fin_scalex_entry.set_precision(self.decimals)
         # self.fin_scalex_entry.setSingleStep(0.1)
@@ -564,7 +561,7 @@ class ToolCalibration(FlatCAMTool):
         # self.fin_scaley_label.setToolTip(
         #     _("Final factor for Scale action over Y axis.")
         # )
-        # self.fin_scaley_entry = FCDoubleSpinner()
+        # self.fin_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.fin_scaley_entry.set_range(0, 9999.9999)
         # self.fin_scaley_entry.set_precision(self.decimals)
         # self.fin_scaley_entry.setSingleStep(0.1)
@@ -577,7 +574,7 @@ class ToolCalibration(FlatCAMTool):
         #     _("Final value for angle for Skew action, in degrees.\n"
         #       "Float number between -360 and 359.")
         # )
-        # self.fin_skewx_entry = FCDoubleSpinner()
+        # self.fin_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.fin_skewx_entry.set_range(-360, 360)
         # self.fin_skewx_entry.set_precision(self.decimals)
         # self.fin_skewx_entry.setSingleStep(0.1)
@@ -590,7 +587,7 @@ class ToolCalibration(FlatCAMTool):
         #     _("Final value for angle for Skew action, in degrees.\n"
         #       "Float number between -360 and 359.")
         # )
-        # self.fin_skewy_entry = FCDoubleSpinner()
+        # self.fin_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.fin_skewy_entry.set_range(-360, 360)
         # self.fin_skewy_entry.set_precision(self.decimals)
         # self.fin_skewy_entry.setSingleStep(0.1)
@@ -630,18 +627,15 @@ class ToolCalibration(FlatCAMTool):
         )
         grid_lay.addWidget(step_5, 45, 0, 1, 3)
 
-        self.adj_object_type_combo = QtWidgets.QComboBox()
+        self.adj_object_type_combo = FCComboBox()
         self.adj_object_type_combo.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
-        self.adj_object_type_combo.setCurrentIndex(0)
 
         self.adj_object_type_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.adj_object_type_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
         self.adj_object_type_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
         self.adj_object_type_label = QtWidgets.QLabel("%s:" % _("Adjusted object type"))
-        self.adj_object_type_label.setToolTip(
-            _("Type of the FlatCAM Object to be adjusted.")
-        )
+        self.adj_object_type_label.setToolTip(_("Type of the FlatCAM Object to be adjusted."))
 
         grid_lay.addWidget(self.adj_object_type_label, 46, 0, 1, 3)
         grid_lay.addWidget(self.adj_object_type_combo, 47, 0, 1, 3)
@@ -649,7 +643,10 @@ class ToolCalibration(FlatCAMTool):
         self.adj_object_combo = FCComboBox()
         self.adj_object_combo.setModel(self.app.collection)
         self.adj_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.adj_object_combo.setCurrentIndex(0)
+        self.adj_object_combo.is_last = True
+        self.adj_object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.adj_object_type_combo.get_value()]
 
         self.adj_object_label = QtWidgets.QLabel("%s:" % _("Adjusted object selection"))
         self.adj_object_label.setToolTip(
@@ -762,7 +759,7 @@ class ToolCalibration(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Calibration Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+E', **kwargs)
 
     def set_tool_ui(self):
         self.units = self.app.defaults['units'].upper()
@@ -770,6 +767,9 @@ class ToolCalibration(FlatCAMTool):
         if self.local_connected is True:
             self.disconnect_cal_events()
 
+        self.bottom_left_coordx_found.set_value(_("Origin"))
+        self.bottom_left_coordy_found.set_value(_("Origin"))
+
         self.reset_calibration_points()
 
         self.cal_source_radio.set_value(self.app.defaults['tools_cal_calsource'])
@@ -786,6 +786,14 @@ class ToolCalibration(FlatCAMTool):
         self.skewx_entry.set_value(0.0)
         self.skewy_entry.set_value(0.0)
 
+        # default object selection is Excellon = index_1
+        self.obj_type_combo.setCurrentIndex(1)
+        self.on_obj_type_combo()
+
+        self.adj_object_type_combo.setCurrentIndex(0)
+        self.on_adj_obj_type_combo()
+        # self.adj_object_combo.setCurrentIndex(0)
+
         # calibrated object
         self.cal_object = None
 
@@ -794,12 +802,18 @@ class ToolCalibration(FlatCAMTool):
     def on_obj_type_combo(self):
         obj_type = self.obj_type_combo.currentIndex()
         self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.object_combo.setCurrentIndex(0)
+        # self.object_combo.setCurrentIndex(0)
+        self.object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon"
+        }[self.obj_type_combo.get_value()]
 
     def on_adj_obj_type_combo(self):
         obj_type = self.adj_object_type_combo.currentIndex()
         self.adj_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.adj_object_combo.setCurrentIndex(0)
+        # self.adj_object_combo.setCurrentIndex(0)
+        self.adj_object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.adj_object_type_combo.get_value()]
 
     def on_cal_source_radio(self, val):
         if val == 'object':
@@ -925,7 +939,7 @@ class ToolCalibration(FlatCAMTool):
             self.disconnect_cal_events()
 
     def reset_calibration_points(self):
-        self.click_points = list()
+        self.click_points = []
 
         self.bottom_left_coordx_tgt.set_value('')
         self.bottom_left_coordy_tgt.set_value('')
@@ -1275,7 +1289,7 @@ class ToolCalibration(FlatCAMTool):
                 if obj.tools:
                     obj_init.tools = deepcopy(obj.tools)
             except Exception as ee:
-                log.debug("App.on_copy_object() --> %s" % str(ee))
+                log.debug("ToolCalibration.new_calibrated_object.initialize_geometry() --> %s" % str(ee))
 
             obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
             obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
@@ -1301,7 +1315,7 @@ class ToolCalibration(FlatCAMTool):
                 if obj.tools:
                     obj_init.tools = deepcopy(obj.tools)
             except Exception as err:
-                log.debug("App.on_copy_object() --> %s" % str(err))
+                log.debug("ToolCalibration.new_calibrated_object.initialize_gerber() --> %s" % str(err))
 
             obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
             obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))

+ 92 - 86
flatcamTools/ToolCopperThieving.py

@@ -9,7 +9,7 @@ from PyQt5 import QtWidgets, QtCore
 
 import FlatCAMApp
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, FCEntry
+from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, FCEntry, FCComboBox
 from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon
 
 import shapely.geometry.base as base
@@ -66,10 +66,11 @@ class ToolCopperThieving(FlatCAMTool):
         i_grid_lay.setColumnStretch(0, 0)
         i_grid_lay.setColumnStretch(1, 1)
 
-        self.grb_object_combo = QtWidgets.QComboBox()
+        self.grb_object_combo = FCComboBox()
         self.grb_object_combo.setModel(self.app.collection)
         self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.grb_object_combo.setCurrentIndex(1)
+        self.grb_object_combo.is_last = True
+        self.grb_object_combo.obj_type = 'Gerber'
 
         self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
         self.grbobj_label.setToolTip(
@@ -99,7 +100,7 @@ class ToolCopperThieving(FlatCAMTool):
               "(the polygon fill may be split in multiple polygons)\n"
               "and the copper traces in the Gerber file.")
         )
-        self.clearance_entry = FCDoubleSpinner()
+        self.clearance_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_entry.set_range(0.00001, 9999.9999)
         self.clearance_entry.set_precision(self.decimals)
         self.clearance_entry.setSingleStep(0.1)
@@ -112,7 +113,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.margin_label.setToolTip(
             _("Bounding box margin.")
         )
-        self.margin_entry = FCDoubleSpinner()
+        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.margin_entry.set_range(0.0, 9999.9999)
         self.margin_entry.set_precision(self.decimals)
         self.margin_entry.setSingleStep(0.1)
@@ -135,35 +136,36 @@ class ToolCopperThieving(FlatCAMTool):
         grid_lay.addWidget(self.reference_label, 3, 0)
         grid_lay.addWidget(self.reference_radio, 3, 1)
 
-        self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
-        self.box_combo_type_label.setToolTip(
+        self.ref_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
+        self.ref_combo_type_label.setToolTip(
             _("The type of FlatCAM object to be used as copper thieving reference.\n"
               "It can be Gerber, Excellon or Geometry.")
         )
-        self.box_combo_type = QtWidgets.QComboBox()
-        self.box_combo_type.addItem(_("Reference Gerber"))
-        self.box_combo_type.addItem(_("Reference Excellon"))
-        self.box_combo_type.addItem(_("Reference Geometry"))
+        self.ref_combo_type = FCComboBox()
+        self.ref_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
 
-        grid_lay.addWidget(self.box_combo_type_label, 4, 0)
-        grid_lay.addWidget(self.box_combo_type, 4, 1)
+        grid_lay.addWidget(self.ref_combo_type_label, 4, 0)
+        grid_lay.addWidget(self.ref_combo_type, 4, 1)
 
-        self.box_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
-        self.box_combo_label.setToolTip(
+        self.ref_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
+        self.ref_combo_label.setToolTip(
             _("The FlatCAM object to be used as non copper clearing reference.")
         )
-        self.box_combo = QtWidgets.QComboBox()
-        self.box_combo.setModel(self.app.collection)
-        self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.box_combo.setCurrentIndex(1)
-
-        grid_lay.addWidget(self.box_combo_label, 5, 0)
-        grid_lay.addWidget(self.box_combo, 5, 1)
-
-        self.box_combo.hide()
-        self.box_combo_label.hide()
-        self.box_combo_type.hide()
-        self.box_combo_type_label.hide()
+        self.ref_combo = FCComboBox()
+        self.ref_combo.setModel(self.app.collection)
+        self.ref_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ref_combo.is_last = True
+        self.ref_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.ref_combo_type.get_value()]
+
+        grid_lay.addWidget(self.ref_combo_label, 5, 0)
+        grid_lay.addWidget(self.ref_combo, 5, 1)
+
+        self.ref_combo.hide()
+        self.ref_combo_label.hide()
+        self.ref_combo_type.hide()
+        self.ref_combo_type_label.hide()
 
         # Bounding Box Type #
         self.bbox_type_radio = RadioSet([
@@ -221,7 +223,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.dotdia_label.setToolTip(
             _("Dot diameter in Dots Grid.")
         )
-        self.dot_dia_entry = FCDoubleSpinner()
+        self.dot_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.dot_dia_entry.set_range(0.0, 9999.9999)
         self.dot_dia_entry.set_precision(self.decimals)
         self.dot_dia_entry.setSingleStep(0.1)
@@ -234,7 +236,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.dotspacing_label.setToolTip(
             _("Distance between each two dots in Dots Grid.")
         )
-        self.dot_spacing_entry = FCDoubleSpinner()
+        self.dot_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.dot_spacing_entry.set_range(0.0, 9999.9999)
         self.dot_spacing_entry.set_precision(self.decimals)
         self.dot_spacing_entry.setSingleStep(0.1)
@@ -261,7 +263,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.square_size_label.setToolTip(
             _("Square side size in Squares Grid.")
         )
-        self.square_size_entry = FCDoubleSpinner()
+        self.square_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.square_size_entry.set_range(0.0, 9999.9999)
         self.square_size_entry.set_precision(self.decimals)
         self.square_size_entry.setSingleStep(0.1)
@@ -274,7 +276,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.squares_spacing_label.setToolTip(
             _("Distance between each two squares in Squares Grid.")
         )
-        self.squares_spacing_entry = FCDoubleSpinner()
+        self.squares_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.squares_spacing_entry.set_range(0.0, 9999.9999)
         self.squares_spacing_entry.set_precision(self.decimals)
         self.squares_spacing_entry.setSingleStep(0.1)
@@ -301,7 +303,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.line_size_label.setToolTip(
             _("Line thickness size in Lines Grid.")
         )
-        self.line_size_entry = FCDoubleSpinner()
+        self.line_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.line_size_entry.set_range(0.0, 9999.9999)
         self.line_size_entry.set_precision(self.decimals)
         self.line_size_entry.setSingleStep(0.1)
@@ -314,7 +316,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.lines_spacing_label.setToolTip(
             _("Distance between each two lines in Lines Grid.")
         )
-        self.lines_spacing_entry = FCDoubleSpinner()
+        self.lines_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.lines_spacing_entry.set_range(0.0, 9999.9999)
         self.lines_spacing_entry.set_precision(self.decimals)
         self.lines_spacing_entry.setSingleStep(0.1)
@@ -362,7 +364,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.rb_margin_label.setToolTip(
             _("Bounding box margin for robber bar.")
         )
-        self.rb_margin_entry = FCDoubleSpinner()
+        self.rb_margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.rb_margin_entry.set_range(-9999.9999, 9999.9999)
         self.rb_margin_entry.set_precision(self.decimals)
         self.rb_margin_entry.setSingleStep(0.1)
@@ -375,7 +377,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.rb_thickness_label.setToolTip(
             _("The robber bar thickness.")
         )
-        self.rb_thickness_entry = FCDoubleSpinner()
+        self.rb_thickness_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.rb_thickness_entry.set_range(0.0000, 9999.9999)
         self.rb_thickness_entry.set_precision(self.decimals)
         self.rb_thickness_entry.setSingleStep(0.1)
@@ -417,10 +419,11 @@ class ToolCopperThieving(FlatCAMTool):
               "the pattern plating mask.")
         )
 
-        self.sm_object_combo = QtWidgets.QComboBox()
+        self.sm_object_combo = FCComboBox()
         self.sm_object_combo.setModel(self.app.collection)
         self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.sm_object_combo.setCurrentIndex(1)
+        self.sm_object_combo.is_last = True
+        self.sm_object_combo.obj_type = 'Gerber'
 
         grid_lay_1.addWidget(self.sm_obj_label, 7, 0, 1, 3)
         grid_lay_1.addWidget(self.sm_object_combo, 8, 0, 1, 3)
@@ -431,7 +434,7 @@ class ToolCopperThieving(FlatCAMTool):
             _("The distance between the possible copper thieving elements\n"
               "and/or robber bar and the actual openings in the mask.")
         )
-        self.clearance_ppm_entry = FCDoubleSpinner()
+        self.clearance_ppm_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_ppm_entry.set_range(-9999.9999, 9999.9999)
         self.clearance_ppm_entry.set_precision(self.decimals)
         self.clearance_ppm_entry.setSingleStep(0.1)
@@ -494,11 +497,11 @@ class ToolCopperThieving(FlatCAMTool):
         # Objects involved in Copper thieving
         self.grb_object = None
         self.ref_obj = None
-        self.sel_rect = list()
+        self.sel_rect = []
         self.sm_object = None
 
         # store the flattened geometry here:
-        self.flat_geometry = list()
+        self.flat_geometry = []
 
         # Events ID
         self.mr = None
@@ -517,7 +520,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.geo_steps_per_circle = 128
 
         # Thieving geometry storage
-        self.new_solid_geometry = list()
+        self.new_solid_geometry = []
 
         # Robber bar geometry storage
         self.robber_geo = None
@@ -526,7 +529,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.rb_thickness = None
 
         # SIGNALS
-        self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
+        self.ref_combo_type.currentIndexChanged.connect(self.on_ref_combo_type_change)
         self.reference_radio.group_toggle_fn = self.on_toggle_reference
         self.fill_type_radio.activated_custom.connect(self.on_thieving_type)
 
@@ -566,7 +569,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Copper Thieving Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+F', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+F', **kwargs)
 
     def set_tool_ui(self):
         self.units = self.app.defaults['units']
@@ -594,22 +597,25 @@ class ToolCopperThieving(FlatCAMTool):
         self.robber_line = None
         self.new_solid_geometry = None
 
-    def on_combo_box_type(self):
-        obj_type = self.box_combo_type.currentIndex()
-        self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.box_combo.setCurrentIndex(0)
+    def on_ref_combo_type_change(self):
+        obj_type = self.ref_combo_type.currentIndex()
+        self.ref_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ref_combo.setCurrentIndex(0)
+        self.ref_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.ref_combo_type.get_value()]
 
     def on_toggle_reference(self):
         if self.reference_radio.get_value() == "itself" or self.reference_radio.get_value() == "area":
-            self.box_combo.hide()
-            self.box_combo_label.hide()
-            self.box_combo_type.hide()
-            self.box_combo_type_label.hide()
+            self.ref_combo.hide()
+            self.ref_combo_label.hide()
+            self.ref_combo_type.hide()
+            self.ref_combo_type_label.hide()
         else:
-            self.box_combo.show()
-            self.box_combo_label.show()
-            self.box_combo_type.show()
-            self.box_combo_type_label.show()
+            self.ref_combo.show()
+            self.ref_combo_label.show()
+            self.ref_combo_type.show()
+            self.ref_combo_type_label.show()
 
         if self.reference_radio.get_value() == "itself":
             self.bbox_type_label.show()
@@ -681,7 +687,7 @@ class ToolCopperThieving(FlatCAMTool):
                 break
 
         if aperture_found:
-            geo_elem = dict()
+            geo_elem = {}
             geo_elem['solid'] = self.robber_geo
             geo_elem['follow'] = self.robber_line
             self.grb_object.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem))
@@ -692,19 +698,19 @@ class ToolCopperThieving(FlatCAMTool):
             else:
                 new_apid = '10'
 
-            self.grb_object.apertures[new_apid] = dict()
+            self.grb_object.apertures[new_apid] = {}
             self.grb_object.apertures[new_apid]['type'] = 'C'
             self.grb_object.apertures[new_apid]['size'] = self.rb_thickness
-            self.grb_object.apertures[new_apid]['geometry'] = list()
+            self.grb_object.apertures[new_apid]['geometry'] = []
 
-            geo_elem = dict()
+            geo_elem = {}
             geo_elem['solid'] = self.robber_geo
             geo_elem['follow'] = self.robber_line
             self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
 
         geo_obj = self.grb_object.solid_geometry
         if isinstance(geo_obj, MultiPolygon):
-            s_list = list()
+            s_list = []
             for pol in geo_obj.geoms:
                 s_list.append(pol)
             s_list.append(self.robber_geo)
@@ -778,7 +784,7 @@ class ToolCopperThieving(FlatCAMTool):
             self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
 
         elif reference_method == 'box':
-            bound_obj_name = self.box_combo.currentText()
+            bound_obj_name = self.ref_combo.currentText()
 
             # Get reference object.
             try:
@@ -911,10 +917,10 @@ class ToolCopperThieving(FlatCAMTool):
         if self.cursor_pos is None:
             self.cursor_pos = (0, 0)
 
-        dx = curr_pos[0] - float(self.cursor_pos[0])
-        dy = curr_pos[1] - float(self.cursor_pos[1])
+        self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
+        self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
         self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
 
         # draw the utility geometry
         if self.first_click:
@@ -1127,7 +1133,7 @@ class ToolCopperThieving(FlatCAMTool):
 
             if fill_type == 'dot' or fill_type == 'square':
                 # build the MultiPolygon of dots/squares that will fill the entire bounding box
-                thieving_list = list()
+                thieving_list = []
 
                 if fill_type == 'dot':
                     radius = dot_dia / 2.0
@@ -1169,7 +1175,7 @@ class ToolCopperThieving(FlatCAMTool):
                 except TypeError:
                     thieving_box_geo = [thieving_box_geo]
 
-                thieving_geo = list()
+                thieving_geo = []
                 for dot_geo in thieving_box_geo:
                     for geo_t in app_obj.new_solid_geometry:
                         if dot_geo.within(geo_t):
@@ -1212,7 +1218,7 @@ class ToolCopperThieving(FlatCAMTool):
                 app_obj.app.proc_container.update_view_text(' %s' % _("Buffering"))
                 outline_geometry = unary_union(outline_geometry)
 
-                outline_line = list()
+                outline_line = []
                 try:
                     for geo_o in outline_geometry:
                         outline_line.append(
@@ -1238,7 +1244,7 @@ class ToolCopperThieving(FlatCAMTool):
                 )
 
                 bx0, by0, bx1, by1 = box_outline_geo.bounds
-                thieving_lines_geo = list()
+                thieving_lines_geo = []
                 new_x = bx0
                 new_y = by0
                 while new_x <= x1 - half_thick_line:
@@ -1258,7 +1264,7 @@ class ToolCopperThieving(FlatCAMTool):
                     new_y += line_size + line_spacing
 
                 # merge everything together
-                diff_lines_geo = list()
+                diff_lines_geo = []
                 for line_poly in thieving_lines_geo:
                     rest_line = line_poly.difference(clearance_geometry)
                     diff_lines_geo.append(rest_line)
@@ -1271,8 +1277,8 @@ class ToolCopperThieving(FlatCAMTool):
                 geo_list = list(app_obj.grb_object.solid_geometry.geoms)
 
             if '0' not in app_obj.grb_object.apertures:
-                app_obj.grb_object.apertures['0'] = dict()
-                app_obj.grb_object.apertures['0']['geometry'] = list()
+                app_obj.grb_object.apertures['0'] = {}
+                app_obj.grb_object.apertures['0']['geometry'] = []
                 app_obj.grb_object.apertures['0']['type'] = 'REG'
                 app_obj.grb_object.apertures['0']['size'] = 0.0
 
@@ -1282,7 +1288,7 @@ class ToolCopperThieving(FlatCAMTool):
                     geo_list.append(poly)
 
                     # append into the '0' aperture
-                    geo_elem = dict()
+                    geo_elem = {}
                     geo_elem['solid'] = poly
                     geo_elem['follow'] = poly.exterior
                     app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
@@ -1291,7 +1297,7 @@ class ToolCopperThieving(FlatCAMTool):
                 geo_list.append(app_obj.new_solid_geometry)
 
                 # append into the '0' aperture
-                geo_elem = dict()
+                geo_elem = {}
                 geo_elem['solid'] = app_obj.new_solid_geometry
                 geo_elem['follow'] = app_obj.new_solid_geometry.exterior
                 app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
@@ -1350,7 +1356,7 @@ class ToolCopperThieving(FlatCAMTool):
 
         # if the clearance is negative apply it to the original soldermask too
         if ppm_clearance < 0:
-            temp_geo_list = list()
+            temp_geo_list = []
             for geo in geo_list:
                 temp_geo_list.append(geo.buffer(ppm_clearance))
             geo_list = temp_geo_list
@@ -1372,11 +1378,11 @@ class ToolCopperThieving(FlatCAMTool):
 
         def obj_init(grb_obj, app_obj):
             grb_obj.multitool = False
-            grb_obj.source_file = list()
+            grb_obj.source_file = []
             grb_obj.multigeo = False
             grb_obj.follow = False
-            grb_obj.apertures = dict()
-            grb_obj.solid_geometry = list()
+            grb_obj.apertures = {}
+            grb_obj.solid_geometry = []
 
             # try:
             #     grb_obj.options['xmin'] = 0
@@ -1389,8 +1395,8 @@ class ToolCopperThieving(FlatCAMTool):
             # if we have copper thieving geometry, add it
             if thieving_solid_geo:
                 if '0' not in grb_obj.apertures:
-                    grb_obj.apertures['0'] = dict()
-                    grb_obj.apertures['0']['geometry'] = list()
+                    grb_obj.apertures['0'] = {}
+                    grb_obj.apertures['0']['geometry'] = []
                     grb_obj.apertures['0']['type'] = 'REG'
                     grb_obj.apertures['0']['size'] = 0.0
 
@@ -1402,7 +1408,7 @@ class ToolCopperThieving(FlatCAMTool):
                         geo_list.append(poly_b)
 
                         # append into the '0' aperture
-                        geo_elem = dict()
+                        geo_elem = {}
                         geo_elem['solid'] = poly_b
                         geo_elem['follow'] = poly_b.exterior
                         grb_obj.apertures['0']['geometry'].append(deepcopy(geo_elem))
@@ -1411,7 +1417,7 @@ class ToolCopperThieving(FlatCAMTool):
                     geo_list.append(thieving_solid_geo.buffer(ppm_clearance))
 
                     # append into the '0' aperture
-                    geo_elem = dict()
+                    geo_elem = {}
                     geo_elem['solid'] = thieving_solid_geo.buffer(ppm_clearance)
                     geo_elem['follow'] = thieving_solid_geo.buffer(ppm_clearance).exterior
                     grb_obj.apertures['0']['geometry'].append(deepcopy(geo_elem))
@@ -1425,7 +1431,7 @@ class ToolCopperThieving(FlatCAMTool):
                         break
 
                 if aperture_found:
-                    geo_elem = dict()
+                    geo_elem = {}
                     geo_elem['solid'] = robber_solid_geo
                     geo_elem['follow'] = robber_line
                     grb_obj.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem))
@@ -1437,12 +1443,12 @@ class ToolCopperThieving(FlatCAMTool):
                     else:
                         new_apid = '10'
 
-                    grb_obj.apertures[new_apid] = dict()
+                    grb_obj.apertures[new_apid] = {}
                     grb_obj.apertures[new_apid]['type'] = 'C'
                     grb_obj.apertures[new_apid]['size'] = rb_thickness + ppm_clearance
-                    grb_obj.apertures[new_apid]['geometry'] = list()
+                    grb_obj.apertures[new_apid]['geometry'] = []
 
-                    geo_elem = dict()
+                    geo_elem = {}
                     geo_elem['solid'] = robber_solid_geo.buffer(ppm_clearance)
                     geo_elem['follow'] = Polygon(robber_line).buffer(ppm_clearance / 2.0).exterior
                     grb_obj.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
@@ -1510,7 +1516,7 @@ class ToolCopperThieving(FlatCAMTool):
         self.grb_object = None
         self.sm_object = None
         self.ref_obj = None
-        self.sel_rect = list()
+        self.sel_rect = []
 
         # Events ID
         self.mr = None

+ 142 - 95
flatcamTools/ToolCutOut.py

@@ -7,7 +7,7 @@
 
 from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, OptionalInputSection
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, OptionalInputSection, FCButton
 from FlatCAMObj import FlatCAMGerber
 
 from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing
@@ -59,49 +59,21 @@ class CutOut(FlatCAMTool):
                         """)
         self.layout.addWidget(title_label)
 
+        self.layout.addWidget(QtWidgets.QLabel(''))
+
         # Form Layout
         grid0 = QtWidgets.QGridLayout()
         grid0.setColumnStretch(0, 0)
         grid0.setColumnStretch(1, 1)
         self.layout.addLayout(grid0)
 
-        # Type of object to be cutout
-        self.type_obj_combo = QtWidgets.QComboBox()
-        self.type_obj_combo.addItem("Gerber")
-        self.type_obj_combo.addItem("Excellon")
-        self.type_obj_combo.addItem("Geometry")
-
-        # we get rid of item1 ("Excellon") as it is not suitable for creating film
-        self.type_obj_combo.view().setRowHidden(1, True)
-        self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
-        # self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
-        self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
-
-        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
-        self.type_obj_combo_label.setToolTip(
-            _("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.")
-        )
-        self.type_obj_combo_label.setMinimumWidth(60)
-        grid0.addWidget(self.type_obj_combo_label, 0, 0)
-        grid0.addWidget(self.type_obj_combo, 0, 1)
-
-        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Object to be cutout"))
+        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Source Object"))
         self.object_label.setToolTip('%s.' % _("Object to be cutout"))
 
-        # Object to be cutout
-        self.obj_combo = QtWidgets.QComboBox()
-        self.obj_combo.setModel(self.app.collection)
-        self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.obj_combo.setCurrentIndex(1)
-
-        grid0.addWidget(self.object_label, 1, 0, 1, 2)
-        grid0.addWidget(self.obj_combo, 2, 0, 1, 2)
+        grid0.addWidget(self.object_label, 0, 0, 1, 2)
 
         # Object kind
-        self.kindlabel = QtWidgets.QLabel('%s:' % _('Object kind'))
+        self.kindlabel = QtWidgets.QLabel('%s:' % _('Kind'))
         self.kindlabel.setToolTip(
             _("Choice of what kind the object we want to cutout is.<BR>"
               "- <B>Single</B>: contain a single PCB Gerber outline object.<BR>"
@@ -112,11 +84,46 @@ class CutOut(FlatCAMTool):
             {"label": _("Single"), "value": "single"},
             {"label": _("Panel"), "value": "panel"},
         ])
-        grid0.addWidget(self.kindlabel, 3, 0)
-        grid0.addWidget(self.obj_kind_combo, 3, 1)
+        grid0.addWidget(self.kindlabel, 1, 0)
+        grid0.addWidget(self.obj_kind_combo, 1, 1)
+
+        # Type of object to be cutout
+        self.type_obj_radio = RadioSet([
+            {"label": _("Gerber"), "value": "grb"},
+            {"label": _("Geometry"), "value": "geo"},
+        ])
+
+        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Type"))
+        self.type_obj_combo_label.setToolTip(
+            _("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.")
+        )
+
+        grid0.addWidget(self.type_obj_combo_label, 2, 0)
+        grid0.addWidget(self.type_obj_radio, 2, 1)
+
+        # Object to be cutout
+        self.obj_combo = FCComboBox()
+        self.obj_combo.setModel(self.app.collection)
+        self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.obj_combo.is_last = True
+
+        grid0.addWidget(self.obj_combo, 3, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 4, 0, 1, 2)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 5, 0, 1, 2)
+
+        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _("Tool Parameters"))
+        grid0.addWidget(self.param_label, 6, 0, 1, 2)
 
         # Tool Diameter
-        self.dia = FCDoubleSpinner()
+        self.dia = FCDoubleSpinner(callback=self.confirmation_message)
         self.dia.set_precision(self.decimals)
         self.dia.set_range(0.0000, 9999.9999)
 
@@ -125,8 +132,8 @@ class CutOut(FlatCAMTool):
            _("Diameter of the tool used to cutout\n"
              "the PCB shape out of the surrounding material.")
         )
-        grid0.addWidget(self.dia_label, 4, 0)
-        grid0.addWidget(self.dia, 4, 1)
+        grid0.addWidget(self.dia_label, 8, 0)
+        grid0.addWidget(self.dia, 8, 1)
 
         # Cut Z
         cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
@@ -136,7 +143,7 @@ class CutOut(FlatCAMTool):
                 "below the copper surface."
             )
         )
-        self.cutz_entry = FCDoubleSpinner()
+        self.cutz_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.cutz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
@@ -146,8 +153,8 @@ class CutOut(FlatCAMTool):
 
         self.cutz_entry.setSingleStep(0.1)
 
-        grid0.addWidget(cutzlabel, 5, 0)
-        grid0.addWidget(self.cutz_entry, 5, 1)
+        grid0.addWidget(cutzlabel, 9, 0)
+        grid0.addWidget(self.cutz_entry, 9, 1)
 
         # Multi-pass
         self.mpass_cb = FCCheckBox('%s:' % _("Multi-Depth"))
@@ -160,7 +167,7 @@ class CutOut(FlatCAMTool):
             )
         )
 
-        self.maxdepth_entry = FCDoubleSpinner()
+        self.maxdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.maxdepth_entry.set_precision(self.decimals)
         self.maxdepth_entry.setRange(0, 9999.9999)
         self.maxdepth_entry.setSingleStep(0.1)
@@ -172,11 +179,11 @@ class CutOut(FlatCAMTool):
         )
         self.ois_mpass_geo = OptionalInputSection(self.mpass_cb, [self.maxdepth_entry])
 
-        grid0.addWidget(self.mpass_cb, 6, 0)
-        grid0.addWidget(self.maxdepth_entry, 6, 1)
+        grid0.addWidget(self.mpass_cb, 10, 0)
+        grid0.addWidget(self.maxdepth_entry, 10, 1)
 
         # Margin
-        self.margin = FCDoubleSpinner()
+        self.margin = FCDoubleSpinner(callback=self.confirmation_message)
         self.margin.set_range(-9999.9999, 9999.9999)
         self.margin.setSingleStep(0.1)
         self.margin.set_precision(self.decimals)
@@ -187,11 +194,11 @@ class CutOut(FlatCAMTool):
              "will make the cutout of the PCB further from\n"
              "the actual PCB border")
         )
-        grid0.addWidget(self.margin_label, 7, 0)
-        grid0.addWidget(self.margin, 7, 1)
+        grid0.addWidget(self.margin_label, 11, 0)
+        grid0.addWidget(self.margin, 11, 1)
 
         # Gapsize
-        self.gapsize = FCDoubleSpinner()
+        self.gapsize = FCDoubleSpinner(callback=self.confirmation_message)
         self.gapsize.set_precision(self.decimals)
 
         self.gapsize_label = QtWidgets.QLabel('%s:' % _("Gap size"))
@@ -201,8 +208,8 @@ class CutOut(FlatCAMTool):
              "the surrounding material (the one \n"
              "from which the PCB is cutout).")
         )
-        grid0.addWidget(self.gapsize_label, 8, 0)
-        grid0.addWidget(self.gapsize, 8, 1)
+        grid0.addWidget(self.gapsize_label, 13, 0)
+        grid0.addWidget(self.gapsize, 13, 1)
 
         # How gaps wil be rendered:
         # lr    - left + right
@@ -219,23 +226,25 @@ class CutOut(FlatCAMTool):
             _("Create a convex shape surrounding the entire PCB.\n"
               "Used only if the source object type is Gerber.")
         )
-        grid0.addWidget(self.convex_box, 9, 0, 1, 2)
+        grid0.addWidget(self.convex_box, 15, 0, 1, 2)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 10, 0, 1, 2)
+        grid0.addWidget(separator_line, 16, 0, 1, 2)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 17, 0, 1, 2)
 
         # Title2
         title_param_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('A. Automatic Bridge Gaps'))
         title_param_label.setToolTip(
             _("This section handle creation of automatic bridge gaps.")
         )
-        self.layout.addWidget(title_param_label)
+        grid0.addWidget(title_param_label, 18, 0, 1, 2)
 
         # Form Layout
         form_layout_2 = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout_2)
+        grid0.addLayout(form_layout_2, 19, 0, 1, 2)
 
         # Gaps
         gaps_label = QtWidgets.QLabel('%s:' % _('Gaps'))
@@ -251,7 +260,7 @@ class CutOut(FlatCAMTool):
               "- 2tb  - 2*top + 2*bottom\n"
               "- 8     - 2*left + 2*right +2*top + 2*bottom")
         )
-        gaps_label.setMinimumWidth(60)
+        # gaps_label.setMinimumWidth(60)
 
         self.gaps = FCComboBox()
         gaps_items = ['None', 'LR', 'TB', '4', '2LR', '2TB', '8']
@@ -273,7 +282,7 @@ class CutOut(FlatCAMTool):
                             font-weight: bold;
                         }
                         """)
-        self.layout.addWidget(self.ff_cutout_object_btn)
+        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.setToolTip(
@@ -293,7 +302,7 @@ class CutOut(FlatCAMTool):
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
+        grid0.addWidget(separator_line, 21, 0, 1, 2)
 
         # Title5
         title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
@@ -302,23 +311,24 @@ class CutOut(FlatCAMTool):
               "This is done by mouse clicking on the perimeter of the\n"
               "Geometry object that is used as a cutout object. ")
         )
-        self.layout.addWidget(title_manual_label)
+        grid0.addWidget(title_manual_label, 22, 0, 1, 2)
 
         # Form Layout
         form_layout_3 = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout_3)
+        grid0.addLayout(form_layout_3, 23, 0, 1, 2)
 
         # Manual Geo Object
-        self.man_object_combo = QtWidgets.QComboBox()
+        self.man_object_combo = FCComboBox()
         self.man_object_combo.setModel(self.app.collection)
         self.man_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
-        self.man_object_combo.setCurrentIndex(1)
+        self.man_object_combo.is_last = True
+        self.man_object_combo.obj_type = "Geometry"
 
         self.man_object_label = QtWidgets.QLabel('%s:' % _("Geometry Object"))
         self.man_object_label.setToolTip(
             _("Geometry object used to create the manual cutout.")
         )
-        self.man_object_label.setMinimumWidth(60)
+        # self.man_object_label.setMinimumWidth(60)
 
         form_layout_3.addRow(self.man_object_label)
         form_layout_3.addRow(self.man_object_combo)
@@ -338,7 +348,7 @@ class CutOut(FlatCAMTool):
                             font-weight: bold;
                         }
                         """)
-        self.layout.addWidget(self.man_geo_creation_btn)
+        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.setToolTip(
@@ -354,7 +364,7 @@ class CutOut(FlatCAMTool):
                             font-weight: bold;
                         }
                         """)
-        self.layout.addWidget(self.man_gaps_creation_btn)
+        grid0.addWidget(self.man_gaps_creation_btn, 27, 0, 1, 2)
 
         self.layout.addStretch()
 
@@ -398,15 +408,16 @@ class CutOut(FlatCAMTool):
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
 
-        self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
+        self.type_obj_radio.activated_custom.connect(self.on_type_obj_changed)
         self.man_geo_creation_btn.clicked.connect(self.on_manual_geo)
         self.man_gaps_creation_btn.clicked.connect(self.on_manual_gap_click)
         self.reset_button.clicked.connect(self.set_tool_ui)
 
-    def on_type_obj_index_changed(self, index):
-        obj_type = self.type_obj_combo.currentIndex()
+    def on_type_obj_changed(self, val):
+        obj_type = {'grb': 0, 'geo': 2}[val]
         self.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.obj_combo.setCurrentIndex(0)
+        self.obj_combo.obj_type = {"grb": "Gerber", "geo": "Geometry"}[val]
 
     def run(self, toggle=True):
         self.app.report_usage("ToolCutOut()")
@@ -436,7 +447,7 @@ class CutOut(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Cutout Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+X', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+X', **kwargs)
 
     def set_tool_ui(self):
         self.reset_fields()
@@ -451,6 +462,7 @@ class CutOut(FlatCAMTool):
         self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
         self.gaps.set_value(self.app.defaults["tools_gaps_ff"])
         self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
+        self.type_obj_radio.set_value('grb')
 
     def on_freeform_cutout(self):
 
@@ -514,9 +526,16 @@ class CutOut(FlatCAMTool):
             solid_geo = []
 
             if isinstance(cutout_obj, FlatCAMGerber):
-                if convex_box:
-                    object_geo = cutout_obj.solid_geometry.convex_hull
-                else:
+                if isinstance(cutout_obj.solid_geometry, list):
+                    cutout_obj.solid_geometry = MultiPolygon(cutout_obj.solid_geometry)
+
+                try:
+                    if convex_box:
+                        object_geo = cutout_obj.solid_geometry.convex_hull
+                    else:
+                        object_geo = cutout_obj.solid_geometry
+                except Exception as err:
+                    log.debug("CutOut.on_freeform_cutout().geo_init() --> %s" % str(err))
                     object_geo = cutout_obj.solid_geometry
             else:
                 object_geo = cutout_obj.solid_geometry
@@ -588,12 +607,14 @@ class CutOut(FlatCAMTool):
                     if isinstance(object_geo, MultiPolygon):
                         x0, y0, x1, y1 = object_geo.bounds
                         object_geo = box(x0, y0, x1, y1)
+                    if margin >= 0:
+                        geo_buf = object_geo.buffer(margin + abs(dia / 2))
+                    else:
+                        geo_buf = object_geo.buffer(margin - abs(dia / 2))
 
-                    geo_buf = object_geo.buffer(margin + abs(dia / 2))
                     geo = geo_buf.exterior
                 else:
                     geo = object_geo
-
                 solid_geo = cutout_handler(geom=geo)
             else:
                 try:
@@ -603,7 +624,11 @@ class CutOut(FlatCAMTool):
 
                 for geom_struct in object_geo:
                     if isinstance(cutout_obj, FlatCAMGerber):
-                        geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior
+                        if margin >= 0:
+                            geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior
+                        else:
+                            geom_struct_buff = geom_struct.buffer(-margin + abs(dia / 2))
+                            geom_struct = geom_struct_buff.interiors
 
                     solid_geo += cutout_handler(geom=geom_struct)
 
@@ -623,7 +648,7 @@ class CutOut(FlatCAMTool):
 
         cutout_obj.plot()
         self.app.inform.emit('[success] %s' % _("Any form CutOut operation finished."))
-        self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
+        # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
         self.app.should_we_save = True
 
     def on_rectangular_cutout(self):
@@ -751,24 +776,43 @@ class CutOut(FlatCAMTool):
                 # if Gerber create a buffer at a distance
                 # if Geometry then cut through the geometry
                 if isinstance(cutout_obj, FlatCAMGerber):
-                    geo = geo.buffer(margin + abs(dia / 2))
+                    if margin >= 0:
+                        geo = geo.buffer(margin + abs(dia / 2))
+                    else:
+                        geo = geo.buffer(margin - abs(dia / 2))
 
                 solid_geo = cutout_rect_handler(geom=geo)
             else:
-                try:
-                    __ = iter(object_geo)
-                except TypeError:
-                    object_geo = [object_geo]
+                if cutout_obj.kind == 'geometry':
+                    try:
+                        __ = iter(object_geo)
+                    except TypeError:
+                        object_geo = [object_geo]
+
+                    for geom_struct in object_geo:
+                        geom_struct = unary_union(geom_struct)
+                        xmin, ymin, xmax, ymax = geom_struct.bounds
+                        geom_struct = box(xmin, ymin, xmax, ymax)
+
+                        solid_geo += cutout_rect_handler(geom=geom_struct)
+                elif cutout_obj.kind == 'gerber' and margin >= 0:
+                    try:
+                        __ = iter(object_geo)
+                    except TypeError:
+                        object_geo = [object_geo]
+
+                    for geom_struct in object_geo:
+                        geom_struct = unary_union(geom_struct)
+                        xmin, ymin, xmax, ymax = geom_struct.bounds
+                        geom_struct = box(xmin, ymin, xmax, ymax)
 
-                for geom_struct in object_geo:
-                    geom_struct = unary_union(geom_struct)
-                    xmin, ymin, xmax, ymax = geom_struct.bounds
-                    geom_struct = box(xmin, ymin, xmax, ymax)
-
-                    if isinstance(cutout_obj, FlatCAMGerber):
                         geom_struct = geom_struct.buffer(margin + abs(dia / 2))
 
-                    solid_geo += cutout_rect_handler(geom=geom_struct)
+                        solid_geo += cutout_rect_handler(geom=geom_struct)
+                elif cutout_obj.kind == 'gerber' and margin < 0:
+                    self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                         _("Rectangular cutout with negative margin is not possible."))
+                    return "fail"
 
             geo_obj.solid_geometry = deepcopy(solid_geo)
             geo_obj.options['cnctooldia'] = str(dia)
@@ -777,12 +821,12 @@ class CutOut(FlatCAMTool):
             geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
 
         outname = cutout_obj.options["name"] + "_cutout"
-        self.app.new_object('geometry', outname, geo_init)
+        ret = self.app.new_object('geometry', outname, geo_init)
 
-        # cutout_obj.plot()
-        self.app.inform.emit('[success] %s' %
-                             _("Any form CutOut operation finished."))
-        self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
+        if ret != 'fail':
+            # cutout_obj.plot()
+            self.app.inform.emit('[success] %s' % _("Any form CutOut operation finished."))
+        # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
         self.app.should_we_save = True
 
     def on_manual_gap_click(self):
@@ -923,6 +967,9 @@ class CutOut(FlatCAMTool):
         self.app.new_object('geometry', outname, geo_init)
 
     def cutting_geo(self, pos):
+        self.cutting_dia = float(self.dia.get_value())
+        self.cutting_gapsize = float(self.gapsize.get_value())
+
         offset = self.cutting_dia / 2 + self.cutting_gapsize / 2
 
         # cutting area definition
@@ -1018,7 +1065,7 @@ class CutOut(FlatCAMTool):
         except TypeError:
             return
 
-        if self.app.grid_status() == True:
+        if self.app.grid_status():
             snap_x, snap_y = self.app.geo_editor.snap(x, y)
         else:
             snap_x, snap_y = x, y
@@ -1048,7 +1095,7 @@ class CutOut(FlatCAMTool):
                 else:
                     radian = math.atan(dx / dy)
                     angle = radian * 180 / math.pi
-            except Exception as e:
+            except Exception:
                 angle = 0
             return angle
 

+ 366 - 214
flatcamTools/ToolDblSided.py

@@ -2,7 +2,7 @@
 from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry
+from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry, FCButton, FCComboBox
 from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry
 
 from numpy import Inf
@@ -41,22 +41,28 @@ class DblSidedTool(FlatCAMTool):
                         """)
         self.layout.addWidget(title_label)
 
-        self.empty_lb = QtWidgets.QLabel("")
-        self.layout.addWidget(self.empty_lb)
+        self.layout.addWidget(QtWidgets.QLabel(""))
 
         # ## Grid Layout
         grid_lay = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay)
         grid_lay.setColumnStretch(0, 1)
         grid_lay.setColumnStretch(1, 0)
+        self.layout.addLayout(grid_lay)
+
+        # Objects to be mirrored
+        self.m_objects_label = QtWidgets.QLabel("<b>%s:</b>" % _("Mirror Operation"))
+        self.m_objects_label.setToolTip('%s.' % _("Objects to be mirrored"))
+
+        grid_lay.addWidget(self.m_objects_label, 0, 0, 1, 2)
 
         # ## Gerber Object to mirror
-        self.gerber_object_combo = QtWidgets.QComboBox()
+        self.gerber_object_combo = FCComboBox()
         self.gerber_object_combo.setModel(self.app.collection)
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.gerber_object_combo.setCurrentIndex(1)
+        self.gerber_object_combo.is_last = True
+        self.gerber_object_combo.obj_type = "Gerber"
 
-        self.botlay_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.botlay_label = QtWidgets.QLabel("%s:" % _("GERBER"))
         self.botlay_label.setToolTip('%s.' % _("Gerber to be mirrored"))
 
         self.mirror_gerber_button = QtWidgets.QPushButton(_("Mirror"))
@@ -73,18 +79,18 @@ class DblSidedTool(FlatCAMTool):
                         """)
         self.mirror_gerber_button.setMinimumWidth(60)
 
-        # grid_lay.addRow("Bottom Layer:", self.object_combo)
-        grid_lay.addWidget(self.botlay_label, 0, 0)
-        grid_lay.addWidget(self.gerber_object_combo, 1, 0)
-        grid_lay.addWidget(self.mirror_gerber_button, 1, 1)
+        grid_lay.addWidget(self.botlay_label, 1, 0)
+        grid_lay.addWidget(self.gerber_object_combo, 2, 0)
+        grid_lay.addWidget(self.mirror_gerber_button, 2, 1)
 
         # ## Excellon Object to mirror
-        self.exc_object_combo = QtWidgets.QComboBox()
+        self.exc_object_combo = FCComboBox()
         self.exc_object_combo.setModel(self.app.collection)
         self.exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.exc_object_combo.setCurrentIndex(1)
+        self.exc_object_combo.is_last = True
+        self.exc_object_combo.obj_type = "Excellon"
 
-        self.excobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("EXCELLON"))
+        self.excobj_label = QtWidgets.QLabel("%s:" % _("EXCELLON"))
         self.excobj_label.setToolTip(_("Excellon Object to be mirrored."))
 
         self.mirror_exc_button = QtWidgets.QPushButton(_("Mirror"))
@@ -101,18 +107,18 @@ class DblSidedTool(FlatCAMTool):
                         """)
         self.mirror_exc_button.setMinimumWidth(60)
 
-        # grid_lay.addRow("Bottom Layer:", self.object_combo)
-        grid_lay.addWidget(self.excobj_label, 2, 0)
-        grid_lay.addWidget(self.exc_object_combo, 3, 0)
-        grid_lay.addWidget(self.mirror_exc_button, 3, 1)
+        grid_lay.addWidget(self.excobj_label, 3, 0)
+        grid_lay.addWidget(self.exc_object_combo, 4, 0)
+        grid_lay.addWidget(self.mirror_exc_button, 4, 1)
 
         # ## Geometry Object to mirror
-        self.geo_object_combo = QtWidgets.QComboBox()
+        self.geo_object_combo = FCComboBox()
         self.geo_object_combo.setModel(self.app.collection)
         self.geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
-        self.geo_object_combo.setCurrentIndex(1)
+        self.geo_object_combo.is_last = True
+        self.geo_object_combo.obj_type = "Geometry"
 
-        self.geoobj_label = QtWidgets.QLabel("<b>%s</b>:" % _("GEOMETRY"))
+        self.geoobj_label = QtWidgets.QLabel("%s:" % _("GEOMETRY"))
         self.geoobj_label.setToolTip(
             _("Geometry Obj to be mirrored.")
         )
@@ -132,257 +138,194 @@ class DblSidedTool(FlatCAMTool):
         self.mirror_geo_button.setMinimumWidth(60)
 
         # grid_lay.addRow("Bottom Layer:", self.object_combo)
-        grid_lay.addWidget(self.geoobj_label, 4, 0)
-        grid_lay.addWidget(self.geo_object_combo, 5, 0)
-        grid_lay.addWidget(self.mirror_geo_button, 5, 1)
+        grid_lay.addWidget(self.geoobj_label, 5, 0)
+        grid_lay.addWidget(self.geo_object_combo, 6, 0)
+        grid_lay.addWidget(self.mirror_geo_button, 6, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 7, 0, 1, 2)
+
+        self.layout.addWidget(QtWidgets.QLabel(""))
 
         # ## Grid Layout
         grid_lay1 = QtWidgets.QGridLayout()
+        grid_lay1.setColumnStretch(0, 0)
+        grid_lay1.setColumnStretch(1, 1)
         self.layout.addLayout(grid_lay1)
 
+        # Objects to be mirrored
+        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Mirror Parameters"))
+        self.param_label.setToolTip('%s.' % _("Parameters for the mirror operation"))
+
+        grid_lay1.addWidget(self.param_label, 0, 0, 1, 2)
+
         # ## Axis
+        self.mirax_label = QtWidgets.QLabel('%s:' % _("Mirror Axis"))
+        self.mirax_label.setToolTip(_("Mirror vertically (X) or horizontally (Y)."))
         self.mirror_axis = RadioSet([{'label': 'X', 'value': 'X'},
                                      {'label': 'Y', 'value': 'Y'}])
-        self.mirax_label = QtWidgets.QLabel(_("Mirror Axis:"))
-        self.mirax_label.setToolTip(_("Mirror vertically (X) or horizontally (Y)."))
 
-        # grid_lay.addRow("Mirror Axis:", self.mirror_axis)
-        self.empty_lb1 = QtWidgets.QLabel("")
-        grid_lay1.addWidget(self.empty_lb1, 6, 0)
-        grid_lay1.addWidget(self.mirax_label, 7, 0)
-        grid_lay1.addWidget(self.mirror_axis, 7, 1)
+        grid_lay1.addWidget(self.mirax_label, 2, 0)
+        grid_lay1.addWidget(self.mirror_axis, 2, 1, 1, 2)
 
         # ## Axis Location
-        self.axis_location = RadioSet([{'label': _('Point'), 'value': 'point'},
-                                       {'label': _('Box'), 'value': 'box'}])
-        self.axloc_label = QtWidgets.QLabel('%s:' % _("Axis Ref"))
+        self.axloc_label = QtWidgets.QLabel('%s:' % _("Reference"))
         self.axloc_label.setToolTip(
-            _("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.")
+            _("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")
         )
-        # grid_lay.addRow("Axis Location:", self.axis_location)
-        grid_lay1.addWidget(self.axloc_label, 8, 0)
-        grid_lay1.addWidget(self.axis_location, 8, 1)
+        self.axis_location = RadioSet([{'label': _('Point'), 'value': 'point'},
+                                       {'label': _('Box'), 'value': 'box'}])
 
-        self.empty_lb2 = QtWidgets.QLabel("")
-        grid_lay1.addWidget(self.empty_lb2, 9, 0)
-
-        # ## Grid Layout
-        grid_lay2 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay2)
-        grid_lay2.setColumnStretch(0, 1)
-        grid_lay2.setColumnStretch(1, 0)
+        grid_lay1.addWidget(self.axloc_label, 4, 0)
+        grid_lay1.addWidget(self.axis_location, 4, 1, 1, 2)
 
         # ## Point/Box
-        self.point_box_container = QtWidgets.QVBoxLayout()
-        self.pb_label = QtWidgets.QLabel("<b>%s:</b>" % _('Point/Box Reference'))
-        self.pb_label.setToolTip(
-            _("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.")
-        )
+        self.point_entry = EvalEntry()
+        self.point_entry.setPlaceholderText(_("Point coordinates"))
 
+        # 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 "
               "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 coords manually.")
+              "and left mouse button click on canvas or you can enter the coordinates manually.")
         )
         self.add_point_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
         self.add_point_button.setMinimumWidth(60)
 
-        grid_lay2.addWidget(self.pb_label, 10, 0)
-        grid_lay2.addLayout(self.point_box_container, 11, 0)
-        grid_lay2.addWidget(self.add_point_button, 11, 1)
-
-        self.point_entry = EvalEntry()
-        self.point_box_container.addWidget(self.point_entry)
-
-        self.box_combo = QtWidgets.QComboBox()
-        self.box_combo.setModel(self.app.collection)
-        self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.box_combo.setCurrentIndex(1)
-
-        self.box_combo_type = QtWidgets.QComboBox()
-        self.box_combo_type.addItem(_("Reference Gerber"))
-        self.box_combo_type.addItem(_("Reference Excellon"))
-        self.box_combo_type.addItem(_("Reference Geometry"))
-
-        self.point_box_container.addWidget(self.box_combo_type)
-        self.point_box_container.addWidget(self.box_combo)
-        self.box_combo.hide()
-        self.box_combo_type.hide()
+        grid_lay1.addWidget(self.point_entry, 7, 0, 1, 2)
+        grid_lay1.addWidget(self.add_point_button, 7, 2)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay2.addWidget(separator_line, 12, 0, 1, 2)
-
-        # ## Alignment holes
-        self.ah_label = QtWidgets.QLabel("<b>%s:</b>" % _('Alignment Drill Coordinates'))
-        self.ah_label.setToolTip(
-           _("Alignment holes (x1, y1), (x2, y2), ... "
-             "on one side of the mirror axis. For each set of (x, y) coordinates\n"
-             "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 'Mirror Axis'.")
-        )
-        self.layout.addWidget(self.ah_label)
-
-        grid_lay3 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay3)
-
-        self.alignment_holes = EvalEntry()
+        # ## Grid Layout
+        grid_lay2 = QtWidgets.QGridLayout()
+        grid_lay2.setColumnStretch(0, 0)
+        grid_lay2.setColumnStretch(1, 1)
+        self.layout.addLayout(grid_lay2)
 
-        self.add_drill_point_button = QtWidgets.QPushButton(_("Add"))
-        self.add_drill_point_button.setToolTip(
-            _("Add alignment drill holes coords in the format: (x1, y1), (x2, y2), ... \n"
-              "on one side of the mirror 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), ...")
+        self.box_type_label = QtWidgets.QLabel('%s:' % _("Reference Object"))
+        self.box_type_label.setToolTip(
+            _("It can be of type: Gerber or Excellon or Geometry.\n"
+              "The coordinates of the center of the bounding box are used\n"
+              "as reference for mirror operation.")
         )
-        self.add_drill_point_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.add_drill_point_button.setMinimumWidth(60)
 
-        grid_lay3.addWidget(self.alignment_holes, 0, 0)
-        grid_lay3.addWidget(self.add_drill_point_button, 0, 1)
+        # Type of object used as BOX reference
+        self.box_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
+                                        {'label': _('Excellon'), 'value': 'exc'},
+                                        {'label': _('Geometry'), 'value': 'geo'}])
 
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-        grid0.setColumnStretch(0, 0)
-        grid0.setColumnStretch(1, 1)
+        self.box_type_label.hide()
+        self.box_type_radio.hide()
 
-        # ## Drill diameter for alignment holes
-        self.dt_label = QtWidgets.QLabel("<b>%s:</b>" % _('Alignment Drill Diameter'))
-        self.dt_label.setToolTip(
-            _("Diameter of the drill for the "
-              "alignment holes.")
-        )
-        grid0.addWidget(self.dt_label, 0, 0, 1, 2)
-
-        # Drill diameter value
-        self.drill_dia = FCDoubleSpinner()
-        self.drill_dia.set_precision(self.decimals)
-        self.drill_dia.set_range(0.0000, 9999.9999)
+        grid_lay2.addWidget(self.box_type_label, 0, 0, 1, 2)
+        grid_lay2.addWidget(self.box_type_radio, 1, 0, 1, 2)
 
-        self.drill_dia.setToolTip(
-            _("Diameter of the drill for the "
-              "alignment holes.")
-        )
+        # Object used as BOX reference
+        self.box_combo = FCComboBox()
+        self.box_combo.setModel(self.app.collection)
+        self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.box_combo.is_last = True
 
-        grid0.addWidget(self.drill_dia, 1, 0, 1, 2)
+        self.box_combo.hide()
 
-        # ## Buttons
-        self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object"))
-        self.create_alignment_hole_button.setToolTip(
-            _("Creates an Excellon Object containing the\n"
-              "specified alignment holes and their mirror\n"
-              "images.")
-        )
-        self.create_alignment_hole_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(self.create_alignment_hole_button)
+        grid_lay2.addWidget(self.box_combo, 3, 0, 1, 2)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
+        grid_lay2.addWidget(separator_line, 4, 0, 1, 2)
 
-        self.layout.addWidget(QtWidgets.QLabel(''))
+        grid_lay2.addWidget(QtWidgets.QLabel(""), 5, 0, 1, 2)
 
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-        grid1.setColumnStretch(0, 0)
-        grid1.setColumnStretch(1, 1)
+        # ## Title Bounds Values
+        self.bv_label = QtWidgets.QLabel("<b>%s:</b>" % _('Bounds Values'))
+        self.bv_label.setToolTip(
+            _("Select on canvas the object(s)\n"
+              "for which to calculate bounds values.")
+        )
+        grid_lay2.addWidget(self.bv_label, 6, 0, 1, 2)
 
         # Xmin value
-        self.xmin_entry = FCDoubleSpinner()
+        self.xmin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.xmin_entry.set_precision(self.decimals)
         self.xmin_entry.set_range(-9999.9999, 9999.9999)
 
-        self.xmin_label = QtWidgets.QLabel('%s:' % _("X min"))
-        self.xmin_label.setToolTip(
+        self.xmin_btn = FCButton('%s:' % _("X min"))
+        self.xmin_btn.setToolTip(
             _("Minimum location.")
         )
         self.xmin_entry.setReadOnly(True)
 
-        grid1.addWidget(self.xmin_label, 1, 0)
-        grid1.addWidget(self.xmin_entry, 1, 1)
+        grid_lay2.addWidget(self.xmin_btn, 7, 0)
+        grid_lay2.addWidget(self.xmin_entry, 7, 1)
 
         # Ymin value
-        self.ymin_entry = FCDoubleSpinner()
+        self.ymin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.ymin_entry.set_precision(self.decimals)
         self.ymin_entry.set_range(-9999.9999, 9999.9999)
 
-        self.ymin_label = QtWidgets.QLabel('%s:' % _("Y min"))
-        self.ymin_label.setToolTip(
+        self.ymin_btn = FCButton('%s:' % _("Y min"))
+        self.ymin_btn.setToolTip(
             _("Minimum location.")
         )
         self.ymin_entry.setReadOnly(True)
 
-        grid1.addWidget(self.ymin_label, 2, 0)
-        grid1.addWidget(self.ymin_entry, 2, 1)
+        grid_lay2.addWidget(self.ymin_btn, 8, 0)
+        grid_lay2.addWidget(self.ymin_entry, 8, 1)
 
         # Xmax value
-        self.xmax_entry = FCDoubleSpinner()
+        self.xmax_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.xmax_entry.set_precision(self.decimals)
         self.xmax_entry.set_range(-9999.9999, 9999.9999)
 
-        self.xmax_label = QtWidgets.QLabel('%s:' % _("X max"))
-        self.xmax_label.setToolTip(
+        self.xmax_btn = FCButton('%s:' % _("X max"))
+        self.xmax_btn.setToolTip(
             _("Maximum location.")
         )
         self.xmax_entry.setReadOnly(True)
 
-        grid1.addWidget(self.xmax_label, 3, 0)
-        grid1.addWidget(self.xmax_entry, 3, 1)
+        grid_lay2.addWidget(self.xmax_btn, 9, 0)
+        grid_lay2.addWidget(self.xmax_entry, 9, 1)
 
         # Ymax value
-        self.ymax_entry = FCDoubleSpinner()
+        self.ymax_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.ymax_entry.set_precision(self.decimals)
         self.ymax_entry.set_range(-9999.9999, 9999.9999)
 
-        self.ymax_label = QtWidgets.QLabel('%s:' % _("Y max"))
-        self.ymax_label.setToolTip(
+        self.ymax_btn = FCButton('%s:' % _("Y max"))
+        self.ymax_btn.setToolTip(
             _("Maximum location.")
         )
         self.ymax_entry.setReadOnly(True)
 
-        grid1.addWidget(self.ymax_label, 4, 0)
-        grid1.addWidget(self.ymax_entry, 4, 1)
+        grid_lay2.addWidget(self.ymax_btn, 10, 0)
+        grid_lay2.addWidget(self.ymax_entry, 10, 1)
 
         # Center point value
         self.center_entry = FCEntry()
+        self.center_entry.setPlaceholderText(_("Center point coordinates"))
 
-        self.center_label = QtWidgets.QLabel('%s:' % _("Centroid"))
-        self.center_label.setToolTip(
+        self.center_btn = FCButton('%s:' % _("Centroid"))
+        self.center_btn.setToolTip(
             _("The center point location for the rectangular\n"
               "bounding shape. Centroid. Format is (x, y).")
         )
         self.center_entry.setReadOnly(True)
 
-        grid1.addWidget(self.center_label, 5, 0)
-        grid1.addWidget(self.center_entry, 5, 1)
+        grid_lay2.addWidget(self.center_btn, 12, 0)
+        grid_lay2.addWidget(self.center_entry, 12, 1)
 
         # Calculate Bounding box
         self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounds Values"))
@@ -397,7 +340,131 @@ class DblSidedTool(FlatCAMTool):
                                     font-weight: bold;
                                 }
                                 """)
-        self.layout.addWidget(self.calculate_bb_button)
+        grid_lay2.addWidget(self.calculate_bb_button, 13, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay2.addWidget(separator_line, 14, 0, 1, 2)
+
+        grid_lay2.addWidget(QtWidgets.QLabel(""), 15, 0, 1, 2)
+
+        # ## Alignment holes
+        self.alignment_label = QtWidgets.QLabel("<b>%s:</b>" % _('PCB Alignment'))
+        self.alignment_label.setToolTip(
+            _("Creates an Excellon Object containing the\n"
+              "specified alignment holes and their mirror\n"
+              "images.")
+        )
+        grid_lay2.addWidget(self.alignment_label, 25, 0, 1, 2)
+
+        # ## Drill diameter for alignment holes
+        self.dt_label = QtWidgets.QLabel("%s:" % _('Drill Diameter'))
+        self.dt_label.setToolTip(
+            _("Diameter of the drill for the alignment holes.")
+        )
+
+        self.drill_dia = FCDoubleSpinner(callback=self.confirmation_message)
+        self.drill_dia.setToolTip(
+            _("Diameter of the drill for the alignment holes.")
+        )
+        self.drill_dia.set_precision(self.decimals)
+        self.drill_dia.set_range(0.0000, 9999.9999)
+
+        grid_lay2.addWidget(self.dt_label, 26, 0)
+        grid_lay2.addWidget(self.drill_dia, 26, 1)
+
+        # ## Alignment Axis
+        self.align_ax_label = QtWidgets.QLabel('%s:' % _("Align Axis"))
+        self.align_ax_label.setToolTip(
+            _("Mirror vertically (X) or horizontally (Y).")
+        )
+        self.align_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
+                                          {'label': 'Y', 'value': 'Y'}])
+
+        grid_lay2.addWidget(self.align_ax_label, 27, 0)
+        grid_lay2.addWidget(self.align_axis_radio, 27, 1)
+
+        # ## Alignment Reference Point
+        self.align_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        self.align_ref_label.setToolTip(
+            _("The reference point used to create the second alignment drill\n"
+              "from the first alignment drill, by doing mirror.\n"
+              "It can be modified in the Mirror Parameters -> Reference section")
+        )
+
+        self.align_ref_label_val = EvalEntry()
+        self.align_ref_label_val.setToolTip(
+            _("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")
+        )
+        self.align_ref_label_val.setDisabled(True)
+
+        grid_lay2.addWidget(self.align_ref_label, 28, 0)
+        grid_lay2.addWidget(self.align_ref_label_val, 28, 1)
+
+        grid_lay4 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay4)
+
+        # ## Alignment holes
+        self.ah_label = QtWidgets.QLabel("%s:" % _('Alignment Drill Coordinates'))
+        self.ah_label.setToolTip(
+           _("Alignment holes (x1, y1), (x2, y2), ... "
+             "on one side of the mirror axis. For each set of (x, y) coordinates\n"
+             "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'.")
+        )
+
+        self.alignment_holes = EvalEntry()
+        self.alignment_holes.setPlaceholderText(_("Drill coordinates"))
+
+        grid_lay4.addWidget(self.ah_label, 0, 0, 1, 2)
+        grid_lay4.addWidget(self.alignment_holes, 1, 0, 1, 2)
+
+        self.add_drill_point_button = FCButton(_("Add"))
+        self.add_drill_point_button.setToolTip(
+            _("Add alignment drill holes coordinates in the format: (x1, y1), (x2, y2), ... \n"
+              "on one side of the alignment axis.\n\n"
+              "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), ...")
+        )
+        # self.add_drill_point_button.setStyleSheet("""
+        #                 QPushButton
+        #                 {
+        #                     font-weight: bold;
+        #                 }
+        #                 """)
+
+        self.delete_drill_point_button = FCButton(_("Delete Last"))
+        self.delete_drill_point_button.setToolTip(
+            _("Delete the last coordinates tuple in the list.")
+        )
+        drill_hlay = QtWidgets.QHBoxLayout()
+
+        drill_hlay.addWidget(self.add_drill_point_button)
+        drill_hlay.addWidget(self.delete_drill_point_button)
+
+        grid_lay4.addLayout(drill_hlay, 2, 0, 1, 2)
+
+        # ## Buttons
+        self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object"))
+        self.create_alignment_hole_button.setToolTip(
+            _("Creates an Excellon Object containing the\n"
+              "specified alignment holes and their mirror\n"
+              "images.")
+        )
+        self.create_alignment_hole_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.create_alignment_hole_button)
 
         self.layout.addStretch()
 
@@ -418,12 +485,25 @@ class DblSidedTool(FlatCAMTool):
         self.mirror_gerber_button.clicked.connect(self.on_mirror_gerber)
         self.mirror_exc_button.clicked.connect(self.on_mirror_exc)
         self.mirror_geo_button.clicked.connect(self.on_mirror_geo)
+
         self.add_point_button.clicked.connect(self.on_point_add)
         self.add_drill_point_button.clicked.connect(self.on_drill_add)
-        self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
+        self.delete_drill_point_button.clicked.connect(self.on_drill_delete_last)
+        self.box_type_radio.activated_custom.connect(self.on_combo_box_type)
 
         self.axis_location.group_toggle_fn = self.on_toggle_pointbox
 
+        self.point_entry.textChanged.connect(lambda val: self.align_ref_label_val.set_value(val))
+
+        self.xmin_btn.clicked.connect(self.on_xmin_clicked)
+        self.ymin_btn.clicked.connect(self.on_ymin_clicked)
+        self.xmax_btn.clicked.connect(self.on_xmax_clicked)
+        self.ymax_btn.clicked.connect(self.on_ymax_clicked)
+
+        self.center_btn.clicked.connect(
+            lambda: self.point_entry.set_value(self.center_entry.get_value())
+        )
+
         self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
         self.calculate_bb_button.clicked.connect(self.on_bbox_coordinates)
 
@@ -432,7 +512,7 @@ class DblSidedTool(FlatCAMTool):
         self.drill_values = ""
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+D', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+D', **kwargs)
 
     def run(self, toggle=True):
         self.app.report_usage("Tool2Sided()")
@@ -470,6 +550,7 @@ class DblSidedTool(FlatCAMTool):
         self.mirror_axis.set_value(self.app.defaults["tools_2sided_mirror_axis"])
         self.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"])
         self.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"])
+        self.align_axis_radio.set_value(self.app.defaults["tools_2sided_allign_axis"])
 
         self.xmin_entry.set_value(0.0)
         self.ymin_entry.set_value(0.0)
@@ -477,13 +558,21 @@ class DblSidedTool(FlatCAMTool):
         self.ymax_entry.set_value(0.0)
         self.center_entry.set_value('')
 
-    def on_combo_box_type(self):
-        obj_type = self.box_combo_type.currentIndex()
+        self.align_ref_label_val.set_value('%.*f' % (self.decimals, 0.0))
+
+        # run once to make sure that the obj_type attribute is updated in the FCComboBox
+        self.box_type_radio.set_value('grb')
+        self.on_combo_box_type('grb')
+
+    def on_combo_box_type(self, val):
+        obj_type = {'grb': 0, 'exc': 1, 'geo': 2}[val]
         self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.box_combo.setCurrentIndex(0)
+        self.box_combo.obj_type = {
+            "grb": "Gerber", "exc": "Excellon", "geo": "Geometry"}[val]
 
     def on_create_alignment_holes(self):
-        axis = self.mirror_axis.get_value()
+        axis = self.align_axis_radio.get_value()
         mode = self.axis_location.get_value()
 
         if mode == "point":
@@ -519,12 +608,15 @@ class DblSidedTool(FlatCAMTool):
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
 
         dia = float(self.drill_dia.get_value())
-        if dia is '':
+        if dia == '':
             self.app.inform.emit('[WARNING_NOTCL] %s' %
                                  _("No value or wrong format in Drill Dia entry. Add it and retry."))
             return
 
-        tools = {"1": {"C": dia}}
+        tools = {}
+        tools["1"] = {}
+        tools["1"]["C"] = dia
+        tools["1"]['solid_geometry'] = []
 
         # holes = self.alignment_holes.get_value()
         holes = eval('[{}]'.format(self.alignment_holes.text()))
@@ -538,17 +630,19 @@ class DblSidedTool(FlatCAMTool):
         for hole in holes:
             point = Point(hole)
             point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
+
             drills.append({"point": point, "tool": "1"})
             drills.append({"point": point_mirror, "tool": "1"})
-            if 'solid_geometry' not in tools:
-                tools["1"]['solid_geometry'] = []
-            else:
-                tools["1"]['solid_geometry'].append(point_mirror)
+
+            tools["1"]['solid_geometry'].append(point)
+            tools["1"]['solid_geometry'].append(point_mirror)
 
         def obj_init(obj_inst, app_inst):
             obj_inst.tools = tools
             obj_inst.drills = drills
             obj_inst.create_geometry()
+            obj_inst.source_file = app_inst.export_excellon(obj_name=obj_inst.options['name'], local_use=obj_inst,
+                                                            filename=None, use_thread=False)
 
         self.app.new_object("excellon", "Alignment Drills", obj_init)
         self.drill_values = ''
@@ -560,7 +654,7 @@ class DblSidedTool(FlatCAMTool):
         model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
         try:
             fcobj = model_index.internalPointer().obj
-        except Exception as e:
+        except Exception:
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             return
 
@@ -575,16 +669,16 @@ class DblSidedTool(FlatCAMTool):
             try:
                 px, py = self.point_entry.get_value()
             except TypeError:
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("'Point' coordinates missing. "
-                                                              "Using Origin (0, 0) as mirroring reference."))
-                px, py = (0, 0)
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("There are no Point coordinates in the Point field. "
+                                                              "Add coords and try again ..."))
+                return
 
         else:
             selection_index_box = self.box_combo.currentIndex()
             model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex())
             try:
                 bb_obj = model_index_box.internalPointer().obj
-            except Exception as e:
+            except Exception:
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
                 return
 
@@ -603,7 +697,7 @@ class DblSidedTool(FlatCAMTool):
         model_index = self.app.collection.index(selection_index, 0, self.exc_object_combo.rootModelIndex())
         try:
             fcobj = model_index.internalPointer().obj
-        except Exception as e:
+        except Exception:
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ..."))
             return
 
@@ -647,7 +741,7 @@ class DblSidedTool(FlatCAMTool):
         model_index = self.app.collection.index(selection_index, 0, self.geo_object_combo.rootModelIndex())
         try:
             fcobj = model_index.internalPointer().obj
-        except Exception as e:
+        except Exception:
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object loaded ..."))
             return
 
@@ -665,7 +759,7 @@ class DblSidedTool(FlatCAMTool):
             model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex())
             try:
                 bb_obj = model_index_box.internalPointer().obj
-            except Exception as e:
+            except Exception:
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
                 return
 
@@ -678,27 +772,39 @@ class DblSidedTool(FlatCAMTool):
         fcobj.plot()
         self.app.inform.emit('[success] Geometry %s %s...' % (str(fcobj.options['name']), _("was mirrored")))
 
-
     def on_point_add(self):
-        val = self.app.defaults["global_point_clipboard_format"] % (self.app.pos[0], self.app.pos[1])
+        val = self.app.defaults["global_point_clipboard_format"] % \
+              (self.decimals, self.app.pos[0], self.decimals, self.app.pos[1])
         self.point_entry.set_value(val)
 
     def on_drill_add(self):
         self.drill_values += (self.app.defaults["global_point_clipboard_format"] %
-                              (self.app.pos[0], self.app.pos[1])) + ','
+                              (self.decimals, self.app.pos[0], self.decimals, self.app.pos[1])) + ','
+        self.alignment_holes.set_value(self.drill_values)
+
+    def on_drill_delete_last(self):
+        drill_values_without_last_tupple = self.drill_values.rpartition('(')[0]
+        self.drill_values = drill_values_without_last_tupple
         self.alignment_holes.set_value(self.drill_values)
 
     def on_toggle_pointbox(self):
         if self.axis_location.get_value() == "point":
             self.point_entry.show()
+            self.add_point_button.show()
+            self.box_type_label.hide()
+            self.box_type_radio.hide()
             self.box_combo.hide()
-            self.box_combo_type.hide()
-            self.add_point_button.setDisabled(False)
+
+            self.align_ref_label_val.set_value(self.point_entry.get_value())
         else:
             self.point_entry.hide()
+            self.add_point_button.hide()
+
+            self.box_type_label.show()
+            self.box_type_radio.show()
             self.box_combo.show()
-            self.box_combo_type.show()
-            self.add_point_button.setDisabled(True)
+
+            self.align_ref_label_val.set_value("Box centroid")
 
     def on_bbox_coordinates(self):
 
@@ -734,6 +840,51 @@ class DblSidedTool(FlatCAMTool):
         self.center_entry.set_value(val_txt)
         self.axis_location.set_value('point')
         self.point_entry.set_value(val_txt)
+        self.app.delete_selection_shape()
+
+    def on_xmin_clicked(self):
+        xmin = self.xmin_entry.get_value()
+        self.axis_location.set_value('point')
+
+        try:
+            px, py = self.point_entry.get_value()
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmin, self.decimals, py)
+        except TypeError:
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmin, self.decimals, 0.0)
+        self.point_entry.set_value(val)
+
+    def on_ymin_clicked(self):
+        ymin = self.ymin_entry.get_value()
+        self.axis_location.set_value('point')
+
+        try:
+            px, py = self.point_entry.get_value()
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, px, self.decimals, ymin)
+        except TypeError:
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, 0.0, self.decimals, ymin)
+        self.point_entry.set_value(val)
+
+    def on_xmax_clicked(self):
+        xmax = self.xmax_entry.get_value()
+        self.axis_location.set_value('point')
+
+        try:
+            px, py = self.point_entry.get_value()
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmax, self.decimals, py)
+        except TypeError:
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmax, self.decimals, 0.0)
+        self.point_entry.set_value(val)
+
+    def on_ymax_clicked(self):
+        ymax = self.ymax_entry.get_value()
+        self.axis_location.set_value('point')
+
+        try:
+            px, py = self.point_entry.get_value()
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, px, self.decimals, ymax)
+        except TypeError:
+            val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, 0.0, self.decimals, ymax)
+        self.point_entry.set_value(val)
 
     def reset_fields(self):
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
@@ -745,6 +896,7 @@ class DblSidedTool(FlatCAMTool):
         self.exc_object_combo.setCurrentIndex(0)
         self.geo_object_combo.setCurrentIndex(0)
         self.box_combo.setCurrentIndex(0)
-        self.box_combo_type.setCurrentIndex(0)
+        self.box_type_radio.set_value('grb')
 
         self.drill_values = ""
+        self.align_ref_label_val.set_value('')

+ 229 - 76
flatcamTools/ToolDistance.py

@@ -9,13 +9,18 @@ from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.VisPyVisuals import *
-from flatcamGUI.GUIElements import FCEntry
+from flatcamGUI.GUIElements import FCEntry, FCButton, FCCheckBox
+
+from shapely.geometry import Point, MultiLineString, Polygon
+
+import FlatCAMTranslation as fcTranslate
+from camlib import FlatCAMRTreeStorage
+from flatcamEditors.FlatCAMGeoEditor import DrawToolShape
 
 from copy import copy
 import math
 import logging
 import gettext
-import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -43,82 +48,101 @@ class Distance(FlatCAMTool):
         self.layout.addWidget(title_label)
 
         # ## Form Layout
-        form_layout = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout)
+        grid0 = QtWidgets.QGridLayout()
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+        self.layout.addLayout(grid0)
 
         self.units_label = QtWidgets.QLabel('%s:' % _("Units"))
         self.units_label.setToolTip(_("Those are the units in which the distance is measured."))
         self.units_value = QtWidgets.QLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units]))
         self.units_value.setDisabled(True)
 
-        self.start_label = QtWidgets.QLabel("%s:" % _('Start Coords'))
-        self.start_label.setToolTip(_("This is measuring Start point coordinates."))
-
-        self.stop_label = QtWidgets.QLabel("%s:" % _('Stop Coords'))
-        self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
-
-        self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx"))
-        self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
+        grid0.addWidget(self.units_label, 0, 0)
+        grid0.addWidget(self.units_value, 0, 1)
 
-        self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy"))
-        self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
+        self.snap_center_cb = FCCheckBox(_("Snap to center"))
+        self.snap_center_cb.setToolTip(
+            _("Mouse cursor will snap to the center of the pad/drill\n"
+              "when it is hovering over the geometry of the pad/drill.")
+        )
+        grid0.addWidget(self.snap_center_cb, 1, 0, 1, 2)
 
-        self.angle_label = QtWidgets.QLabel('%s:' % _("Angle"))
-        self.angle_label.setToolTip(_("This is orientation angle of the measuring line."))
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 2, 0, 1, 2)
 
-        self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
-        self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance."))
+        self.start_label = QtWidgets.QLabel("%s:" % _('Start Coords'))
+        self.start_label.setToolTip(_("This is measuring Start point coordinates."))
 
         self.start_entry = FCEntry()
         self.start_entry.setReadOnly(True)
         self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.start_entry.setToolTip(_("This is measuring Start point coordinates."))
 
+        grid0.addWidget(self.start_label, 3, 0)
+        grid0.addWidget(self.start_entry, 3, 1)
+
+        self.stop_label = QtWidgets.QLabel("%s:" % _('Stop Coords'))
+        self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
+
         self.stop_entry = FCEntry()
         self.stop_entry.setReadOnly(True)
         self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.stop_entry.setToolTip(_("This is the measuring Stop point coordinates."))
 
+        grid0.addWidget(self.stop_label, 4, 0)
+        grid0.addWidget(self.stop_entry, 4, 1)
+
+        self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx"))
+        self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
+
         self.distance_x_entry = FCEntry()
         self.distance_x_entry.setReadOnly(True)
         self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis."))
 
+        grid0.addWidget(self.distance_x_label, 5, 0)
+        grid0.addWidget(self.distance_x_entry, 5, 1)
+
+        self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy"))
+        self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
+
         self.distance_y_entry = FCEntry()
         self.distance_y_entry.setReadOnly(True)
         self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis."))
 
+        grid0.addWidget(self.distance_y_label, 6, 0)
+        grid0.addWidget(self.distance_y_entry, 6, 1)
+
+        self.angle_label = QtWidgets.QLabel('%s:' % _("Angle"))
+        self.angle_label.setToolTip(_("This is orientation angle of the measuring line."))
+
         self.angle_entry = FCEntry()
         self.angle_entry.setReadOnly(True)
         self.angle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.angle_entry.setToolTip(_("This is orientation angle of the measuring line."))
 
+        grid0.addWidget(self.angle_label, 7, 0)
+        grid0.addWidget(self.angle_entry, 7, 1)
+
+        self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
+        self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance."))
+
         self.total_distance_entry = FCEntry()
         self.total_distance_entry.setReadOnly(True)
         self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.total_distance_entry.setToolTip(_("This is the point to point Euclidian distance."))
 
-        self.measure_btn = QtWidgets.QPushButton(_("Measure"))
+        grid0.addWidget(self.total_distance_label, 8, 0)
+        grid0.addWidget(self.total_distance_entry, 8, 1)
+
+        self.measure_btn = FCButton(_("Measure"))
         # self.measure_btn.setFixedWidth(70)
         self.layout.addWidget(self.measure_btn)
 
-        form_layout.addRow(self.units_label, self.units_value)
-        form_layout.addRow(self.start_label, self.start_entry)
-        form_layout.addRow(self.stop_label, self.stop_entry)
-        form_layout.addRow(self.distance_x_label, self.distance_x_entry)
-        form_layout.addRow(self.distance_y_label, self.distance_y_entry)
-        form_layout.addRow(self.angle_label, self.angle_entry)
-        form_layout.addRow(self.total_distance_label, self.total_distance_entry)
-
-        # initial view of the layout
-        self.start_entry.set_value('(0, 0)')
-        self.stop_entry.set_value('(0, 0)')
-        self.distance_x_entry.set_value('0.0')
-        self.distance_y_entry.set_value('0.0')
-        self.angle_entry.set_value('0.0')
-        self.total_distance_entry.set_value('0.0')
-
         self.layout.addStretch()
 
         # store here the first click and second click of the measurement process
@@ -137,6 +161,15 @@ class Distance(FlatCAMTool):
         self.mm = None
         self.mr = None
 
+        # monitor if the tool was used
+        self.tool_done = False
+
+        # store the grid status here
+        self.grid_status_memory = False
+
+        # store here if the snap button was clicked
+        self.snap_toggled = None
+
         # VisPy visuals
         if self.app.is_legacy is False:
             self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
@@ -154,6 +187,8 @@ class Distance(FlatCAMTool):
         self.rel_point1 = None
         self.rel_point2 = None
 
+        self.tool_done = False
+
         if self.app.tool_tab_locked is True:
             return
 
@@ -171,13 +206,13 @@ class Distance(FlatCAMTool):
             self.deactivate_measure_tool()
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='CTRL+M', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Ctrl+M', **kwargs)
 
     def set_tool_ui(self):
         # Remove anything else in the GUI
         self.app.ui.tool_scroll_area.takeWidget()
 
-        # Put ourself in the GUI
+        # Put ourselves in the GUI
         self.app.ui.tool_scroll_area.setWidget(self)
 
         # Switch notebook to tool page
@@ -195,17 +230,47 @@ class Distance(FlatCAMTool):
         self.angle_entry.set_value('0.0')
         self.total_distance_entry.set_value('0.0')
 
+        self.snap_center_cb.set_value(self.app.defaults['tools_dist_snap_center'])
+
+        # snap center works only for Gerber and Execellon Editor's
+        if self.original_call_source == 'exc_editor' or self.original_call_source == 'grb_editor':
+            self.snap_center_cb.show()
+            snap_center = self.app.defaults['tools_dist_snap_center']
+            self.on_snap_toggled(snap_center)
+
+            self.snap_center_cb.toggled.connect(self.on_snap_toggled)
+        else:
+            self.snap_center_cb.hide()
+            try:
+                self.snap_center_cb.toggled.disconnect(self.on_snap_toggled)
+            except (TypeError, AttributeError):
+                pass
+
         # this is a hack; seems that triggering the grid will make the visuals better
         # trigger it twice to return to the original state
         self.app.ui.grid_snap_btn.trigger()
         self.app.ui.grid_snap_btn.trigger()
 
+        if self.app.ui.grid_snap_btn.isChecked():
+            self.grid_status_memory = True
+
         log.debug("Distance Tool --> tool initialized")
 
+    def on_snap_toggled(self, state):
+        self.app.defaults['tools_dist_snap_center'] = state
+        if state:
+            # disengage the grid snapping since it will be hard to find the drills or pads on grid
+            if self.app.ui.grid_snap_btn.isChecked():
+                self.app.ui.grid_snap_btn.trigger()
+
     def activate_measure_tool(self):
         # ENABLE the Measuring TOOL
         self.active = True
 
+        # disable the measuring button
+        self.measure_btn.setDisabled(True)
+        self.measure_btn.setText('%s...' % _("Working"))
+
         self.clicked_meas = 0
         self.original_call_source = copy(self.app.call_source)
 
@@ -267,6 +332,10 @@ class Distance(FlatCAMTool):
         self.active = False
         self.points = []
 
+        # disable the measuring button
+        self.measure_btn.setDisabled(False)
+        self.measure_btn.setText(_("Measure"))
+
         self.app.call_source = copy(self.original_call_source)
         if self.original_call_source == 'app':
             self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
@@ -307,8 +376,16 @@ class Distance(FlatCAMTool):
         # delete the measuring line
         self.delete_shape()
 
+        # restore the grid status
+        if (self.app.ui.grid_snap_btn.isChecked() and self.grid_status_memory is False) or \
+                (not self.app.ui.grid_snap_btn.isChecked() and self.grid_status_memory is True):
+            self.app.ui.grid_snap_btn.trigger()
+
         log.debug("Distance Tool --> exit tool")
 
+        if self.tool_done is False:
+            self.app.inform.emit('%s' % _("Distance Tool finished."))
+
     def on_mouse_click_release(self, event):
         # mouse click releases will be accepted only if the left button is clicked
         # this is necessary because right mouse click or middle mouse click
@@ -323,11 +400,71 @@ class Distance(FlatCAMTool):
 
             pos_canvas = self.canvas.translate_coords(event_pos)
 
-            # if GRID is active we need to get the snapped positions
-            if self.app.grid_status() == True:
-                pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
+            if self.snap_center_cb.get_value() is False:
+                # if GRID is active we need to get the snapped positions
+                if self.app.grid_status():
+                    pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
+                else:
+                    pos = pos_canvas[0], pos_canvas[1]
             else:
-                pos = pos_canvas[0], pos_canvas[1]
+                pos = (pos_canvas[0], pos_canvas[1])
+                current_pt = Point(pos)
+                shapes_storage = self.make_storage()
+
+                if self.original_call_source == 'exc_editor':
+                    for storage in self.app.exc_editor.storage_dict:
+                        __, st_closest_shape = self.app.exc_editor.storage_dict[storage].nearest(pos)
+                        shapes_storage.insert(st_closest_shape)
+
+                    __, closest_shape = shapes_storage.nearest(pos)
+
+                    # if it's a drill
+                    if isinstance(closest_shape.geo, MultiLineString):
+                        radius = closest_shape.geo[0].length / 2.0
+                        center_pt = closest_shape.geo.centroid
+
+                        geo_buffered = center_pt.buffer(radius)
+
+                        if current_pt.within(geo_buffered):
+                            pos = (center_pt.x, center_pt.y)
+
+                    # if it's a slot
+                    elif isinstance(closest_shape.geo, Polygon):
+                        geo_buffered = closest_shape.geo.buffer(0)
+                        center_pt = geo_buffered.centroid
+
+                        if current_pt.within(geo_buffered):
+                            pos = (center_pt.x, center_pt.y)
+
+                elif self.original_call_source == 'grb_editor':
+                    clicked_pads = []
+                    for storage in self.app.grb_editor.storage_dict:
+                        try:
+                            for shape_stored in self.app.grb_editor.storage_dict[storage]['geometry']:
+                                if 'solid' in shape_stored.geo:
+                                    geometric_data = shape_stored.geo['solid']
+                                    if Point(current_pt).within(geometric_data):
+                                        if isinstance(shape_stored.geo['follow'], Point):
+                                            clicked_pads.append(shape_stored.geo['follow'])
+                        except KeyError:
+                            pass
+
+                    if len(clicked_pads) > 1:
+                        self.tool_done = True
+                        self.deactivate_measure_tool()
+                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Pads overlapped. Aborting."))
+                        return
+
+                    pos = (clicked_pads[0].x, clicked_pads[0].y)
+
+                self.app.on_jump_to(custom_location=pos, fit_center=False)
+                # Update cursor
+                self.app.app_cursor.enabled = True
+                self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
+                                             symbol='++', edge_color='#000000',
+                                             edge_width=self.app.defaults["global_cursor_width"],
+                                             size=self.app.defaults["global_cursor_size"])
+
             self.points.append(pos)
 
             # Reset here the relative coordinates so there is a new reference on the click position
@@ -340,40 +477,46 @@ class Distance(FlatCAMTool):
                 self.rel_point2 = copy(self.rel_point1)
                 self.rel_point1 = pos
 
-            if len(self.points) == 1:
-                self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
-                self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
-            elif len(self.points) == 2:
-                dx = self.points[1][0] - self.points[0][0]
-                dy = self.points[1][1] - self.points[0][1]
-                d = math.sqrt(dx ** 2 + dy ** 2)
-                self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
-
-                self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
-                    tx1=_("MEASURING"),
-                    tx2=_("Result"),
-                    tx3=_("Distance"),
-                    d_x='%*f' % (self.decimals, abs(dx)),
-                    d_y='%*f' % (self.decimals, abs(dy)),
-                    d_z='%*f' % (self.decimals, abs(d)))
-                )
+            self.calculate_distance(pos=pos)
+
+    def calculate_distance(self, pos):
+        if len(self.points) == 1:
+            self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
+            self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
+        elif len(self.points) == 2:
+            # self.app.app_cursor.enabled = False
+            dx = self.points[1][0] - self.points[0][0]
+            dy = self.points[1][1] - self.points[0][1]
+            d = math.sqrt(dx ** 2 + dy ** 2)
+            self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
+
+            self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
+                tx1=_("MEASURING"),
+                tx2=_("Result"),
+                tx3=_("Distance"),
+                d_x='%*f' % (self.decimals, abs(dx)),
+                d_y='%*f' % (self.decimals, abs(dy)),
+                d_z='%*f' % (self.decimals, abs(d)))
+            )
 
-                self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
-                self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
+            self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
+            self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
 
+            if dx != 0.0:
                 try:
                     angle = math.degrees(math.atan(dy / dx))
                     self.angle_entry.set_value('%.*f' % (self.decimals, angle))
-                except Exception as e:
+                except Exception:
                     pass
 
-                self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
-                self.app.ui.rel_position_label.setText(
-                    "<b>Dx</b>: {}&nbsp;&nbsp;  <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".format(
-                        '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
-                    )
+            self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
+            self.app.ui.rel_position_label.setText(
+                "<b>Dx</b>: {}&nbsp;&nbsp;  <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".format(
+                    '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
                 )
-                self.deactivate_measure_tool()
+            )
+            self.tool_done = True
+            self.deactivate_measure_tool()
 
     def on_mouse_move_meas(self, event):
         try:  # May fail in case mouse not within axes
@@ -390,7 +533,7 @@ class Distance(FlatCAMTool):
 
             pos_canvas = self.app.plotcanvas.translate_coords((x, y))
 
-            if self.app.grid_status() == True:
+            if self.app.grid_status():
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
 
                 # Update cursor
@@ -424,11 +567,13 @@ class Distance(FlatCAMTool):
             if len(self.points) == 1:
                 self.utility_geometry(pos=pos)
                 # and display the temporary angle
-                try:
-                    angle = math.degrees(math.atan(dy / dx))
-                    self.angle_entry.set_value('%.*f' % (self.decimals, angle))
-                except Exception as e:
-                    pass
+                if dx != 0.0:
+                    try:
+                        angle = math.degrees(math.atan(dy / dx))
+                        self.angle_entry.set_value('%.*f' % (self.decimals, angle))
+                    except Exception as e:
+                        log.debug("Distance.on_mouse_move_meas() -> update utility geometry -> %s" % str(e))
+                        pass
 
         except Exception as e:
             log.debug("Distance.on_mouse_move_meas() --> %s" % str(e))
@@ -453,7 +598,7 @@ class Distance(FlatCAMTool):
         else:
             color = '#FFFFFFFF'
 
-        self.sel_shapes.add(meas_line, color=color, update=True, layer=0, tolerance=None)
+        self.sel_shapes.add(meas_line, color=color, update=True, layer=0, tolerance=None, linewidth=2)
 
         if self.app.is_legacy is True:
             self.sel_shapes.redraw()
@@ -462,7 +607,15 @@ class Distance(FlatCAMTool):
         self.sel_shapes.clear()
         self.sel_shapes.redraw()
 
-    def set_meas_units(self, units):
-        self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]")
+    @staticmethod
+    def make_storage():
+        # ## Shape storage.
+        storage = FlatCAMRTreeStorage()
+        storage.get_points = DrawToolShape.get_pts
+
+        return storage
+
+    # def set_meas_units(self, units):
+    #     self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]")
 
 # end of file

+ 15 - 12
flatcamTools/ToolDistanceMin.py

@@ -11,7 +11,8 @@ from flatcamGUI.VisPyVisuals import *
 from flatcamGUI.GUIElements import FCEntry
 
 from shapely.ops import nearest_points
-from shapely.geometry import Point
+from shapely.geometry import Point, MultiPolygon
+from shapely.ops import cascaded_union
 
 import math
 import logging
@@ -127,15 +128,6 @@ class DistanceMin(FlatCAMTool):
         form_layout.addRow(self.total_distance_label, self.total_distance_entry)
         form_layout.addRow(self.half_point_label, self.half_point_entry)
 
-        # initial view of the layout
-        self.start_entry.set_value('(0, 0)')
-        self.stop_entry.set_value('(0, 0)')
-        self.distance_x_entry.set_value('0.0')
-        self.distance_y_entry.set_value('0.0')
-        self.angle_entry.set_value('0.0')
-        self.total_distance_entry.set_value('0.0')
-        self.half_point_entry.set_value('(0, 0)')
-
         self.layout.addStretch()
 
         self.h_point = (0, 0)
@@ -163,7 +155,7 @@ class DistanceMin(FlatCAMTool):
                              _("Select two objects and no more, to measure the distance between them ..."))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='SHIFT+M', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Shift+M', **kwargs)
 
     def set_tool_ui(self):
         # Remove anything else in the GUI
@@ -205,6 +197,17 @@ class DistanceMin(FlatCAMTool):
                                      str(len(selected_objs))))
                 return
             else:
+                if isinstance(selected_objs[0].solid_geometry, list):
+                    try:
+                        selected_objs[0].solid_geometry = MultiPolygon(selected_objs[0].solid_geometry)
+                    except Exception:
+                        selected_objs[0].solid_geometry = cascaded_union(selected_objs[0].solid_geometry)
+
+                    try:
+                        selected_objs[1].solid_geometry = MultiPolygon(selected_objs[1].solid_geometry)
+                    except Exception:
+                        selected_objs[1].solid_geometry = cascaded_union(selected_objs[1].solid_geometry)
+
                 first_pos, last_pos = nearest_points(selected_objs[0].solid_geometry, selected_objs[1].solid_geometry)
 
         elif self.app.call_source == 'geo_editor':
@@ -278,7 +281,7 @@ class DistanceMin(FlatCAMTool):
             )
 
         if d != 0:
-            self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | (tx3} = {d_z}".format(
+            self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
                 tx1=_("MEASURING"),
                 tx2=_("Result"),
                 tx3=_("Distance"),

+ 694 - 0
flatcamTools/ToolExtractDrills.py

@@ -0,0 +1,694 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# File Author: Marius Adrian Stanciu (c)                   #
+# Date: 1/10/2020                                          #
+# MIT Licence                                              #
+# ##########################################################
+
+from PyQt5 import QtWidgets, QtCore
+
+from FlatCAMTool import FlatCAMTool
+from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCComboBox
+
+from shapely.geometry import Point
+
+import logging
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+log = logging.getLogger('base')
+
+
+class ToolExtractDrills(FlatCAMTool):
+
+    toolName = _("Extract Drills")
+
+    def __init__(self, app):
+        FlatCAMTool.__init__(self, app)
+        self.decimals = self.app.decimals
+
+        # ## Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                        QLabel
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(title_label)
+
+        self.layout.addWidget(QtWidgets.QLabel(""))
+
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 1)
+        grid_lay.setColumnStretch(1, 0)
+
+        # ## Gerber Object
+        self.gerber_object_combo = FCComboBox()
+        self.gerber_object_combo.setModel(self.app.collection)
+        self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.gerber_object_combo.is_last = True
+        self.gerber_object_combo.obj_type = "Gerber"
+
+        self.grb_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.grb_label.setToolTip('%s.' % _("Gerber from which to extract drill holes"))
+
+        # grid_lay.addRow("Bottom Layer:", self.object_combo)
+        grid_lay.addWidget(self.grb_label, 0, 0, 1, 2)
+        grid_lay.addWidget(self.gerber_object_combo, 1, 0, 1, 2)
+
+        self.padt_label = QtWidgets.QLabel("<b>%s</b>" % _("Processed Pads Type"))
+        self.padt_label.setToolTip(
+            _("The type of pads shape to be processed.\n"
+              "If the PCB has many SMD pads with rectangular pads,\n"
+              "disable the Rectangular aperture.")
+        )
+
+        grid_lay.addWidget(self.padt_label, 2, 0, 1, 2)
+
+        # Circular Aperture Selection
+        self.circular_cb = FCCheckBox('%s' % _("Circular"))
+        self.circular_cb.setToolTip(
+            _("Process Circular Pads.")
+        )
+
+        grid_lay.addWidget(self.circular_cb, 3, 0, 1, 2)
+
+        # Oblong Aperture Selection
+        self.oblong_cb = FCCheckBox('%s' % _("Oblong"))
+        self.oblong_cb.setToolTip(
+            _("Process Oblong Pads.")
+        )
+
+        grid_lay.addWidget(self.oblong_cb, 4, 0, 1, 2)
+
+        # Square Aperture Selection
+        self.square_cb = FCCheckBox('%s' % _("Square"))
+        self.square_cb.setToolTip(
+            _("Process Square Pads.")
+        )
+
+        grid_lay.addWidget(self.square_cb, 5, 0, 1, 2)
+
+        # Rectangular Aperture Selection
+        self.rectangular_cb = FCCheckBox('%s' % _("Rectangular"))
+        self.rectangular_cb.setToolTip(
+            _("Process Rectangular Pads.")
+        )
+
+        grid_lay.addWidget(self.rectangular_cb, 6, 0, 1, 2)
+
+        # Others type of Apertures Selection
+        self.other_cb = FCCheckBox('%s' % _("Others"))
+        self.other_cb.setToolTip(
+            _("Process pads not in the categories above.")
+        )
+
+        grid_lay.addWidget(self.other_cb, 7, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 8, 0, 1, 2)
+
+        # ## Grid Layout
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+        grid1.setColumnStretch(0, 0)
+        grid1.setColumnStretch(1, 1)
+
+        self.method_label = QtWidgets.QLabel('<b>%s</b>' % _("Method"))
+        self.method_label.setToolTip(
+            _("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"))
+        grid1.addWidget(self.method_label, 2, 0, 1, 2)
+
+        # ## Holes Size
+        self.hole_size_radio = RadioSet(
+            [
+                {'label': _("Fixed Diameter"), 'value': 'fixed'},
+                {'label': _("Fixed Annular Ring"), 'value': 'ring'},
+                {'label': _("Proportional"), 'value': 'prop'}
+            ],
+            orientation='vertical',
+            stretch=False)
+
+        grid1.addWidget(self.hole_size_radio, 3, 0, 1, 2)
+
+        # grid_lay1.addWidget(QtWidgets.QLabel(''))
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 5, 0, 1, 2)
+
+        # Annular Ring
+        self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
+        grid1.addWidget(self.fixed_label, 6, 0, 1, 2)
+
+        # Diameter value
+        self.dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.dia_entry.set_precision(self.decimals)
+        self.dia_entry.set_range(0.0000, 9999.9999)
+
+        self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
+        self.dia_label.setToolTip(
+            _("Fixed hole diameter.")
+        )
+
+        grid1.addWidget(self.dia_label, 8, 0)
+        grid1.addWidget(self.dia_entry, 8, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 9, 0, 1, 2)
+
+        self.ring_frame = QtWidgets.QFrame()
+        self.ring_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.ring_frame)
+
+        self.ring_box = QtWidgets.QVBoxLayout()
+        self.ring_box.setContentsMargins(0, 0, 0, 0)
+        self.ring_frame.setLayout(self.ring_box)
+
+        # ## Grid Layout
+        grid2 = QtWidgets.QGridLayout()
+        grid2.setColumnStretch(0, 0)
+        grid2.setColumnStretch(1, 1)
+        self.ring_box.addLayout(grid2)
+
+        # Annular Ring value
+        self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
+        self.ring_label.setToolTip(
+            _("The size of annular ring.\n"
+              "The copper sliver between the hole exterior\n"
+              "and the margin of the copper pad.")
+        )
+        grid2.addWidget(self.ring_label, 0, 0, 1, 2)
+
+        # Circular Annular Ring Value
+        self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
+        self.circular_ring_label.setToolTip(
+            _("The size of annular ring for circular pads.")
+        )
+
+        self.circular_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.circular_ring_entry.set_precision(self.decimals)
+        self.circular_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid2.addWidget(self.circular_ring_label, 1, 0)
+        grid2.addWidget(self.circular_ring_entry, 1, 1)
+
+        # Oblong Annular Ring Value
+        self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
+        self.oblong_ring_label.setToolTip(
+            _("The size of annular ring for oblong pads.")
+        )
+
+        self.oblong_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.oblong_ring_entry.set_precision(self.decimals)
+        self.oblong_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid2.addWidget(self.oblong_ring_label, 2, 0)
+        grid2.addWidget(self.oblong_ring_entry, 2, 1)
+
+        # Square Annular Ring Value
+        self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
+        self.square_ring_label.setToolTip(
+            _("The size of annular ring for square pads.")
+        )
+
+        self.square_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.square_ring_entry.set_precision(self.decimals)
+        self.square_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid2.addWidget(self.square_ring_label, 3, 0)
+        grid2.addWidget(self.square_ring_entry, 3, 1)
+
+        # Rectangular Annular Ring Value
+        self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
+        self.rectangular_ring_label.setToolTip(
+            _("The size of annular ring for rectangular pads.")
+        )
+
+        self.rectangular_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.rectangular_ring_entry.set_precision(self.decimals)
+        self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid2.addWidget(self.rectangular_ring_label, 4, 0)
+        grid2.addWidget(self.rectangular_ring_entry, 4, 1)
+
+        # Others Annular Ring Value
+        self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
+        self.other_ring_label.setToolTip(
+            _("The size of annular ring for other pads.")
+        )
+
+        self.other_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.other_ring_entry.set_precision(self.decimals)
+        self.other_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid2.addWidget(self.other_ring_label, 5, 0)
+        grid2.addWidget(self.other_ring_entry, 5, 1)
+
+        grid3 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid3)
+        grid3.setColumnStretch(0, 0)
+        grid3.setColumnStretch(1, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid3.addWidget(separator_line, 1, 0, 1, 2)
+
+        # Annular Ring value
+        self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
+        grid3.addWidget(self.prop_label, 2, 0, 1, 2)
+
+        # Diameter value
+        self.factor_entry = FCDoubleSpinner(callback=self.confirmation_message, suffix='%')
+        self.factor_entry.set_precision(self.decimals)
+        self.factor_entry.set_range(0.0000, 100.0000)
+        self.factor_entry.setSingleStep(0.1)
+
+        self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
+        self.factor_label.setToolTip(
+            _("Proportional Diameter.\n"
+              "The hole diameter will be a fraction of the pad size.")
+        )
+
+        grid3.addWidget(self.factor_label, 3, 0)
+        grid3.addWidget(self.factor_entry, 3, 1)
+
+        # Extract drills from Gerber apertures flashes (pads)
+        self.e_drills_button = QtWidgets.QPushButton(_("Extract Drills"))
+        self.e_drills_button.setToolTip(
+            _("Extract drills from a given Gerber file.")
+        )
+        self.e_drills_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(self.e_drills_button)
+
+        self.layout.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.reset_button)
+
+        self.circular_ring_entry.setEnabled(False)
+        self.oblong_ring_entry.setEnabled(False)
+        self.square_ring_entry.setEnabled(False)
+        self.rectangular_ring_entry.setEnabled(False)
+        self.other_ring_entry.setEnabled(False)
+
+        self.dia_entry.setDisabled(True)
+        self.dia_label.setDisabled(True)
+        self.factor_label.setDisabled(True)
+        self.factor_entry.setDisabled(True)
+
+        self.ring_frame.setDisabled(True)
+
+        # ## Signals
+        self.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
+        self.e_drills_button.clicked.connect(self.on_extract_drills_click)
+        self.reset_button.clicked.connect(self.set_tool_ui)
+
+        self.circular_cb.stateChanged.connect(
+            lambda state:
+                self.circular_ring_entry.setDisabled(False) if state else self.circular_ring_entry.setDisabled(True)
+        )
+
+        self.oblong_cb.stateChanged.connect(
+            lambda state:
+            self.oblong_ring_entry.setDisabled(False) if state else self.oblong_ring_entry.setDisabled(True)
+        )
+
+        self.square_cb.stateChanged.connect(
+            lambda state:
+            self.square_ring_entry.setDisabled(False) if state else self.square_ring_entry.setDisabled(True)
+        )
+
+        self.rectangular_cb.stateChanged.connect(
+            lambda state:
+            self.rectangular_ring_entry.setDisabled(False) if state else self.rectangular_ring_entry.setDisabled(True)
+        )
+
+        self.other_cb.stateChanged.connect(
+            lambda state:
+            self.other_ring_entry.setDisabled(False) if state else self.other_ring_entry.setDisabled(True)
+        )
+
+    def install(self, icon=None, separator=None, **kwargs):
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+I', **kwargs)
+
+    def run(self, toggle=True):
+        self.app.report_usage("Extract Drills()")
+
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
+
+        self.app.ui.notebook.setTabText(2, _("Extract Drills Tool"))
+
+    def set_tool_ui(self):
+        self.reset_fields()
+
+        self.hole_size_radio.set_value(self.app.defaults["tools_edrills_hole_type"])
+
+        self.dia_entry.set_value(float(self.app.defaults["tools_edrills_hole_fixed_dia"]))
+
+        self.circular_ring_entry.set_value(float(self.app.defaults["tools_edrills_circular_ring"]))
+        self.oblong_ring_entry.set_value(float(self.app.defaults["tools_edrills_oblong_ring"]))
+        self.square_ring_entry.set_value(float(self.app.defaults["tools_edrills_square_ring"]))
+        self.rectangular_ring_entry.set_value(float(self.app.defaults["tools_edrills_rectangular_ring"]))
+        self.other_ring_entry.set_value(float(self.app.defaults["tools_edrills_others_ring"]))
+
+        self.circular_cb.set_value(self.app.defaults["tools_edrills_circular"])
+        self.oblong_cb.set_value(self.app.defaults["tools_edrills_oblong"])
+        self.square_cb.set_value(self.app.defaults["tools_edrills_square"])
+        self.rectangular_cb.set_value(self.app.defaults["tools_edrills_rectangular"])
+        self.other_cb.set_value(self.app.defaults["tools_edrills_others"])
+
+        self.factor_entry.set_value(float(self.app.defaults["tools_edrills_hole_prop_factor"]))
+
+    def on_extract_drills_click(self):
+
+        drill_dia = self.dia_entry.get_value()
+        circ_r_val = self.circular_ring_entry.get_value()
+        oblong_r_val = self.oblong_ring_entry.get_value()
+        square_r_val = self.square_ring_entry.get_value()
+        rect_r_val = self.rectangular_ring_entry.get_value()
+        other_r_val = self.other_ring_entry.get_value()
+
+        prop_factor = self.factor_entry.get_value() / 100.0
+
+        drills = []
+        tools = {}
+
+        selection_index = self.gerber_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
+
+        try:
+            fcobj = model_index.internalPointer().obj
+        except Exception:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
+            return
+
+        outname = fcobj.options['name'].rpartition('.')[0]
+
+        mode = self.hole_size_radio.get_value()
+
+        if mode == 'fixed':
+            tools = {"1": {"C": drill_dia}}
+            for apid, apid_value in fcobj.apertures.items():
+                ap_type = apid_value['type']
+
+                if ap_type == 'C':
+                    if self.circular_cb.get_value() is False:
+                        continue
+                elif ap_type == 'O':
+                    if self.oblong_cb.get_value() is False:
+                        continue
+                elif ap_type == 'R':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    # if the height == width (float numbers so the reason for the following)
+                    if round(width, self.decimals) == round(height, self.decimals):
+                        if self.square_cb.get_value() is False:
+                            continue
+                    else:
+                        if self.rectangular_cb.get_value() is False:
+                            continue
+                else:
+                    if self.other_cb.get_value() is False:
+                        continue
+
+                for geo_el in apid_value['geometry']:
+                    if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
+                        drills.append({"point": geo_el['follow'], "tool": "1"})
+                        if 'solid_geometry' not in tools["1"]:
+                            tools["1"]['solid_geometry'] = []
+                        else:
+                            tools["1"]['solid_geometry'].append(geo_el['follow'])
+
+            if 'solid_geometry' not in tools["1"] or not tools["1"]['solid_geometry']:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
+                return
+        elif mode == 'ring':
+            drills_found = set()
+            for apid, apid_value in fcobj.apertures.items():
+                ap_type = apid_value['type']
+
+                dia = None
+                if ap_type == 'C':
+                    if self.circular_cb.get_value():
+                        dia = float(apid_value['size']) - (2 * circ_r_val)
+                elif ap_type == 'O':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+                    if self.oblong_cb.get_value():
+                        if width > height:
+                            dia = float(apid_value['height']) - (2 * oblong_r_val)
+                        else:
+                            dia = float(apid_value['width']) - (2 * oblong_r_val)
+                elif ap_type == 'R':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    # if the height == width (float numbers so the reason for the following)
+                    if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
+                            (10 ** -self.decimals):
+                        if self.square_cb.get_value():
+                            dia = float(apid_value['height']) - (2 * square_r_val)
+                    else:
+                        if self.rectangular_cb.get_value():
+                            if width > height:
+                                dia = float(apid_value['height']) - (2 * rect_r_val)
+                            else:
+                                dia = float(apid_value['width']) - (2 * rect_r_val)
+                else:
+                    if self.other_cb.get_value():
+                        try:
+                            dia = float(apid_value['size']) - (2 * other_r_val)
+                        except KeyError:
+                            if ap_type == 'AM':
+                                pol = apid_value['geometry'][0]['solid']
+                                x0, y0, x1, y1 = pol.bounds
+                                dx = x1 - x0
+                                dy = y1 - y0
+                                if dx <= dy:
+                                    dia = dx - (2 * other_r_val)
+                                else:
+                                    dia = dy - (2 * other_r_val)
+
+                # if dia is None then none of the above applied so we skip the following
+                if dia is None:
+                    continue
+
+                tool_in_drills = False
+                for tool, tool_val in tools.items():
+                    if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
+                            (10 ** -self.decimals):
+                        tool_in_drills = tool
+
+                if tool_in_drills is False:
+                    if tools:
+                        new_tool = max([int(t) for t in tools]) + 1
+                        tool_in_drills = str(new_tool)
+                    else:
+                        tool_in_drills = "1"
+
+                for geo_el in apid_value['geometry']:
+                    if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
+                        if tool_in_drills not in tools:
+                            tools[tool_in_drills] = {"C": dia}
+
+                        drills.append({"point": geo_el['follow'], "tool": tool_in_drills})
+
+                        if 'solid_geometry' not in tools[tool_in_drills]:
+                            tools[tool_in_drills]['solid_geometry'] = []
+                        else:
+                            tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
+
+                if tool_in_drills in tools:
+                    if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
+                        drills_found.add(False)
+                    else:
+                        drills_found.add(True)
+
+            if True not in drills_found:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
+                return
+        else:
+            drills_found = set()
+            for apid, apid_value in fcobj.apertures.items():
+                ap_type = apid_value['type']
+
+                dia = None
+                if ap_type == 'C':
+                    if self.circular_cb.get_value():
+                        dia = float(apid_value['size']) * prop_factor
+                elif ap_type == 'O':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+                    if self.oblong_cb.get_value():
+                        if width > height:
+                            dia = float(apid_value['height']) * prop_factor
+                        else:
+                            dia = float(apid_value['width']) * prop_factor
+                elif ap_type == 'R':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    # if the height == width (float numbers so the reason for the following)
+                    if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
+                            (10 ** -self.decimals):
+                        if self.square_cb.get_value():
+                            dia = float(apid_value['height']) * prop_factor
+                    else:
+                        if self.rectangular_cb.get_value():
+                            if width > height:
+                                dia = float(apid_value['height']) * prop_factor
+                            else:
+                                dia = float(apid_value['width']) * prop_factor
+                else:
+                    if self.other_cb.get_value():
+                        try:
+                            dia = float(apid_value['size']) * prop_factor
+                        except KeyError:
+                            if ap_type == 'AM':
+                                pol = apid_value['geometry'][0]['solid']
+                                x0, y0, x1, y1 = pol.bounds
+                                dx = x1 - x0
+                                dy = y1 - y0
+                                if dx <= dy:
+                                    dia = dx * prop_factor
+                                else:
+                                    dia = dy * prop_factor
+
+                # if dia is None then none of the above applied so we skip the following
+                if dia is None:
+                    continue
+
+                tool_in_drills = False
+                for tool, tool_val in tools.items():
+                    if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
+                            (10 ** -self.decimals):
+                        tool_in_drills = tool
+
+                if tool_in_drills is False:
+                    if tools:
+                        new_tool = max([int(t) for t in tools]) + 1
+                        tool_in_drills = str(new_tool)
+                    else:
+                        tool_in_drills = "1"
+
+                for geo_el in apid_value['geometry']:
+                    if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
+                        if tool_in_drills not in tools:
+                            tools[tool_in_drills] = {"C": dia}
+
+                        drills.append({"point": geo_el['follow'], "tool": tool_in_drills})
+
+                        if 'solid_geometry' not in tools[tool_in_drills]:
+                            tools[tool_in_drills]['solid_geometry'] = []
+                        else:
+                            tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
+
+                if tool_in_drills in tools:
+                    if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
+                        drills_found.add(False)
+                    else:
+                        drills_found.add(True)
+
+            if True not in drills_found:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
+                return
+
+        def obj_init(obj_inst, app_inst):
+            obj_inst.tools = tools
+            obj_inst.drills = drills
+            obj_inst.create_geometry()
+            obj_inst.source_file = self.app.export_excellon(obj_name=outname, local_use=obj_inst, filename=None,
+                                                            use_thread=False)
+
+        self.app.new_object("excellon", outname, obj_init)
+
+    def on_hole_size_toggle(self, val):
+        if val == "fixed":
+            self.fixed_label.setDisabled(False)
+            self.dia_entry.setDisabled(False)
+            self.dia_label.setDisabled(False)
+
+            self.ring_frame.setDisabled(True)
+
+            self.prop_label.setDisabled(True)
+            self.factor_label.setDisabled(True)
+            self.factor_entry.setDisabled(True)
+        elif val == "ring":
+            self.fixed_label.setDisabled(True)
+            self.dia_entry.setDisabled(True)
+            self.dia_label.setDisabled(True)
+
+            self.ring_frame.setDisabled(False)
+
+            self.prop_label.setDisabled(True)
+            self.factor_label.setDisabled(True)
+            self.factor_entry.setDisabled(True)
+        elif val == "prop":
+            self.fixed_label.setDisabled(True)
+            self.dia_entry.setDisabled(True)
+            self.dia_label.setDisabled(True)
+
+            self.ring_frame.setDisabled(True)
+
+            self.prop_label.setDisabled(False)
+            self.factor_label.setDisabled(False)
+            self.factor_entry.setDisabled(False)
+
+    def reset_fields(self):
+        self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.gerber_object_combo.setCurrentIndex(0)

+ 35 - 33
flatcamTools/ToolFiducials.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, EvalEntry, FCTable
+from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, EvalEntry, FCTable, FCComboBox
 
 from shapely.geometry import Point, Polygon, MultiPolygon, LineString
 from shapely.geometry import box as box
@@ -159,7 +159,7 @@ class ToolFiducials(FlatCAMTool):
               "otherwise is the size of the fiducial.\n"
               "The soldermask opening is double than that.")
         )
-        self.fid_size_entry = FCDoubleSpinner()
+        self.fid_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.fid_size_entry.set_range(1.0000, 3.0000)
         self.fid_size_entry.set_precision(self.decimals)
         self.fid_size_entry.setWrapping(True)
@@ -173,7 +173,7 @@ class ToolFiducials(FlatCAMTool):
         self.margin_label.setToolTip(
             _("Bounding box margin.")
         )
-        self.margin_entry = FCDoubleSpinner()
+        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.margin_entry.set_range(-9999.9999, 9999.9999)
         self.margin_entry.set_precision(self.decimals)
         self.margin_entry.setSingleStep(0.1)
@@ -236,7 +236,7 @@ class ToolFiducials(FlatCAMTool):
         self.line_thickness_label.setToolTip(
             _("Bounding box margin.")
         )
-        self.line_thickness_entry = FCDoubleSpinner()
+        self.line_thickness_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.line_thickness_entry.set_range(0.00001, 9999.9999)
         self.line_thickness_entry.set_precision(self.decimals)
         self.line_thickness_entry.setSingleStep(0.1)
@@ -250,10 +250,11 @@ class ToolFiducials(FlatCAMTool):
         grid_lay.addWidget(separator_line_1, 8, 0, 1, 2)
 
         # Copper Gerber object
-        self.grb_object_combo = QtWidgets.QComboBox()
+        self.grb_object_combo = FCComboBox()
         self.grb_object_combo.setModel(self.app.collection)
         self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.grb_object_combo.setCurrentIndex(1)
+        self.grb_object_combo.is_last = True
+        self.grb_object_combo.obj_type = "Gerber"
 
         self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("Copper Gerber"))
         self.grbobj_label.setToolTip(
@@ -286,10 +287,11 @@ class ToolFiducials(FlatCAMTool):
         self.sm_object_label.setToolTip(
             _("The Soldermask Gerber object.")
         )
-        self.sm_object_combo = QtWidgets.QComboBox()
+        self.sm_object_combo = FCComboBox()
         self.sm_object_combo.setModel(self.app.collection)
         self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.sm_object_combo.setCurrentIndex(1)
+        self.sm_object_combo.is_last = True
+        self.sm_object_combo.obj_type = "Gerber"
 
         grid_lay.addWidget(self.sm_object_label, 13, 0, 1, 2)
         grid_lay.addWidget(self.sm_object_combo, 14, 0, 1, 2)
@@ -333,7 +335,7 @@ class ToolFiducials(FlatCAMTool):
         self.sm_obj_set = set()
 
         # store the flattened geometry here:
-        self.flat_geometry = list()
+        self.flat_geometry = []
 
         # Events ID
         self.mr = None
@@ -353,7 +355,7 @@ class ToolFiducials(FlatCAMTool):
         self.sec_position = None
         self.geo_steps_per_circle = 128
 
-        self.click_points = list()
+        self.click_points = []
 
         # SIGNALS
         self.add_cfid_button.clicked.connect(self.add_fiducials)
@@ -393,7 +395,7 @@ class ToolFiducials(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Fiducials Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+J', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+J', **kwargs)
 
     def set_tool_ui(self):
         self.units = self.app.defaults['units']
@@ -404,7 +406,7 @@ class ToolFiducials(FlatCAMTool):
         self.fid_type_radio.set_value(self.app.defaults["tools_fiducials_type"])
         self.line_thickness_entry.set_value(float(self.app.defaults["tools_fiducials_line_thickness"]))
 
-        self.click_points = list()
+        self.click_points = []
         self.bottom_left_coords_entry.set_value('')
         self.top_right_coords_entry.set_value('')
         self.sec_points_coords_entry.set_value('')
@@ -429,7 +431,7 @@ class ToolFiducials(FlatCAMTool):
         :return: None
         """
         if val == 'auto':
-            self.click_points = list()
+            self.click_points = []
 
             try:
                 self.disconnect_event_handlers()
@@ -451,7 +453,7 @@ class ToolFiducials(FlatCAMTool):
         self.sec_position = self.pos_radio.get_value()
         fid_type = self.fid_type_radio.get_value()
 
-        self.click_points = list()
+        self.click_points = []
 
         # get the Gerber object on which the Fiducial will be inserted
         selection_index = self.grb_object_combo.currentIndex()
@@ -547,7 +549,7 @@ class ToolFiducials(FlatCAMTool):
 
             if aperture_found:
                 for geo in geo_list:
-                    dict_el = dict()
+                    dict_el = {}
                     dict_el['follow'] = geo.centroid
                     dict_el['solid'] = geo
                     g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
@@ -558,18 +560,18 @@ class ToolFiducials(FlatCAMTool):
                 else:
                     new_apid = '10'
 
-                g_obj.apertures[new_apid] = dict()
+                g_obj.apertures[new_apid] = {}
                 g_obj.apertures[new_apid]['type'] = 'C'
                 g_obj.apertures[new_apid]['size'] = fid_size
-                g_obj.apertures[new_apid]['geometry'] = list()
+                g_obj.apertures[new_apid]['geometry'] = []
 
                 for geo in geo_list:
-                    dict_el = dict()
+                    dict_el = {}
                     dict_el['follow'] = geo.centroid
                     dict_el['solid'] = geo
                     g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
 
-            s_list = list()
+            s_list = []
             if g_obj.solid_geometry:
                 try:
                     for poly in g_obj.solid_geometry:
@@ -580,7 +582,7 @@ class ToolFiducials(FlatCAMTool):
             s_list += geo_list
             g_obj.solid_geometry = MultiPolygon(s_list)
         elif fid_type == 'cross':
-            geo_list = list()
+            geo_list = []
 
             for pt in points_list:
                 x = pt[0]
@@ -599,7 +601,7 @@ class ToolFiducials(FlatCAMTool):
                     aperture_found = ap_id
                     break
 
-            geo_buff_list = list()
+            geo_buff_list = []
             if aperture_found:
                 for geo in geo_list:
                     geo_buff_h = geo[0].buffer(line_thickness / 2.0)
@@ -607,7 +609,7 @@ class ToolFiducials(FlatCAMTool):
                     geo_buff_list.append(geo_buff_h)
                     geo_buff_list.append(geo_buff_v)
 
-                    dict_el = dict()
+                    dict_el = {}
                     dict_el['follow'] = geo_buff_h.centroid
                     dict_el['solid'] = geo_buff_h
                     g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
@@ -621,10 +623,10 @@ class ToolFiducials(FlatCAMTool):
                 else:
                     new_apid = '10'
 
-                g_obj.apertures[new_apid] = dict()
+                g_obj.apertures[new_apid] = {}
                 g_obj.apertures[new_apid]['type'] = 'C'
                 g_obj.apertures[new_apid]['size'] = line_thickness
-                g_obj.apertures[new_apid]['geometry'] = list()
+                g_obj.apertures[new_apid]['geometry'] = []
 
                 for geo in geo_list:
                     geo_buff_h = geo[0].buffer(line_thickness / 2.0)
@@ -632,7 +634,7 @@ class ToolFiducials(FlatCAMTool):
                     geo_buff_list.append(geo_buff_h)
                     geo_buff_list.append(geo_buff_v)
 
-                    dict_el = dict()
+                    dict_el = {}
                     dict_el['follow'] = geo_buff_h.centroid
                     dict_el['solid'] = geo_buff_h
                     g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
@@ -640,7 +642,7 @@ class ToolFiducials(FlatCAMTool):
                     dict_el['solid'] = geo_buff_v
                     g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
 
-            s_list = list()
+            s_list = []
             if g_obj.solid_geometry:
                 try:
                     for poly in g_obj.solid_geometry:
@@ -655,7 +657,7 @@ class ToolFiducials(FlatCAMTool):
             g_obj.solid_geometry = MultiPolygon(s_list)
         else:
             # chess pattern fiducial type
-            geo_list = list()
+            geo_list = []
 
             def make_square_poly(center_pt, side_size):
                 half_s = side_size / 2
@@ -684,12 +686,12 @@ class ToolFiducials(FlatCAMTool):
                     aperture_found = ap_id
                     break
 
-            geo_buff_list = list()
+            geo_buff_list = []
             if aperture_found:
                 for geo in geo_list:
                     geo_buff_list.append(geo)
 
-                    dict_el = dict()
+                    dict_el = {}
                     dict_el['follow'] = geo.centroid
                     dict_el['solid'] = geo
                     g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
@@ -700,22 +702,22 @@ class ToolFiducials(FlatCAMTool):
                 else:
                     new_apid = '10'
 
-                g_obj.apertures[new_apid] = dict()
+                g_obj.apertures[new_apid] = {}
                 g_obj.apertures[new_apid]['type'] = 'R'
                 g_obj.apertures[new_apid]['size'] = new_ap_size
                 g_obj.apertures[new_apid]['width'] = fid_size
                 g_obj.apertures[new_apid]['height'] = fid_size
-                g_obj.apertures[new_apid]['geometry'] = list()
+                g_obj.apertures[new_apid]['geometry'] = []
 
                 for geo in geo_list:
                     geo_buff_list.append(geo)
 
-                    dict_el = dict()
+                    dict_el = {}
                     dict_el['follow'] = geo.centroid
                     dict_el['solid'] = geo
                     g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
 
-            s_list = list()
+            s_list = []
             if g_obj.solid_geometry:
                 try:
                     for poly in g_obj.solid_geometry:

+ 67 - 50
flatcamTools/ToolFilm.py

@@ -9,7 +9,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
 
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
-    OptionalHideInputSection, OptionalInputSection, FCComboBox
+    OptionalHideInputSection, OptionalInputSection, FCComboBox, FCFileSaveDialog
 
 from copy import deepcopy
 import logging
@@ -65,15 +65,13 @@ class Film(FlatCAMTool):
         grid0.setColumnStretch(1, 1)
 
         # Type of object for which to create the film
-        self.tf_type_obj_combo = QtWidgets.QComboBox()
-        self.tf_type_obj_combo.addItem("Gerber")
-        self.tf_type_obj_combo.addItem("Excellon")
-        self.tf_type_obj_combo.addItem("Geometry")
+        self.tf_type_obj_combo = FCComboBox()
+        self.tf_type_obj_combo.addItems([_("Gerber"), _("Geometry")])
 
         # we get rid of item1 ("Excellon") as it is not suitable for creating film
-        self.tf_type_obj_combo.view().setRowHidden(1, True)
+        # self.tf_type_obj_combo.view().setRowHidden(1, True)
         self.tf_type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
-        self.tf_type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
+        self.tf_type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
         self.tf_type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.tf_type_obj_combo_label.setToolTip(
@@ -86,10 +84,10 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.tf_type_obj_combo, 0, 1)
 
         # List of objects for which we can create the film
-        self.tf_object_combo = QtWidgets.QComboBox()
+        self.tf_object_combo = FCComboBox()
         self.tf_object_combo.setModel(self.app.collection)
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.tf_object_combo.setCurrentIndex(1)
+        self.tf_object_combo.is_last = True
 
         self.tf_object_label = QtWidgets.QLabel('%s:' % _("Film Object"))
         self.tf_object_label.setToolTip(
@@ -100,15 +98,17 @@ class Film(FlatCAMTool):
 
         # Type of Box Object to be used as an envelope for film creation
         # Within this we can create negative
-        self.tf_type_box_combo = QtWidgets.QComboBox()
-        self.tf_type_box_combo.addItem("Gerber")
-        self.tf_type_box_combo.addItem("Excellon")
-        self.tf_type_box_combo.addItem("Geometry")
+        self.tf_type_box_combo = FCComboBox()
+        self.tf_type_box_combo.addItems([_("Gerber"), _("Geometry")])
+
+        # self.tf_type_box_combo.addItem("Gerber")
+        # self.tf_type_box_combo.addItem("Excellon")
+        # self.tf_type_box_combo.addItem("Geometry")
 
         # we get rid of item1 ("Excellon") as it is not suitable for box when creating film
-        self.tf_type_box_combo.view().setRowHidden(1, True)
+        # self.tf_type_box_combo.view().setRowHidden(1, True)
         self.tf_type_box_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
-        self.tf_type_box_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
+        self.tf_type_box_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
         self.tf_type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
         self.tf_type_box_combo_label.setToolTip(
@@ -121,10 +121,10 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.tf_type_box_combo, 2, 1)
 
         # Box
-        self.tf_box_combo = QtWidgets.QComboBox()
+        self.tf_box_combo = FCComboBox()
         self.tf_box_combo.setModel(self.app.collection)
         self.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.tf_box_combo.setCurrentIndex(1)
+        self.tf_box_combo.is_last = True
 
         self.tf_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Object"))
         self.tf_box_combo_label.setToolTip(
@@ -160,7 +160,7 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2)
 
         self.film_scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
-        self.film_scalex_entry = FCDoubleSpinner()
+        self.film_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_scalex_entry.set_range(-999.9999, 999.9999)
         self.film_scalex_entry.set_precision(self.decimals)
         self.film_scalex_entry.setSingleStep(0.01)
@@ -169,7 +169,7 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.film_scalex_entry, 7, 1)
 
         self.film_scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
-        self.film_scaley_entry = FCDoubleSpinner()
+        self.film_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_scaley_entry.set_range(-999.9999, 999.9999)
         self.film_scaley_entry.set_precision(self.decimals)
         self.film_scaley_entry.setSingleStep(0.01)
@@ -199,7 +199,7 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2)
 
         self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
-        self.film_skewx_entry = FCDoubleSpinner()
+        self.film_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_skewx_entry.set_range(-999.9999, 999.9999)
         self.film_skewx_entry.set_precision(self.decimals)
         self.film_skewx_entry.setSingleStep(0.01)
@@ -208,7 +208,7 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.film_skewx_entry, 11, 1)
 
         self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
-        self.film_skewy_entry = FCDoubleSpinner()
+        self.film_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_skewy_entry.set_range(-999.9999, 999.9999)
         self.film_skewy_entry.set_precision(self.decimals)
         self.film_skewy_entry.setSingleStep(0.01)
@@ -275,7 +275,7 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.film_param_label, 18, 0, 1, 2)
 
         # Scale Stroke size
-        self.film_scale_stroke_entry = FCDoubleSpinner()
+        self.film_scale_stroke_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_scale_stroke_entry.set_range(-999.9999, 999.9999)
         self.film_scale_stroke_entry.setSingleStep(0.01)
         self.film_scale_stroke_entry.set_precision(self.decimals)
@@ -308,7 +308,7 @@ class Film(FlatCAMTool):
         grid0.addWidget(self.film_type, 21, 1)
 
         # Boundary for negative film generation
-        self.boundary_entry = FCDoubleSpinner()
+        self.boundary_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.boundary_entry.set_range(-999.9999, 999.9999)
         self.boundary_entry.setSingleStep(0.01)
         self.boundary_entry.set_precision(self.decimals)
@@ -366,10 +366,12 @@ class Film(FlatCAMTool):
         self.exc_label.setToolTip(
             _("Remove the geometry of Excellon from the Film to create the holes in pads.")
         )
-        self.exc_combo = QtWidgets.QComboBox()
+        self.exc_combo = FCComboBox()
         self.exc_combo.setModel(self.app.collection)
         self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.exc_combo.setCurrentIndex(1)
+        self.exc_combo.is_last = True
+        self.exc_combo.obj_type = "Excellon"
+
         punch_grid.addWidget(self.exc_label, 1, 0)
         punch_grid.addWidget(self.exc_combo, 1, 1)
 
@@ -378,7 +380,7 @@ class Film(FlatCAMTool):
 
         self.punch_size_label = QtWidgets.QLabel('%s:' % _("Punch Size"))
         self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads."))
-        self.punch_size_spinner = FCDoubleSpinner()
+        self.punch_size_spinner = FCDoubleSpinner(callback=self.confirmation_message)
         self.punch_size_spinner.set_range(0, 999.9999)
         self.punch_size_spinner.setSingleStep(0.1)
         self.punch_size_spinner.set_precision(self.decimals)
@@ -434,7 +436,7 @@ class Film(FlatCAMTool):
 
         self.pagesize_combo = FCComboBox()
 
-        self.pagesize = dict()
+        self.pagesize = {}
         self.pagesize.update(
             {
                 'Bounds': None,
@@ -539,15 +541,21 @@ class Film(FlatCAMTool):
         self.file_type_radio.activated_custom.connect(self.on_file_type)
         self.reset_button.clicked.connect(self.set_tool_ui)
 
-    def on_type_obj_index_changed(self, index):
+    def on_type_obj_index_changed(self):
         obj_type = self.tf_type_obj_combo.currentIndex()
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.tf_object_combo.setCurrentIndex(0)
+        self.tf_object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Geometry"): "Geometry"
+        }[self.tf_type_obj_combo.get_value()]
 
-    def on_type_box_index_changed(self, index):
+    def on_type_box_index_changed(self):
         obj_type = self.tf_type_box_combo.currentIndex()
         self.tf_box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.tf_box_combo.setCurrentIndex(0)
+        self.tf_box_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Geometry"): "Geometry"
+        }[self.tf_type_obj_combo.get_value()]
 
     def run(self, toggle=True):
         self.app.report_usage("ToolFilm()")
@@ -578,7 +586,7 @@ class Film(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Film Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+L', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+L', **kwargs)
 
     def set_tool_ui(self):
         self.reset_fields()
@@ -610,6 +618,10 @@ class Film(FlatCAMTool):
         self.orientation_radio.set_value(self.app.defaults["tools_film_orientation"])
         self.pagesize_combo.set_value(self.app.defaults["tools_film_pagesize"])
 
+        # run once to update the obj_type attribute in the FCCombobox so the last object is showed in cb
+        self.on_type_obj_index_changed()
+        self.on_type_box_index_changed()
+
     def on_film_type(self, val):
         type_of_film = val
 
@@ -729,17 +741,17 @@ class Film(FlatCAMTool):
                          "All Files (*.*)"
 
         try:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+            filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export positive film"),
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
                 filter=filter_ext)
         except TypeError:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export positive film"))
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export positive film"))
 
         filename = str(filename)
 
         if str(filename) == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export positive film cancelled."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
             return
         else:
             pagesize = self.pagesize_combo.get_value()
@@ -752,7 +764,7 @@ class Film(FlatCAMTool):
                                  skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
                                  skew_reference=skew_reference,
                                  mirror=mirror,
-                                 pagesize=pagesize, orientation=orientation, color=color, opacity=1.0,
+                                 pagesize_val=pagesize, orientation_val=orientation, color_val=color, opacity_val=1.0,
                                  ftype=ftype
                                  )
 
@@ -786,7 +798,7 @@ class Film(FlatCAMTool):
 
             punch_size = float(self.punch_size_spinner.get_value())
 
-            punching_geo = list()
+            punching_geo = []
             for apid in film_obj.apertures:
                 if film_obj.apertures[apid]['type'] == 'C':
                     if punch_size >= float(film_obj.apertures[apid]['size']):
@@ -875,17 +887,17 @@ class Film(FlatCAMTool):
                          "All Files (*.*)"
 
         try:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+            filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export negative film"),
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
                 filter=filter_ext)
         except TypeError:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export negative film"))
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export negative film"))
 
         filename = str(filename)
 
         if str(filename) == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export negative film cancelled."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
             return
         else:
             self.export_negative(name, boxname, filename, border,
@@ -1080,23 +1092,28 @@ class Film(FlatCAMTool):
                         skew_factor_x=None, skew_factor_y=None, skew_reference='center',
                         mirror=None,  orientation_val='p', pagesize_val='A4', color_val='black', opacity_val=1.0,
                         use_thread=True, ftype='svg'):
+
         """
         Exports a Geometry Object to an SVG file in positive black.
 
-        :param obj_name: the name of the FlatCAM object to be saved as SVG
-        :param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
-        :param filename: Path to the SVG file to save to.
+        :param obj_name:            the name of the FlatCAM object to be saved
+        :param box_name:            the name of the FlatCAM object to be used as delimitation of the content to be saved
+        :param filename:            Path to the file to save to.
         :param scale_stroke_factor: factor by which to change/scale the thickness of the features
-        :param scale_factor_x: factor to scale the svg geometry on the X axis
-        :param scale_factor_y: factor to scale the svg geometry on the Y axis
-        :param skew_factor_x: factor to skew the svg geometry on the X axis
-        :param skew_factor_y: factor to skew the svg geometry on the Y axis
-        :param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
-        those are the 4 points of the bounding box of the geometry to be skewed.
-        :param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
+        :param scale_factor_x:      factor to scale the geometry on the X axis
+        :param scale_factor_y:      factor to scale the geometry on the Y axis
+        :param skew_factor_x:       factor to skew the geometry on the X axis
+        :param skew_factor_y:       factor to skew the geometry on the Y axis
+        :param skew_reference:      reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft',
+        'topright' and those are the 4 points of the bounding box of the geometry to be skewed.
+        :param mirror:              can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
+        :param orientation_val:
+        :param pagesize_val:
+        :param color_val:
+        :param opacity_val:
+        :param use_thread:          if to be run in a separate thread; boolean
+        :param ftype:               the type of file for saving the film: 'svg', 'png' or 'pdf'
 
-        :param use_thread: if to be run in a separate thread; boolean
-        :param ftype: the type of file for saving the film: 'svg', 'png' or 'pdf'
         :return:
         """
         self.app.report_usage("export_positive()")

+ 13 - 14
flatcamTools/ToolImage.py

@@ -46,8 +46,7 @@ class ToolImage(FlatCAMTool):
 
         # Type of object to create for the image
         self.tf_type_obj_combo = FCComboBox()
-        self.tf_type_obj_combo.addItem("Gerber")
-        self.tf_type_obj_combo.addItem("Geometry")
+        self.tf_type_obj_combo.addItems([_("Gerber"), _("Geometry")])
 
         self.tf_type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.tf_type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
@@ -61,7 +60,7 @@ class ToolImage(FlatCAMTool):
         ti_form_layout.addRow(self.tf_type_obj_combo_label, self.tf_type_obj_combo)
 
         # DPI value of the imported image
-        self.dpi_entry = FCSpinner()
+        self.dpi_entry = FCSpinner(callback=self.confirmation_message_int)
         self.dpi_entry.set_range(0, 99999)
         self.dpi_label = QtWidgets.QLabel('%s:' % _("DPI value"))
         self.dpi_label.setToolTip(_("Specify a DPI value for the image.") )
@@ -87,7 +86,7 @@ class ToolImage(FlatCAMTool):
         ti2_form_layout.addRow(self.image_type_label, self.image_type)
 
         # Mask value of the imported image when image monochrome
-        self.mask_bw_entry = FCSpinner()
+        self.mask_bw_entry = FCSpinner(callback=self.confirmation_message_int)
         self.mask_bw_entry.set_range(0, 255)
 
         self.mask_bw_label = QtWidgets.QLabel("%s <b>B/W</b>:" % _('Mask value'))
@@ -102,7 +101,7 @@ class ToolImage(FlatCAMTool):
         ti2_form_layout.addRow(self.mask_bw_label, self.mask_bw_entry)
 
         # Mask value of the imported image for RED color when image color
-        self.mask_r_entry = FCSpinner()
+        self.mask_r_entry = FCSpinner(callback=self.confirmation_message_int)
         self.mask_r_entry.set_range(0, 255)
 
         self.mask_r_label = QtWidgets.QLabel("%s <b>R:</b>" % _('Mask value'))
@@ -115,7 +114,7 @@ class ToolImage(FlatCAMTool):
         ti2_form_layout.addRow(self.mask_r_label, self.mask_r_entry)
 
         # Mask value of the imported image for GREEN color when image color
-        self.mask_g_entry = FCSpinner()
+        self.mask_g_entry = FCSpinner(callback=self.confirmation_message_int)
         self.mask_g_entry.set_range(0, 255)
 
         self.mask_g_label = QtWidgets.QLabel("%s <b>G:</b>" % _('Mask value'))
@@ -128,7 +127,7 @@ class ToolImage(FlatCAMTool):
         ti2_form_layout.addRow(self.mask_g_label, self.mask_g_entry)
 
         # Mask value of the imported image for BLUE color when image color
-        self.mask_b_entry = FCSpinner()
+        self.mask_b_entry = FCSpinner(callback=self.confirmation_message_int)
         self.mask_b_entry.set_range(0, 255)
 
         self.mask_b_label = QtWidgets.QLabel("%s <b>B:</b>" % _('Mask value'))
@@ -223,7 +222,7 @@ class ToolImage(FlatCAMTool):
         :type type_of_obj: str
         :return: None
         """
-        mask = list()
+        mask = []
         self.app.log.debug("on_file_importimage()")
 
         _filter = "Image Files(*.BMP *.PNG *.JPG *.JPEG);;" \
@@ -238,19 +237,19 @@ class ToolImage(FlatCAMTool):
             filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"), filter=filter)
 
         filename = str(filename)
-        type_obj = self.tf_type_obj_combo.get_value().lower()
+        type_obj = self.tf_type_obj_combo.get_value()
         dpi = self.dpi_entry.get_value()
         mode = self.image_type.get_value()
         mask = [self.mask_bw_entry.get_value(), self.mask_r_entry.get_value(), self.mask_g_entry.get_value(),
                 self.mask_b_entry.get_value()]
 
         if filename == "":
-            self.app.inform.emit(_("Open cancelled."))
+            self.app.inform.emit(_("Cancelled."))
         else:
             self.app.worker_task.emit({'fcn': self.import_image,
                                        'params': [filename, type_obj, dpi, mode, mask]})
 
-    def import_image(self, filename, o_type='gerber', dpi=96, mode='black', mask=None, outname=None):
+    def import_image(self, filename, o_type=_("Gerber"), dpi=96, mode='black', mask=None, outname=None):
         """
         Adds a new Geometry Object to the projects and populates
         it with shapes extracted from the SVG file.
@@ -269,10 +268,10 @@ class ToolImage(FlatCAMTool):
         if mask is None:
             mask = [250, 250, 250, 250]
 
-        if o_type is None or o_type == "geometry":
+        if o_type is None or o_type == _("Geometry"):
             obj_type = "geometry"
-        elif o_type == "gerber":
-            obj_type = o_type
+        elif o_type == _("Gerber"):
+            obj_type = "gerber"
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("Not supported type is picked as parameter. "

+ 304 - 0
flatcamTools/ToolInvertGerber.py

@@ -0,0 +1,304 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# File Author: Marius Adrian Stanciu (c)                   #
+# Date: 2/14/2020                                          #
+# MIT Licence                                              #
+# ##########################################################
+
+from PyQt5 import QtWidgets, QtCore
+
+from FlatCAMTool import FlatCAMTool
+from flatcamGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox
+
+from shapely.geometry import box
+
+from copy import deepcopy
+
+import logging
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+log = logging.getLogger('base')
+
+
+class ToolInvertGerber(FlatCAMTool):
+
+    toolName = _("Invert Gerber Tool")
+
+    def __init__(self, app):
+        self.app = app
+        self.decimals = self.app.decimals
+
+        FlatCAMTool.__init__(self, app)
+
+        self.tools_frame = QtWidgets.QFrame()
+        self.tools_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.tools_frame)
+        self.tools_box = QtWidgets.QVBoxLayout()
+        self.tools_box.setContentsMargins(0, 0, 0, 0)
+        self.tools_frame.setLayout(self.tools_box)
+
+        # Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                        QLabel
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.tools_box.addWidget(title_label)
+
+        # Grid Layout
+        grid0 = QtWidgets.QGridLayout()
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+        self.tools_box.addLayout(grid0)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2)
+
+        # Target Gerber Object
+        self.gerber_combo = FCComboBox()
+        self.gerber_combo.setModel(self.app.collection)
+        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.gerber_combo.is_last = True
+        self.gerber_combo.obj_type = "Gerber"
+
+        self.gerber_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
+        self.gerber_label.setToolTip(
+            _("Gerber object that will be inverted.")
+        )
+
+        grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
+        grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
+
+        grid0.addWidget(QtWidgets.QLabel(""), 3, 0, 1, 2)
+
+        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.param_label.setToolTip('%s.' % _("Parameters for this tool"))
+
+        grid0.addWidget(self.param_label, 4, 0, 1, 2)
+
+        # Margin
+        self.margin_label = QtWidgets.QLabel('%s:' % _('Margin'))
+        self.margin_label.setToolTip(
+            _("Distance by which to avoid\n"
+              "the edges of the Gerber object.")
+        )
+        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.margin_entry.set_precision(self.decimals)
+        self.margin_entry.set_range(0.0000, 9999.9999)
+        self.margin_entry.setObjectName(_("Margin"))
+
+        grid0.addWidget(self.margin_label, 5, 0, 1, 2)
+        grid0.addWidget(self.margin_entry, 6, 0, 1, 2)
+
+        self.join_label = QtWidgets.QLabel('%s:' % _("Lines Join Style"))
+        self.join_label.setToolTip(
+            _("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")
+        )
+        self.join_radio = RadioSet([
+            {'label': 'Rounded', 'value': 'r'},
+            {'label': 'Square', 'value': 's'},
+            {'label': 'Bevel', 'value': 'b'}
+        ], orientation='vertical', stretch=False)
+
+        grid0.addWidget(self.join_label, 7, 0, 1, 2)
+        grid0.addWidget(self.join_radio, 8, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 9, 0, 1, 2)
+
+        self.invert_btn = FCButton(_('Invert Gerber'))
+        self.invert_btn.setToolTip(
+            _("Will invert the Gerber object: areas that have copper\n"
+              "will be empty of copper and previous empty area will be\n"
+              "filled with copper.")
+        )
+        self.invert_btn.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        grid0.addWidget(self.invert_btn, 10, 0, 1, 2)
+
+        self.tools_box.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.tools_box.addWidget(self.reset_button)
+
+        self.invert_btn.clicked.connect(self.on_grb_invert)
+        self.reset_button.clicked.connect(self.set_tool_ui)
+
+    def install(self, icon=None, separator=None, **kwargs):
+        FlatCAMTool.install(self, icon, separator, shortcut='', **kwargs)
+
+    def run(self, toggle=True):
+        self.app.report_usage("ToolInvertGerber()")
+        log.debug("ToolInvertGerber() is running ...")
+
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
+
+        self.app.ui.notebook.setTabText(2, _("Invert Tool"))
+
+    def set_tool_ui(self):
+        self.margin_entry.set_value(float(self.app.defaults["tools_invert_margin"]))
+        self.join_radio.set_value(self.app.defaults["tools_invert_join_style"])
+
+    def on_grb_invert(self):
+        margin = self.margin_entry.get_value()
+        if round(margin, self.decimals) == 0.0:
+            margin = 1E-10
+
+        join_style = {'r': 1, 'b': 3, 's': 2}[self.join_radio.get_value()]
+        if join_style is None:
+            join_style = 'r'
+
+        grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
+        obj_name = self.gerber_combo.currentText()
+
+        outname = obj_name + "_inverted"
+
+        # Get source object.
+        try:
+            grb_obj = self.app.collection.get_by_name(obj_name)
+        except Exception as e:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
+            return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
+
+        if grb_obj is None:
+            if obj_name == '':
+                obj_name = 'None'
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
+            return
+
+        xmin, ymin, xmax, ymax = grb_obj.bounds()
+
+        grb_box = box(xmin, ymin, xmax, ymax).buffer(margin, resolution=grb_circle_steps, join_style=join_style)
+
+        try:
+            __ = iter(grb_obj.solid_geometry)
+        except TypeError:
+            grb_obj.solid_geometry = list(grb_obj.solid_geometry)
+
+        new_solid_geometry = deepcopy(grb_box)
+
+        for poly in grb_obj.solid_geometry:
+            new_solid_geometry = new_solid_geometry.difference(poly)
+
+        new_options = {}
+        for opt in grb_obj.options:
+            new_options[opt] = deepcopy(grb_obj.options[opt])
+
+        new_apertures = {}
+
+        # for apid, val in grb_obj.apertures.items():
+        #     new_apertures[apid] = {}
+        #     for key in val:
+        #         if key == 'geometry':
+        #             new_apertures[apid]['geometry'] = []
+        #             for elem in val['geometry']:
+        #                 geo_elem = {}
+        #                 if 'follow' in elem:
+        #                     try:
+        #                         geo_elem['clear'] = elem['follow'].buffer(val['size'] / 2.0).exterior
+        #                     except AttributeError:
+        #                         # TODO should test if width or height is bigger
+        #                         geo_elem['clear'] = elem['follow'].buffer(val['width'] / 2.0).exterior
+        #                 if 'clear' in elem:
+        #                     if isinstance(elem['clear'], Polygon):
+        #                         try:
+        #                             geo_elem['solid'] = elem['clear'].buffer(val['size'] / 2.0, grb_circle_steps)
+        #                         except AttributeError:
+        #                             # TODO should test if width or height is bigger
+        #                             geo_elem['solid'] = elem['clear'].buffer(val['width'] / 2.0, grb_circle_steps)
+        #                     else:
+        #                         geo_elem['follow'] = elem['clear']
+        #                 new_apertures[apid]['geometry'].append(deepcopy(geo_elem))
+        #         else:
+        #             new_apertures[apid][key] = deepcopy(val[key])
+
+        if '0' not in new_apertures:
+            new_apertures['0'] = {}
+            new_apertures['0']['type'] = 'C'
+            new_apertures['0']['size'] = 0.0
+            new_apertures['0']['geometry'] = []
+
+        try:
+            for poly in new_solid_geometry:
+                new_el = {}
+                new_el['solid'] = poly
+                new_el['follow'] = poly.exterior
+                new_apertures['0']['geometry'].append(new_el)
+        except TypeError:
+            new_el = {}
+            new_el['solid'] = new_solid_geometry
+            new_el['follow'] = new_solid_geometry.exterior
+            new_apertures['0']['geometry'].append(new_el)
+
+        for td in new_apertures:
+            print(td, new_apertures[td])
+
+        def init_func(new_obj, app_obj):
+            new_obj.options.update(new_options)
+            new_obj.options['name'] = outname
+            new_obj.fill_color = deepcopy(grb_obj.fill_color)
+            new_obj.outline_color = deepcopy(grb_obj.outline_color)
+
+            new_obj.apertures = deepcopy(new_apertures)
+
+            new_obj.solid_geometry = deepcopy(new_solid_geometry)
+            new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
+                                                         local_use=new_obj, use_thread=False)
+
+        self.app.new_object('gerber', outname, init_func)
+
+    def reset_fields(self):
+        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+
+    @staticmethod
+    def poly2rings(poly):
+        return [poly.exterior] + [interior for interior in poly.interiors]
+# end of file

+ 12 - 2
flatcamTools/ToolMove.py

@@ -111,7 +111,7 @@ class ToolMove(FlatCAMTool):
                 self.draw_sel_bbox()
             else:
                 self.toggle()
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("MOVE action cancelled. No object(s) to move."))
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No object(s) to move."))
 
     def on_left_click(self, event):
         # mouse click will be accepted only if the left button is clicked
@@ -188,6 +188,16 @@ class ToolMove(FlatCAMTool):
                                     sel_obj.options['ymin'] = b
                                     sel_obj.options['xmax'] = c
                                     sel_obj.options['ymax'] = d
+
+                                # update the source_file with the new positions
+                                for sel_obj in obj_list:
+                                    out_name = sel_obj.options["name"]
+                                    if sel_obj.kind == 'gerber':
+                                        sel_obj.source_file = self.app.export_gerber(
+                                            obj_name=out_name, filename=None, local_use=sel_obj, use_thread=False)
+                                    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)))
                                 return "fail"
@@ -257,7 +267,7 @@ class ToolMove(FlatCAMTool):
     def on_key_press(self, event):
         if event.key == 'escape':
             # abort the move action
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Move action cancelled."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
             self.toggle()
         return
 

Plik diff jest za duży
+ 415 - 288
flatcamTools/ToolNCC.py


+ 12 - 11
flatcamTools/ToolOptimal.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox
+from flatcamGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox, FCComboBox
 from FlatCAMObj import FlatCAMGerber
 import FlatCAMApp
 
@@ -63,10 +63,11 @@ class ToolOptimal(FlatCAMTool):
         form_lay.addRow(QtWidgets.QLabel(""))
 
         # ## Gerber Object to mirror
-        self.gerber_object_combo = QtWidgets.QComboBox()
+        self.gerber_object_combo = FCComboBox()
         self.gerber_object_combo.setModel(self.app.collection)
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.gerber_object_combo.setCurrentIndex(1)
+        self.gerber_object_combo.is_last = True
+        self.gerber_object_combo.obj_type = "Gerber"
 
         self.gerber_object_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
         self.gerber_object_label.setToolTip(
@@ -78,7 +79,7 @@ class ToolOptimal(FlatCAMTool):
         self.precision_label = QtWidgets.QLabel('%s:' % _("Precision"))
         self.precision_label.setToolTip(_("Number of decimals kept for found distances."))
 
-        self.precision_spinner = FCSpinner()
+        self.precision_spinner = FCSpinner(callback=self.confirmation_message_int)
         self.precision_spinner.set_range(2, 10)
         self.precision_spinner.setWrapping(True)
         form_lay.addRow(self.precision_label, self.precision_spinner)
@@ -259,7 +260,7 @@ class ToolOptimal(FlatCAMTool):
 
         # dict to hold the distances between every two elements in Gerber as keys and the actual locations where that
         # distances happen as values
-        self.min_dict = dict()
+        self.min_dict = {}
 
         # ############################################################################
         # ############################ Signals #######################################
@@ -277,14 +278,11 @@ class ToolOptimal(FlatCAMTool):
         self.reset_button.clicked.connect(self.set_tool_ui)
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+O', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+O', **kwargs)
 
     def run(self, toggle=True):
         self.app.report_usage("ToolOptimal()")
 
-        self.result_entry.set_value(0.0)
-        self.freq_entry.set_value('0')
-
         if toggle:
             # if the splitter is hidden, display it, else hide it but only if the current widget is the same
             if self.app.ui.splitter.sizes()[0] == 0:
@@ -310,6 +308,9 @@ class ToolOptimal(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
 
     def set_tool_ui(self):
+        self.result_entry.set_value(0.0)
+        self.freq_entry.set_value('0')
+
         self.precision_spinner.set_value(int(self.app.defaults["tools_opt_precision"]))
         self.locations_textb.clear()
         # new cursor - select all document
@@ -354,7 +355,7 @@ class ToolOptimal(FlatCAMTool):
                 old_disp_number = 0
                 pol_nr = 0
                 app_obj.proc_container.update_view_text(' %d%%' % 0)
-                total_geo = list()
+                total_geo = []
 
                 for ap in list(fcobj.apertures.keys()):
                     if 'geometry' in fcobj.apertures[ap]:
@@ -388,7 +389,7 @@ class ToolOptimal(FlatCAMTool):
                     '%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"),
                                 str(geo_len)))
 
-                self.min_dict = dict()
+                self.min_dict = {}
                 idx = 1
                 for geo in total_geo:
                     for s_geo in total_geo[idx:]:

+ 41 - 41
flatcamTools/ToolPDF.py

@@ -105,7 +105,7 @@ class ToolPDF(FlatCAMTool):
         self.restore_gs_re = re.compile(r'^.*Q.*$')
 
         # graphic stack where we save parameters like transformation, line_width
-        self.gs = dict()
+        self.gs = {}
         # each element is a list composed of sublist elements
         # (each sublist has 2 lists each having 2 elements: first is offset like:
         # offset_geo = [off_x, off_y], second element is scale list with 2 elements, like: scale_geo = [sc_x, sc_yy])
@@ -135,7 +135,7 @@ class ToolPDF(FlatCAMTool):
         self.on_open_pdf_click()
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='CTRL+Q', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Ctrl+Q', **kwargs)
 
     def set_tool_ui(self):
         pass
@@ -434,12 +434,12 @@ class ToolPDF(FlatCAMTool):
             traceback.print_exc()
 
     def parse_pdf(self, pdf_content):
-        path = dict()
+        path = {}
         path['lines'] = []      # it's a list of lines subpaths
         path['bezier'] = []     # it's a list of bezier arcs subpaths
         path['rectangle'] = []  # it's a list of rectangle subpaths
 
-        subpath = dict()
+        subpath = {}
         subpath['lines'] = []      # it's a list of points
         subpath['bezier'] = []     # it's a list of sublists each like this [start, c1, c2, stop]
         subpath['rectangle'] = []  # it's a list of sublists of points
@@ -473,9 +473,9 @@ class ToolPDF(FlatCAMTool):
 
         # store the apertures with clear geometry here
         # we are interested only in the circular geometry (drill holes) therefore we target only Bezier subpaths
-        clear_apertures_dict = dict()
+        clear_apertures_dict = {}
         # everything will be stored in the '0' aperture since we are dealing with clear polygons not strokes
-        clear_apertures_dict['0'] = dict()
+        clear_apertures_dict['0'] = {}
         clear_apertures_dict['0']['size'] = 0.0
         clear_apertures_dict['0']['type'] = 'C'
         clear_apertures_dict['0']['geometry'] = []
@@ -515,7 +515,7 @@ class ToolPDF(FlatCAMTool):
                         apertures_dict.clear()
                         layer_nr += 1
 
-                        object_dict[layer_nr] = dict()
+                        object_dict[layer_nr] = {}
                 old_color = copy(color)
                 # we make sure that the following geometry is added to the right storage
                 flag_clear_geo = False
@@ -778,7 +778,7 @@ class ToolPDF(FlatCAMTool):
             if match:
                 # scale the size here; some PDF printers apply transformation after the size is declared
                 applied_size = size * scale_geo[0] * self.point_to_unit_factor
-                path_geo = list()
+                path_geo = []
                 if current_subpath == 'lines':
                     if path['lines']:
                         for subp in path['lines']:
@@ -859,12 +859,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
@@ -879,12 +879,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
@@ -896,12 +896,12 @@ class ToolPDF(FlatCAMTool):
                     for pdf_geo in path_geo:
                         if isinstance(pdf_geo, MultiPolygon):
                             for poly in pdf_geo:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = poly
                                 new_el['follow'] = poly.exterior
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                         else:
-                            new_el = dict()
+                            new_el = {}
                             new_el['solid'] = pdf_geo
                             new_el['follow'] = pdf_geo.exterior
                             apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
@@ -913,7 +913,7 @@ class ToolPDF(FlatCAMTool):
             if match:
                 # scale the size here; some PDF printers apply transformation after the size is declared
                 applied_size = size * scale_geo[0] * self.point_to_unit_factor
-                path_geo = list()
+                path_geo = []
 
                 if current_subpath == 'lines':
                     if path['lines']:
@@ -1007,11 +1007,11 @@ class ToolPDF(FlatCAMTool):
                         if path_geo:
                             try:
                                 for g in path_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['clear'] = g
                                     clear_apertures_dict['0']['geometry'].append(new_el)
                             except TypeError:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['clear'] = path_geo
                                 clear_apertures_dict['0']['geometry'].append(new_el)
 
@@ -1022,11 +1022,11 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['clear'] = poly
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['clear'] = pdf_geo
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     except KeyError:
@@ -1038,11 +1038,11 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['clear'] = poly
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['clear'] = pdf_geo
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                 else:
@@ -1051,12 +1051,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
@@ -1069,12 +1069,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
@@ -1085,8 +1085,8 @@ class ToolPDF(FlatCAMTool):
             if match:
                 # scale the size here; some PDF printers apply transformation after the size is declared
                 applied_size = size * scale_geo[0] * self.point_to_unit_factor
-                path_geo = list()
-                fill_geo = list()
+                path_geo = []
+                fill_geo = []
 
                 if current_subpath == 'lines':
                     if path['lines']:
@@ -1222,12 +1222,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
@@ -1242,12 +1242,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
@@ -1259,12 +1259,12 @@ class ToolPDF(FlatCAMTool):
                     for pdf_geo in path_geo:
                         if isinstance(pdf_geo, MultiPolygon):
                             for poly in pdf_geo:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = poly
                                 new_el['follow'] = poly.exterior
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                         else:
-                            new_el = dict()
+                            new_el = {}
                             new_el['solid'] = pdf_geo
                             new_el['follow'] = pdf_geo.exterior
                             apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
@@ -1279,11 +1279,11 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in fill_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['clear'] = poly
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['clear'] = pdf_geo
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     except KeyError:
@@ -1295,11 +1295,11 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in fill_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['clear'] = poly
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['clear'] = pdf_geo
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                 else:
@@ -1307,12 +1307,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in fill_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
@@ -1325,12 +1325,12 @@ class ToolPDF(FlatCAMTool):
                         for pdf_geo in fill_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = dict()
+                                    new_el = {}
                                     new_el['solid'] = poly
                                     new_el['follow'] = poly.exterior
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = dict()
+                                new_el = {}
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))

Plik diff jest za duży
+ 576 - 193
flatcamTools/ToolPaint.py


+ 53 - 41
flatcamTools/ToolPanelize.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
 
-from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection
+from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection, FCComboBox
 from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber, FlatCAMExcellon
 import FlatCAMApp
 from copy import deepcopy
@@ -49,12 +49,24 @@ class Panelize(FlatCAMTool):
                         """)
         self.layout.addWidget(title_label)
 
+        self.layout.addWidget(QtWidgets.QLabel(''))
+
+        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Source Object"))
+        self.object_label.setToolTip(
+            _("Specify the type of object to be panelized\n"
+              "It can be of type: Gerber, Excellon or Geometry.\n"
+              "The selection here decide the type of objects that will be\n"
+              "in the Object combobox.")
+        )
+
+        self.layout.addWidget(self.object_label)
+
         # Form Layout
         form_layout_0 = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout_0)
 
         # Type of object to be panelized
-        self.type_obj_combo = QtWidgets.QComboBox()
+        self.type_obj_combo = FCComboBox()
         self.type_obj_combo.addItem("Gerber")
         self.type_obj_combo.addItem("Excellon")
         self.type_obj_combo.addItem("Geometry")
@@ -63,27 +75,21 @@ class Panelize(FlatCAMTool):
         self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
-        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
-        self.type_obj_combo_label.setToolTip(
-            _("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.")
-        )
-        form_layout_0.addRow(self.type_obj_combo_label, self.type_obj_combo)
+        self.type_object_label = QtWidgets.QLabel('%s:' % _("Object Type"))
+
+        form_layout_0.addRow(self.type_object_label, self.type_obj_combo)
 
         # Object to be panelized
-        self.object_combo = QtWidgets.QComboBox()
+        self.object_combo = FCComboBox()
         self.object_combo.setModel(self.app.collection)
         self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.object_combo.setCurrentIndex(1)
+        self.object_combo.is_last = True
 
-        self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
-        self.object_label.setToolTip(
+        self.object_combo.setToolTip(
             _("Object to be panelized. This means that it will\n"
               "be duplicated in an array of rows and columns.")
         )
-        form_layout_0.addRow(self.object_label, self.object_combo)
+        form_layout_0.addRow(self.object_combo)
         form_layout_0.addRow(QtWidgets.QLabel(""))
 
         # Form Layout
@@ -108,15 +114,13 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.reference_radio)
 
         # Type of Box Object to be used as an envelope for panelization
-        self.type_box_combo = QtWidgets.QComboBox()
-        self.type_box_combo.addItem("Gerber")
-        self.type_box_combo.addItem("Excellon")
-        self.type_box_combo.addItem("Geometry")
+        self.type_box_combo = FCComboBox()
+        self.type_box_combo.addItems([_("Gerber"), _("Geometry")])
 
         # we get rid of item1 ("Excellon") as it is not suitable for use as a "box" for panelizing
-        self.type_box_combo.view().setRowHidden(1, True)
+        # self.type_box_combo.view().setRowHidden(1, True)
         self.type_box_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
-        self.type_box_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
+        self.type_box_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
         self.type_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Type"))
         self.type_box_combo_label.setToolTip(
@@ -128,17 +132,16 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.type_box_combo_label, self.type_box_combo)
 
         # Box
-        self.box_combo = QtWidgets.QComboBox()
+        self.box_combo = FCComboBox()
         self.box_combo.setModel(self.app.collection)
         self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.box_combo.setCurrentIndex(1)
+        self.box_combo.is_last = True
 
-        self.box_combo_label = QtWidgets.QLabel('%s:' % _("Box Object"))
-        self.box_combo_label.setToolTip(
+        self.box_combo.setToolTip(
             _("The actual object that is used a container for the\n "
               "selected object that is to be panelized.")
         )
-        form_layout.addRow(self.box_combo_label, self.box_combo)
+        form_layout.addRow(self.box_combo)
         form_layout.addRow(QtWidgets.QLabel(""))
 
         panel_data_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Data"))
@@ -153,7 +156,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(panel_data_label)
 
         # Spacing Columns
-        self.spacing_columns = FCDoubleSpinner()
+        self.spacing_columns = FCDoubleSpinner(callback=self.confirmation_message)
         self.spacing_columns.set_range(0, 9999)
         self.spacing_columns.set_precision(4)
 
@@ -165,7 +168,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
 
         # Spacing Rows
-        self.spacing_rows = FCDoubleSpinner()
+        self.spacing_rows = FCDoubleSpinner(callback=self.confirmation_message)
         self.spacing_rows.set_range(0, 9999)
         self.spacing_rows.set_precision(4)
 
@@ -177,7 +180,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
 
         # Columns
-        self.columns = FCSpinner()
+        self.columns = FCSpinner(callback=self.confirmation_message_int)
         self.columns.set_range(0, 9999)
 
         self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
@@ -187,7 +190,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.columns_label, self.columns)
 
         # Rows
-        self.rows = FCSpinner()
+        self.rows = FCSpinner(callback=self.confirmation_message_int)
         self.rows.set_range(0, 9999)
 
         self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
@@ -220,7 +223,7 @@ class Panelize(FlatCAMTool):
         )
         form_layout.addRow(self.constrain_cb)
 
-        self.x_width_entry = FCDoubleSpinner()
+        self.x_width_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.x_width_entry.set_precision(4)
         self.x_width_entry.set_range(0, 9999)
 
@@ -231,7 +234,7 @@ class Panelize(FlatCAMTool):
         )
         form_layout.addRow(self.x_width_lbl, self.x_width_entry)
 
-        self.y_height_entry = FCDoubleSpinner()
+        self.y_height_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.y_height_entry.set_range(0, 9999)
         self.y_height_entry.set_precision(4)
 
@@ -319,7 +322,7 @@ class Panelize(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Panel. Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+Z', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+Z', **kwargs)
 
     def set_tool_ui(self):
         self.reset_fields()
@@ -358,10 +361,18 @@ class Panelize(FlatCAMTool):
             self.app.defaults["tools_panelize_panel_type"] else 'gerber'
         self.panel_type_radio.set_value(panel_type)
 
+        # run once the following so the obj_type attribute is updated in the FCComboBoxes
+        # such that the last loaded object is populated in the combo boxes
+        self.on_type_obj_index_changed()
+        self.on_type_box_index_changed()
+
     def on_type_obj_index_changed(self):
         obj_type = self.type_obj_combo.currentIndex()
         self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.object_combo.setCurrentIndex(0)
+        self.object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.type_obj_combo.get_value()]
 
         # hide the panel type for Excellons, the panel can be only of type Geometry
         if self.type_obj_combo.currentText() != 'Excellon':
@@ -376,18 +387,19 @@ class Panelize(FlatCAMTool):
         obj_type = self.type_box_combo.currentIndex()
         self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.box_combo.setCurrentIndex(0)
+        self.box_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Geometry"): "Geometry"
+        }[self.type_box_combo.get_value()]
 
     def on_reference_radio_changed(self, current_val):
         if current_val == 'object':
             self.type_box_combo.setDisabled(False)
             self.type_box_combo_label.setDisabled(False)
             self.box_combo.setDisabled(False)
-            self.box_combo_label.setDisabled(False)
         else:
             self.type_box_combo.setDisabled(True)
             self.type_box_combo_label.setDisabled(True)
             self.box_combo.setDisabled(True)
-            self.box_combo_label.setDisabled(True)
 
     def on_panelize(self):
         name = self.object_combo.currentText()
@@ -470,13 +482,13 @@ class Panelize(FlatCAMTool):
 
         if isinstance(panel_obj, FlatCAMExcellon) or isinstance(panel_obj, FlatCAMGeometry):
             # make a copy of the panelized Excellon or Geometry tools
-            copied_tools = dict()
+            copied_tools = {}
             for tt, tt_val in list(panel_obj.tools.items()):
                 copied_tools[tt] = deepcopy(tt_val)
 
         if isinstance(panel_obj, FlatCAMGerber):
             # make a copy of the panelized Gerber apertures
-            copied_apertures = dict()
+            copied_apertures = {}
             for tt, tt_val in list(panel_obj.apertures.items()):
                 copied_apertures[tt] = deepcopy(tt_val)
 
@@ -492,7 +504,7 @@ class Panelize(FlatCAMTool):
                     obj_fin.solid_geometry = []
 
                     for option in panel_obj.options:
-                        if option is not 'name':
+                        if option != 'name':
                             try:
                                 obj_fin.options[option] = panel_obj.options[option]
                             except KeyError:
@@ -574,7 +586,7 @@ class Panelize(FlatCAMTool):
 
                     def translate_recursion(geom):
                         if type(geom) == list:
-                            geoms = list()
+                            geoms = []
                             for local_geom in geom:
                                 res_geo = translate_recursion(local_geom)
                                 try:
@@ -597,7 +609,7 @@ class Panelize(FlatCAMTool):
                     elif isinstance(panel_obj, FlatCAMGerber):
                         obj_fin.apertures = copied_apertures
                         for ap in obj_fin.apertures:
-                            obj_fin.apertures[ap]['geometry'] = list()
+                            obj_fin.apertures[ap]['geometry'] = []
 
                     # find the number of polygons in the source solid_geometry
                     geo_len = 0
@@ -733,7 +745,7 @@ class Panelize(FlatCAMTool):
                                                 # graceful abort requested by the user
                                                 raise FlatCAMApp.GracefulException
 
-                                            new_el = dict()
+                                            new_el = {}
                                             if 'solid' in el:
                                                 geo_aper = translate_recursion(el['solid'])
                                                 new_el['solid'] = geo_aper

+ 4 - 4
flatcamTools/ToolPcbWizard.py

@@ -90,7 +90,7 @@ class PcbWizard(FlatCAMTool):
         self.layout.addLayout(form_layout1)
 
         # Integral part of the coordinates
-        self.int_entry = FCSpinner()
+        self.int_entry = FCSpinner(callback=self.confirmation_message_int)
         self.int_entry.set_range(1, 10)
         self.int_label = QtWidgets.QLabel('%s:' % _("Int. digits"))
         self.int_label.setToolTip(
@@ -99,7 +99,7 @@ class PcbWizard(FlatCAMTool):
         form_layout1.addRow(self.int_label, self.int_entry)
 
         # Fractional part of the coordinates
-        self.frac_entry = FCSpinner()
+        self.frac_entry = FCSpinner(callback=self.confirmation_message_int)
         self.frac_entry.set_range(1, 10)
         self.frac_label = QtWidgets.QLabel('%s:' % _("Frac. digits"))
         self.frac_label.setToolTip(
@@ -298,7 +298,7 @@ class PcbWizard(FlatCAMTool):
         filename = str(filename)
 
         if filename == "":
-            self.app.inform.emit(_("Open cancelled."))
+            self.app.inform.emit(_("Cancelled."))
         else:
             self.app.worker_task.emit({'fcn': self.load_excellon, 'params': [filename]})
 
@@ -321,7 +321,7 @@ class PcbWizard(FlatCAMTool):
         filename = str(filename)
 
         if filename == "":
-            self.app.inform.emit(_("Open cancelled."))
+            self.app.inform.emit(_("Cancelled."))
         else:
             self.app.worker_task.emit({'fcn': self.load_inf, 'params': [filename]})
 

+ 115 - 109
flatcamTools/ToolProperties.py

@@ -7,6 +7,7 @@
 
 from PyQt5 import QtGui, QtCore, QtWidgets
 from FlatCAMTool import FlatCAMTool
+from flatcamGUI.GUIElements import FCTree
 
 from shapely.geometry import MultiPolygon, Polygon
 from shapely.ops import cascaded_union
@@ -64,11 +65,7 @@ class Properties(FlatCAMTool):
 
         self.properties_box.addLayout(self.vlay)
 
-        self.treeWidget = QtWidgets.QTreeWidget()
-        self.treeWidget.setColumnCount(2)
-        self.treeWidget.setHeaderHidden(True)
-        self.treeWidget.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
-        self.treeWidget.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Expanding)
+        self.treeWidget = FCTree(columns=2)
 
         self.vlay.addWidget(self.treeWidget)
         self.vlay.setStretch(0, 0)
@@ -116,7 +113,7 @@ class Properties(FlatCAMTool):
     def properties(self):
         obj_list = self.app.collection.get_selected()
         if not obj_list:
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Properties Tool was not displayed. No object selected."))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
             self.app.ui.notebook.setTabText(2, _("Tools"))
             self.properties_frame.hide()
             self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
@@ -132,6 +129,10 @@ class Properties(FlatCAMTool):
         for obj in obj_list:
             self.addItems(obj)
             self.app.inform.emit('[success] %s' % _("Object Properties are displayed."))
+
+        # make sure that the FCTree widget columns are resized to content
+        self.treeWidget.resize_sig.emit()
+
         self.app.ui.notebook.setTabText(2, _("Properties Tool"))
 
     def addItems(self, obj):
@@ -146,36 +147,50 @@ class Properties(FlatCAMTool):
         font.setBold(True)
 
         # main Items categories
-        obj_type = self.addParent(parent, _('TYPE'), expanded=True, color=QtGui.QColor("#000000"), font=font)
-        obj_name = self.addParent(parent, _('NAME'), expanded=True, color=QtGui.QColor("#000000"), font=font)
-        dims = self.addParent(parent, _('Dimensions'), expanded=True, color=QtGui.QColor("#000000"), font=font)
-        units = self.addParent(parent, _('Units'), expanded=True, color=QtGui.QColor("#000000"), font=font)
-        options = self.addParent(parent, _('Options'), color=QtGui.QColor("#000000"), font=font)
+        obj_type = self.treeWidget.addParent(parent, _('TYPE'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+        obj_name = self.treeWidget.addParent(parent, _('NAME'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+        dims = self.treeWidget.addParent(
+            parent, _('Dimensions'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+        units = self.treeWidget.addParent(parent, _('Units'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+        options = self.treeWidget.addParent(parent, _('Options'), color=QtGui.QColor("#000000"), font=font)
 
         if obj.kind.lower() == 'gerber':
-            apertures = self.addParent(parent, _('Apertures'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+            apertures = self.treeWidget.addParent(
+                parent, _('Apertures'), expanded=True, color=QtGui.QColor("#000000"), font=font)
         else:
-            tools = self.addParent(parent, _('Tools'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+            tools = self.treeWidget.addParent(
+                parent, _('Tools'), expanded=True, color=QtGui.QColor("#000000"), font=font)
 
         if obj.kind.lower() == 'excellon':
-            drills = self.addParent(parent, _('Drills'), expanded=True, color=QtGui.QColor("#000000"), font=font)
-            slots = self.addParent(parent, _('Slots'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+            drills = self.treeWidget.addParent(
+                parent, _('Drills'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+            slots = self.treeWidget.addParent(
+                parent, _('Slots'), expanded=True, color=QtGui.QColor("#000000"), font=font)
 
         if obj.kind.lower() == 'cncjob':
-            others = self.addParent(parent, _('Others'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+            others = self.treeWidget.addParent(
+                parent, _('Others'), expanded=True, color=QtGui.QColor("#000000"), font=font)
 
-        separator = self.addParent(parent, '')
+        separator = self.treeWidget.addParent(parent, '')
 
-        self.addChild(obj_type, ['%s:' % _('Object Type'), ('%s' % (obj.kind.upper()))], True, font=font, font_items=1)
+        self.treeWidget.addChild(
+            obj_type, ['%s:' % _('Object Type'), ('%s' % (obj.kind.upper()))], True, font=font, font_items=1)
         try:
-            self.addChild(obj_type,
-                          ['%s:' % _('Geo Type'),
-                           ('%s' % ({False: _("Single-Geo"), True: _("Multi-Geo")}[obj.multigeo]))],
-                          True)
+            self.treeWidget.addChild(obj_type,
+                                     [
+                                         '%s:' % _('Geo Type'),
+                                         ('%s' % (
+                                             {
+                                                 False: _("Single-Geo"),
+                                                 True: _("Multi-Geo")
+                                             }[obj.multigeo])
+                                          )
+                                     ],
+                                     True)
         except Exception as e:
             log.debug("Properties.addItems() --> %s" % str(e))
 
-        self.addChild(obj_name, [obj.options['name']])
+        self.treeWidget.addChild(obj_name, [obj.options['name']])
 
         def job_thread(obj_prop):
             proc = self.app.proc_container.new(_("Calculating dimensions ... Please wait."))
@@ -193,8 +208,8 @@ class Properties(FlatCAMTool):
 
                     length = abs(xmax - xmin)
                     width = abs(ymax - ymin)
-                except Exception as e:
-                    log.debug("PropertiesTool.addItems() -> calculate dimensions --> %s" % str(e))
+                except Exception as ee:
+                    log.debug("PropertiesTool.addItems() -> calculate dimensions --> %s" % str(ee))
 
                 # calculate box area
                 if self.app.defaults['units'].lower() == 'mm':
@@ -266,7 +281,7 @@ class Properties(FlatCAMTool):
                         # calculate copper area
 
                         # create a complete solid_geometry from the tools
-                        geo_tools = list()
+                        geo_tools = []
                         for tool_k in obj_prop.tools:
                             if 'solid_geometry' in obj_prop.tools[tool_k]:
                                 for geo_el in obj_prop.tools[tool_k]['solid_geometry']:
@@ -278,24 +293,27 @@ class Properties(FlatCAMTool):
                         except TypeError:
                             copper_area += geo_tools.area
                         copper_area /= 100
-                except Exception as e:
-                    log.debug("Properties.addItems() --> %s" % str(e))
+                except Exception as err:
+                    log.debug("Properties.addItems() --> %s" % str(err))
 
             area_chull = 0.0
             if obj_prop.kind.lower() != 'cncjob':
                 # calculate and add convex hull area
                 if geo:
-                    if isinstance(geo, MultiPolygon):
-                        env_obj = geo.convex_hull
-                    elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
-                            (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
-                        env_obj = cascaded_union(obj_prop.solid_geometry)
-                        env_obj = env_obj.convex_hull
-                    else:
-                        env_obj = cascaded_union(obj_prop.solid_geometry)
-                        env_obj = env_obj.convex_hull
+                    if isinstance(geo, list) and geo[0] is not None:
+                        if isinstance(geo, MultiPolygon):
+                            env_obj = geo.convex_hull
+                        elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
+                                (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
+                            env_obj = cascaded_union(geo)
+                            env_obj = env_obj.convex_hull
+                        else:
+                            env_obj = cascaded_union(geo)
+                            env_obj = env_obj.convex_hull
 
-                    area_chull = env_obj.area
+                        area_chull = env_obj.area
+                    else:
+                        area_chull = 0
                 else:
                     try:
                         area_chull = []
@@ -303,9 +321,9 @@ class Properties(FlatCAMTool):
                             area_el = cascaded_union(obj_prop.tools[tool_k]['solid_geometry']).convex_hull
                             area_chull.append(area_el.area)
                         area_chull = max(area_chull)
-                    except Exception as e:
+                    except Exception as er:
                         area_chull = None
-                        log.debug("Properties.addItems() --> %s" % str(e))
+                        log.debug("Properties.addItems() --> %s" % str(er))
 
             if self.app.defaults['units'].lower() == 'mm' and area_chull:
                 area_chull = area_chull / 100
@@ -319,7 +337,7 @@ class Properties(FlatCAMTool):
 
         # Units items
         f_unit = {'in': _('Inch'), 'mm': _('Metric')}[str(self.app.defaults['units'].lower())]
-        self.addChild(units, ['FlatCAM units:', f_unit], True)
+        self.treeWidget.addChild(units, ['FlatCAM units:', f_unit], True)
 
         o_unit = {
             'in': _('Inch'),
@@ -327,17 +345,17 @@ class Properties(FlatCAMTool):
             'inch': _('Inch'),
             'metric': _('Metric')
         }[str(obj.units_found.lower())]
-        self.addChild(units, ['Object units:', o_unit], True)
+        self.treeWidget.addChild(units, ['Object units:', o_unit], True)
 
         # Options items
         for option in obj.options:
-            if option is 'name':
+            if option == 'name':
                 continue
-            self.addChild(options, [str(option), str(obj.options[option])], True)
+            self.treeWidget.addChild(options, [str(option), str(obj.options[option])], True)
 
         # Items that depend on the object type
         if obj.kind.lower() == 'gerber':
-            temp_ap = dict()
+            temp_ap = {}
             for ap in obj.apertures:
                 temp_ap.clear()
                 temp_ap = deepcopy(obj.apertures[ap])
@@ -363,15 +381,17 @@ class Properties(FlatCAMTool):
                 temp_ap['Follow_Geo'] = '%s LineStrings' % str(follow_nr)
                 temp_ap['Clear_Geo'] = '%s Polygons' % str(clear_nr)
 
-                apid = self.addParent(apertures, str(ap), expanded=False, color=QtGui.QColor("#000000"), font=font)
+                apid = self.treeWidget.addParent(
+                    apertures, str(ap), expanded=False, color=QtGui.QColor("#000000"), font=font)
                 for key in temp_ap:
-                    self.addChild(apid, [str(key), str(temp_ap[key])], True)
+                    self.treeWidget.addChild(apid, [str(key), str(temp_ap[key])], True)
         elif obj.kind.lower() == 'excellon':
             tot_drill_cnt = 0
             tot_slot_cnt = 0
 
             for tool, value in obj.tools.items():
-                toolid = self.addParent(tools, str(tool), expanded=False, color=QtGui.QColor("#000000"), font=font)
+                toolid = self.treeWidget.addParent(
+                    tools, str(tool), expanded=False, color=QtGui.QColor("#000000"), font=font)
 
                 drill_cnt = 0  # variable to store the nr of drills per tool
                 slot_cnt = 0  # variable to store the nr of slots per tool
@@ -390,7 +410,7 @@ class Properties(FlatCAMTool):
 
                 tot_slot_cnt += slot_cnt
 
-                self.addChild(
+                self.treeWidget.addChild(
                     toolid,
                     [
                         _('Diameter'),
@@ -398,52 +418,59 @@ class Properties(FlatCAMTool):
                     ],
                     True
                 )
-                self.addChild(toolid, [_('Drills number'), str(drill_cnt)], True)
-                self.addChild(toolid, [_('Slots number'), str(slot_cnt)], True)
+                self.treeWidget.addChild(toolid, [_('Drills number'), str(drill_cnt)], True)
+                self.treeWidget.addChild(toolid, [_('Slots number'), str(slot_cnt)], True)
 
-            self.addChild(drills, [_('Drills total number:'), str(tot_drill_cnt)], True)
-            self.addChild(slots, [_('Slots total number:'), str(tot_slot_cnt)], True)
+            self.treeWidget.addChild(drills, [_('Drills total number:'), str(tot_drill_cnt)], True)
+            self.treeWidget.addChild(slots, [_('Slots total number:'), str(tot_slot_cnt)], True)
         elif obj.kind.lower() == 'geometry':
             for tool, value in obj.tools.items():
-                geo_tool = self.addParent(tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
+                geo_tool = self.treeWidget.addParent(
+                    tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
                 for k, v in value.items():
                     if k == 'solid_geometry':
-                        printed_value = _('Present') if v else _('None')
-                        self.addChild(geo_tool, [str(k), printed_value], True)
+                        # printed_value = _('Present') if v else _('None')
+                        try:
+                            printed_value = str(len(v))
+                        except (TypeError, AttributeError):
+                            printed_value = '1'
+                        self.treeWidget.addChild(geo_tool, [str(k), printed_value], True)
                     elif k == 'data':
-                        tool_data = self.addParent(geo_tool, str(k).capitalize(),
-                                                   color=QtGui.QColor("#000000"), font=font)
+                        tool_data = self.treeWidget.addParent(
+                            geo_tool, str(k).capitalize(), color=QtGui.QColor("#000000"), font=font)
                         for data_k, data_v in v.items():
-                            self.addChild(tool_data, [str(data_k), str(data_v)], True)
+                            self.treeWidget.addChild(tool_data, [str(data_k), str(data_v)], True)
                     else:
-                        self.addChild(geo_tool, [str(k), str(v)], True)
+                        self.treeWidget.addChild(geo_tool, [str(k), str(v)], True)
         elif obj.kind.lower() == 'cncjob':
             # for cncjob objects made from gerber or geometry
             for tool, value in obj.cnc_tools.items():
-                geo_tool = self.addParent(tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
+                geo_tool = self.treeWidget.addParent(
+                    tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
                 for k, v in value.items():
                     if k == 'solid_geometry':
                         printed_value = _('Present') if v else _('None')
-                        self.addChild(geo_tool, [_("Solid Geometry"), printed_value], True)
+                        self.treeWidget.addChild(geo_tool, [_("Solid Geometry"), printed_value], True)
                     elif k == 'gcode':
                         printed_value = _('Present') if v != '' else _('None')
-                        self.addChild(geo_tool, [_("GCode Text"), printed_value], True)
+                        self.treeWidget.addChild(geo_tool, [_("GCode Text"), printed_value], True)
                     elif k == 'gcode_parsed':
                         printed_value = _('Present') if v else _('None')
-                        self.addChild(geo_tool, [_("GCode Geometry"), printed_value], True)
+                        self.treeWidget.addChild(geo_tool, [_("GCode Geometry"), printed_value], True)
                     elif k == 'data':
-                        tool_data = self.addParent(geo_tool, _("Data"), color=QtGui.QColor("#000000"), font=font)
+                        tool_data = self.treeWidget.addParent(
+                            geo_tool, _("Data"), color=QtGui.QColor("#000000"), font=font)
                         for data_k, data_v in v.items():
-                            self.addChild(tool_data, [str(data_k).capitalize(), str(data_v)], True)
+                            self.treeWidget.addChild(tool_data, [str(data_k).capitalize(), str(data_v)], True)
                     else:
-                        self.addChild(geo_tool, [str(k), str(v)], True)
+                        self.treeWidget.addChild(geo_tool, [str(k), str(v)], True)
 
             # for cncjob objects made from excellon
             for tool_dia, value in obj.exc_cnc_tools.items():
-                exc_tool = self.addParent(
+                exc_tool = self.treeWidget.addParent(
                     tools, str(value['tool']), expanded=False, color=QtGui.QColor("#000000"), font=font
                 )
-                self.addChild(
+                self.treeWidget.addChild(
                     exc_tool,
                     [
                         _('Diameter'),
@@ -454,15 +481,15 @@ class Properties(FlatCAMTool):
                 for k, v in value.items():
                     if k == 'solid_geometry':
                         printed_value = _('Present') if v else _('None')
-                        self.addChild(exc_tool, [_("Solid Geometry"), printed_value], True)
+                        self.treeWidget.addChild(exc_tool, [_("Solid Geometry"), printed_value], True)
                     elif k == 'nr_drills':
-                        self.addChild(exc_tool, [_("Drills number"), str(v)], True)
+                        self.treeWidget.addChild(exc_tool, [_("Drills number"), str(v)], True)
                     elif k == 'nr_slots':
-                        self.addChild(exc_tool, [_("Slots number"), str(v)], True)
+                        self.treeWidget.addChild(exc_tool, [_("Slots number"), str(v)], True)
                     else:
                         pass
 
-                self.addChild(
+                self.treeWidget.addChild(
                     exc_tool,
                     [
                         _("Depth of Cut"),
@@ -474,7 +501,7 @@ class Properties(FlatCAMTool):
                     ],
                     True
                 )
-                self.addChild(
+                self.treeWidget.addChild(
                     exc_tool,
                     [
                         _("Clearance Height"),
@@ -486,7 +513,7 @@ class Properties(FlatCAMTool):
                     ],
                     True
                 )
-                self.addChild(
+                self.treeWidget.addChild(
                     exc_tool,
                     [
                         _("Feedrate"),
@@ -506,14 +533,14 @@ class Properties(FlatCAMTool):
                 r_time *= 60
                 units_lbl = 'sec'
             r_time = math.ceil(float(r_time))
-            self.addChild(
+            self.treeWidget.addChild(
                 others,
                 [
                     '%s:' % _('Routing time'),
                     '%.*f %s' % (self.decimals, r_time, units_lbl)],
                 True
             )
-            self.addChild(
+            self.treeWidget.addChild(
                 others,
                 [
                     '%s:' % _('Travelled distance'),
@@ -522,40 +549,17 @@ class Properties(FlatCAMTool):
                 True
             )
 
-        self.addChild(separator, [''])
-
-    def addParent(self, parent, title, expanded=False, color=None, font=None):
-        item = QtWidgets.QTreeWidgetItem(parent, [title])
-        item.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.ShowIndicator)
-        item.setExpanded(expanded)
-        if color is not None:
-            # item.setTextColor(0, color) # PyQt4
-            item.setForeground(0, QtGui.QBrush(color))
-        if font is not None:
-            item.setFont(0, font)
-        return item
-
-    def addChild(self, parent, title, column1=None, font=None, font_items=None):
-        item = QtWidgets.QTreeWidgetItem(parent)
-        item.setText(0, str(title[0]))
-        if column1 is not None:
-            item.setText(1, str(title[1]))
-        if font and font_items:
-            try:
-                for fi in font_items:
-                    item.setFont(fi, font)
-            except TypeError:
-                item.setFont(font_items, font)
+        self.treeWidget.addChild(separator, [''])
 
     def show_area_chull(self, area, length, width, chull_area, copper_area, location):
 
         # add dimensions
-        self.addChild(
+        self.treeWidget.addChild(
             location,
             ['%s:' % _('Length'), '%.*f %s' % (self.decimals, length, self.app.defaults['units'].lower())],
             True
         )
-        self.addChild(
+        self.treeWidget.addChild(
             location,
             ['%s:' % _('Width'), '%.*f %s' % (self.decimals, width, self.app.defaults['units'].lower())],
             True
@@ -563,16 +567,16 @@ class Properties(FlatCAMTool):
 
         # add box area
         if self.app.defaults['units'].lower() == 'mm':
-            self.addChild(location, ['%s:' % _('Box Area'), '%.*f %s' % (self.decimals, area, 'cm2')], True)
-            self.addChild(
+            self.treeWidget.addChild(location, ['%s:' % _('Box Area'), '%.*f %s' % (self.decimals, area, 'cm2')], True)
+            self.treeWidget.addChild(
                 location,
                 ['%s:' % _('Convex_Hull Area'), '%.*f %s' % (self.decimals, chull_area, 'cm2')],
                 True
             )
 
         else:
-            self.addChild(location, ['%s:' % _('Box Area'), '%.*f %s' % (self.decimals, area, 'in2')], True)
-            self.addChild(
+            self.treeWidget.addChild(location, ['%s:' % _('Box Area'), '%.*f %s' % (self.decimals, area, 'in2')], True)
+            self.treeWidget.addChild(
                 location,
                 ['%s:' % _('Convex_Hull Area'), '%.*f %s' % (self.decimals, chull_area, 'in2')],
                 True
@@ -580,8 +584,10 @@ class Properties(FlatCAMTool):
 
         # add copper area
         if self.app.defaults['units'].lower() == 'mm':
-            self.addChild(location, ['%s:' % _('Copper Area'), '%.*f %s' % (self.decimals, copper_area, 'cm2')], True)
+            self.treeWidget.addChild(
+                location, ['%s:' % _('Copper Area'), '%.*f %s' % (self.decimals, copper_area, 'cm2')], True)
         else:
-            self.addChild(location, ['%s:' % _('Copper Area'), '%.*f %s' % (self.decimals, copper_area, 'in2')], True)
+            self.treeWidget.addChild(
+                location, ['%s:' % _('Copper Area'), '%.*f %s' % (self.decimals, copper_area, 'in2')], True)
 
 # end of file

+ 994 - 0
flatcamTools/ToolPunchGerber.py

@@ -0,0 +1,994 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# File Author: Marius Adrian Stanciu (c)                   #
+# Date: 1/24/2020                                          #
+# MIT Licence                                              #
+# ##########################################################
+
+from PyQt5 import QtCore, QtWidgets
+
+from FlatCAMTool import FlatCAMTool
+from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCComboBox
+
+from copy import deepcopy
+import logging
+from shapely.geometry import MultiPolygon, Point
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+log = logging.getLogger('base')
+
+
+class ToolPunchGerber(FlatCAMTool):
+
+    toolName = _("Punch Gerber")
+
+    def __init__(self, app):
+        FlatCAMTool.__init__(self, app)
+
+        self.decimals = self.app.decimals
+
+        # Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                        QLabel
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(title_label)
+
+        # Punch Drill holes
+        self.layout.addWidget(QtWidgets.QLabel(""))
+
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 1)
+        grid_lay.setColumnStretch(1, 0)
+
+        # ## Gerber Object
+        self.gerber_object_combo = FCComboBox()
+        self.gerber_object_combo.setModel(self.app.collection)
+        self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.gerber_object_combo.is_last = True
+        self.gerber_object_combo.obj_type = "Gerber"
+
+        self.grb_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.grb_label.setToolTip('%s.' % _("Gerber into which to punch holes"))
+
+        grid_lay.addWidget(self.grb_label, 0, 0, 1, 2)
+        grid_lay.addWidget(self.gerber_object_combo, 1, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 2, 0, 1, 2)
+
+        self.padt_label = QtWidgets.QLabel("<b>%s</b>" % _("Processed Pads Type"))
+        self.padt_label.setToolTip(
+            _("The type of pads shape to be processed.\n"
+              "If the PCB has many SMD pads with rectangular pads,\n"
+              "disable the Rectangular aperture.")
+        )
+
+        grid_lay.addWidget(self.padt_label, 3, 0, 1, 2)
+
+        # Select all
+        self.select_all_cb = FCCheckBox('%s' % _("ALL"))
+        grid_lay.addWidget(self.select_all_cb)
+
+        # Circular Aperture Selection
+        self.circular_cb = FCCheckBox('%s' % _("Circular"))
+        self.circular_cb.setToolTip(
+            _("Process Circular Pads.")
+        )
+
+        grid_lay.addWidget(self.circular_cb, 5, 0, 1, 2)
+
+        # Oblong Aperture Selection
+        self.oblong_cb = FCCheckBox('%s' % _("Oblong"))
+        self.oblong_cb.setToolTip(
+            _("Process Oblong Pads.")
+        )
+
+        grid_lay.addWidget(self.oblong_cb, 6, 0, 1, 2)
+
+        # Square Aperture Selection
+        self.square_cb = FCCheckBox('%s' % _("Square"))
+        self.square_cb.setToolTip(
+            _("Process Square Pads.")
+        )
+
+        grid_lay.addWidget(self.square_cb, 7, 0, 1, 2)
+
+        # Rectangular Aperture Selection
+        self.rectangular_cb = FCCheckBox('%s' % _("Rectangular"))
+        self.rectangular_cb.setToolTip(
+            _("Process Rectangular Pads.")
+        )
+
+        grid_lay.addWidget(self.rectangular_cb, 8, 0, 1, 2)
+
+        # Others type of Apertures Selection
+        self.other_cb = FCCheckBox('%s' % _("Others"))
+        self.other_cb.setToolTip(
+            _("Process pads not in the categories above.")
+        )
+
+        grid_lay.addWidget(self.other_cb, 9, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 10, 0, 1, 2)
+
+        # Grid Layout
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+
+        self.method_label = QtWidgets.QLabel('<b>%s:</b>' % _("Method"))
+        self.method_label.setToolTip(
+            _("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")
+        )
+        self.method_punch = RadioSet(
+            [
+                {'label': _('Excellon'), 'value': 'exc'},
+                {'label': _("Fixed Diameter"), 'value': 'fixed'},
+                {'label': _("Fixed Annular Ring"), 'value': 'ring'},
+                {'label': _("Proportional"), 'value': 'prop'}
+            ],
+            orientation='vertical',
+            stretch=False)
+        grid0.addWidget(self.method_label, 0, 0, 1, 2)
+        grid0.addWidget(self.method_punch, 1, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 2, 0, 1, 2)
+
+        self.exc_label = QtWidgets.QLabel('<b>%s</b>' % _("Excellon"))
+        self.exc_label.setToolTip(
+            _("Remove the geometry of Excellon from the Gerber to create the holes in pads.")
+        )
+
+        self.exc_combo = FCComboBox()
+        self.exc_combo.setModel(self.app.collection)
+        self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
+        self.exc_combo.is_last = True
+        self.exc_combo.obj_type = "Excellon"
+
+        grid0.addWidget(self.exc_label, 3, 0, 1, 2)
+        grid0.addWidget(self.exc_combo, 4, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 5, 0, 1, 2)
+
+        # Fixed Dia
+        self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
+        grid0.addWidget(self.fixed_label, 6, 0, 1, 2)
+
+        # Diameter value
+        self.dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.dia_entry.set_precision(self.decimals)
+        self.dia_entry.set_range(0.0000, 9999.9999)
+
+        self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
+        self.dia_label.setToolTip(
+            _("Fixed hole diameter.")
+        )
+
+        grid0.addWidget(self.dia_label, 8, 0)
+        grid0.addWidget(self.dia_entry, 8, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 9, 0, 1, 2)
+
+        self.ring_frame = QtWidgets.QFrame()
+        self.ring_frame.setContentsMargins(0, 0, 0, 0)
+        grid0.addWidget(self.ring_frame, 10, 0, 1, 2)
+
+        self.ring_box = QtWidgets.QVBoxLayout()
+        self.ring_box.setContentsMargins(0, 0, 0, 0)
+        self.ring_frame.setLayout(self.ring_box)
+
+        # Annular Ring value
+        self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
+        self.ring_label.setToolTip(
+            _("The size of annular ring.\n"
+              "The copper sliver between the hole exterior\n"
+              "and the margin of the copper pad.")
+        )
+        self.ring_box.addWidget(self.ring_label)
+
+        # ## Grid Layout
+        self.grid1 = QtWidgets.QGridLayout()
+        self.grid1.setColumnStretch(0, 0)
+        self.grid1.setColumnStretch(1, 1)
+        self.ring_box.addLayout(self.grid1)
+
+        # Circular Annular Ring Value
+        self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
+        self.circular_ring_label.setToolTip(
+            _("The size of annular ring for circular pads.")
+        )
+
+        self.circular_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.circular_ring_entry.set_precision(self.decimals)
+        self.circular_ring_entry.set_range(0.0000, 9999.9999)
+
+        self.grid1.addWidget(self.circular_ring_label, 3, 0)
+        self.grid1.addWidget(self.circular_ring_entry, 3, 1)
+
+        # Oblong Annular Ring Value
+        self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
+        self.oblong_ring_label.setToolTip(
+            _("The size of annular ring for oblong pads.")
+        )
+
+        self.oblong_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.oblong_ring_entry.set_precision(self.decimals)
+        self.oblong_ring_entry.set_range(0.0000, 9999.9999)
+
+        self.grid1.addWidget(self.oblong_ring_label, 4, 0)
+        self.grid1.addWidget(self.oblong_ring_entry, 4, 1)
+
+        # Square Annular Ring Value
+        self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
+        self.square_ring_label.setToolTip(
+            _("The size of annular ring for square pads.")
+        )
+
+        self.square_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.square_ring_entry.set_precision(self.decimals)
+        self.square_ring_entry.set_range(0.0000, 9999.9999)
+
+        self.grid1.addWidget(self.square_ring_label, 5, 0)
+        self.grid1.addWidget(self.square_ring_entry, 5, 1)
+
+        # Rectangular Annular Ring Value
+        self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
+        self.rectangular_ring_label.setToolTip(
+            _("The size of annular ring for rectangular pads.")
+        )
+
+        self.rectangular_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.rectangular_ring_entry.set_precision(self.decimals)
+        self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
+
+        self.grid1.addWidget(self.rectangular_ring_label, 6, 0)
+        self.grid1.addWidget(self.rectangular_ring_entry, 6, 1)
+
+        # Others Annular Ring Value
+        self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
+        self.other_ring_label.setToolTip(
+            _("The size of annular ring for other pads.")
+        )
+
+        self.other_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.other_ring_entry.set_precision(self.decimals)
+        self.other_ring_entry.set_range(0.0000, 9999.9999)
+
+        self.grid1.addWidget(self.other_ring_label, 7, 0)
+        self.grid1.addWidget(self.other_ring_entry, 7, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 11, 0, 1, 2)
+
+        # Proportional value
+        self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
+        grid0.addWidget(self.prop_label, 12, 0, 1, 2)
+
+        # Diameter value
+        self.factor_entry = FCDoubleSpinner(callback=self.confirmation_message, suffix='%')
+        self.factor_entry.set_precision(self.decimals)
+        self.factor_entry.set_range(0.0000, 100.0000)
+        self.factor_entry.setSingleStep(0.1)
+
+        self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
+        self.factor_label.setToolTip(
+            _("Proportional Diameter.\n"
+              "The hole diameter will be a fraction of the pad size.")
+        )
+
+        grid0.addWidget(self.factor_label, 13, 0)
+        grid0.addWidget(self.factor_entry, 13, 1)
+
+        separator_line3 = QtWidgets.QFrame()
+        separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line3, 14, 0, 1, 2)
+
+        # Buttons
+        self.punch_object_button = QtWidgets.QPushButton(_("Punch Gerber"))
+        self.punch_object_button.setToolTip(
+            _("Create a Gerber object from the selected object, within\n"
+              "the specified box.")
+        )
+        self.punch_object_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.punch_object_button)
+
+        self.layout.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.reset_button)
+
+        self.units = self.app.defaults['units']
+
+        # self.cb_items = [
+        #     self.grid1.itemAt(w).widget() for w in range(self.grid1.count())
+        #     if isinstance(self.grid1.itemAt(w).widget(), FCCheckBox)
+        # ]
+
+        self.circular_ring_entry.setEnabled(False)
+        self.oblong_ring_entry.setEnabled(False)
+        self.square_ring_entry.setEnabled(False)
+        self.rectangular_ring_entry.setEnabled(False)
+        self.other_ring_entry.setEnabled(False)
+
+        self.dia_entry.setDisabled(True)
+        self.dia_label.setDisabled(True)
+        self.factor_label.setDisabled(True)
+        self.factor_entry.setDisabled(True)
+
+        # ## Signals
+        self.method_punch.activated_custom.connect(self.on_method)
+        self.reset_button.clicked.connect(self.set_tool_ui)
+        self.punch_object_button.clicked.connect(self.on_generate_object)
+
+        self.circular_cb.stateChanged.connect(
+            lambda state:
+                self.circular_ring_entry.setDisabled(False) if state else self.circular_ring_entry.setDisabled(True)
+        )
+
+        self.oblong_cb.stateChanged.connect(
+            lambda state:
+            self.oblong_ring_entry.setDisabled(False) if state else self.oblong_ring_entry.setDisabled(True)
+        )
+
+        self.square_cb.stateChanged.connect(
+            lambda state:
+            self.square_ring_entry.setDisabled(False) if state else self.square_ring_entry.setDisabled(True)
+        )
+
+        self.rectangular_cb.stateChanged.connect(
+            lambda state:
+            self.rectangular_ring_entry.setDisabled(False) if state else self.rectangular_ring_entry.setDisabled(True)
+        )
+
+        self.other_cb.stateChanged.connect(
+            lambda state:
+            self.other_ring_entry.setDisabled(False) if state else self.other_ring_entry.setDisabled(True)
+        )
+
+    def run(self, toggle=True):
+        self.app.report_usage("ToolPunchGerber()")
+
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+
+        FlatCAMTool.run(self)
+
+        self.set_tool_ui()
+
+        self.app.ui.notebook.setTabText(2, _("Punch Tool"))
+
+    def install(self, icon=None, separator=None, **kwargs):
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+H', **kwargs)
+
+    def set_tool_ui(self):
+        self.reset_fields()
+
+        self.ui_connect()
+        self.method_punch.set_value(self.app.defaults["tools_punch_hole_type"])
+        self.select_all_cb.set_value(False)
+
+        self.dia_entry.set_value(float(self.app.defaults["tools_punch_hole_fixed_dia"]))
+
+        self.circular_ring_entry.set_value(float(self.app.defaults["tools_punch_circular_ring"]))
+        self.oblong_ring_entry.set_value(float(self.app.defaults["tools_punch_oblong_ring"]))
+        self.square_ring_entry.set_value(float(self.app.defaults["tools_punch_square_ring"]))
+        self.rectangular_ring_entry.set_value(float(self.app.defaults["tools_punch_rectangular_ring"]))
+        self.other_ring_entry.set_value(float(self.app.defaults["tools_punch_others_ring"]))
+
+        self.circular_cb.set_value(self.app.defaults["tools_punch_circular"])
+        self.oblong_cb.set_value(self.app.defaults["tools_punch_oblong"])
+        self.square_cb.set_value(self.app.defaults["tools_punch_square"])
+        self.rectangular_cb.set_value(self.app.defaults["tools_punch_rectangular"])
+        self.other_cb.set_value(self.app.defaults["tools_punch_others"])
+
+        self.factor_entry.set_value(float(self.app.defaults["tools_punch_hole_prop_factor"]))
+
+    def on_select_all(self, state):
+        self.ui_disconnect()
+        if state:
+            self.circular_cb.setChecked(True)
+            self.oblong_cb.setChecked(True)
+            self.square_cb.setChecked(True)
+            self.rectangular_cb.setChecked(True)
+            self.other_cb.setChecked(True)
+        else:
+            self.circular_cb.setChecked(False)
+            self.oblong_cb.setChecked(False)
+            self.square_cb.setChecked(False)
+            self.rectangular_cb.setChecked(False)
+            self.other_cb.setChecked(False)
+        self.ui_connect()
+
+    def on_method(self, val):
+        self.exc_label.setEnabled(False)
+        self.exc_combo.setEnabled(False)
+        self.fixed_label.setEnabled(False)
+        self.dia_label.setEnabled(False)
+        self.dia_entry.setEnabled(False)
+        self.ring_frame.setEnabled(False)
+        self.prop_label.setEnabled(False)
+        self.factor_label.setEnabled(False)
+        self.factor_entry.setEnabled(False)
+
+        if val == 'exc':
+            self.exc_label.setEnabled(True)
+            self.exc_combo.setEnabled(True)
+        elif val == 'fixed':
+            self.fixed_label.setEnabled(True)
+            self.dia_label.setEnabled(True)
+            self.dia_entry.setEnabled(True)
+        elif val == 'ring':
+            self.ring_frame.setEnabled(True)
+        elif val == 'prop':
+            self.prop_label.setEnabled(True)
+            self.factor_label.setEnabled(True)
+            self.factor_entry.setEnabled(True)
+
+    def ui_connect(self):
+        self.select_all_cb.stateChanged.connect(self.on_select_all)
+
+    def ui_disconnect(self):
+        try:
+            self.select_all_cb.stateChanged.disconnect()
+        except (AttributeError, TypeError):
+            pass
+
+    def on_generate_object(self):
+
+        # get the Gerber file who is the source of the punched Gerber
+        selection_index = self.gerber_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
+
+        try:
+            grb_obj = model_index.internalPointer().obj
+        except Exception:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
+            return
+
+        name = grb_obj.options['name'].rpartition('.')[0]
+        outname = name + "_punched"
+
+        punch_method = self.method_punch.get_value()
+
+        new_options = {}
+        for opt in grb_obj.options:
+            new_options[opt] = deepcopy(grb_obj.options[opt])
+
+        if punch_method == 'exc':
+
+            # get the Excellon file whose geometry will create the punch holes
+            selection_index = self.exc_combo.currentIndex()
+            model_index = self.app.collection.index(selection_index, 0, self.exc_combo.rootModelIndex())
+
+            try:
+                exc_obj = model_index.internalPointer().obj
+            except Exception:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ..."))
+                return
+
+            # this is the punching geometry
+            exc_solid_geometry = MultiPolygon(exc_obj.solid_geometry)
+            if isinstance(grb_obj.solid_geometry, list):
+                grb_solid_geometry = MultiPolygon(grb_obj.solid_geometry)
+            else:
+                grb_solid_geometry = grb_obj.solid_geometry
+
+                # create the punched Gerber solid_geometry
+            punched_solid_geometry = grb_solid_geometry.difference(exc_solid_geometry)
+
+            # update the gerber apertures to include the clear geometry so it can be exported successfully
+            new_apertures = deepcopy(grb_obj.apertures)
+            new_apertures_items = new_apertures.items()
+
+            # find maximum aperture id
+            new_apid = max([int(x) for x, __ in new_apertures_items])
+
+            # store here the clear geometry, the key is the drill size
+            holes_apertures = {}
+
+            for apid, val in new_apertures_items:
+                for elem in val['geometry']:
+                    # make it work only for Gerber Flashes who are Points in 'follow'
+                    if 'solid' in elem and isinstance(elem['follow'], Point):
+                        for drill in exc_obj.drills:
+                            clear_apid_size = exc_obj.tools[drill['tool']]['C']
+
+                            # since there may be drills that do not drill into a pad we test only for drills in a pad
+                            if drill['point'].within(elem['solid']):
+                                geo_elem = {}
+                                geo_elem['clear'] = drill['point']
+
+                                if clear_apid_size not in holes_apertures:
+                                    holes_apertures[clear_apid_size] = {}
+                                    holes_apertures[clear_apid_size]['type'] = 'C'
+                                    holes_apertures[clear_apid_size]['size'] = clear_apid_size
+                                    holes_apertures[clear_apid_size]['geometry'] = []
+
+                                holes_apertures[clear_apid_size]['geometry'].append(deepcopy(geo_elem))
+
+            # add the clear geometry to new apertures; it's easier than to test if there are apertures with the same
+            # size and add there the clear geometry
+            for hole_size, ap_val in holes_apertures.items():
+                new_apid += 1
+                new_apertures[str(new_apid)] = deepcopy(ap_val)
+
+            def init_func(new_obj, app_obj):
+                new_obj.options.update(new_options)
+                new_obj.options['name'] = outname
+                new_obj.fill_color = deepcopy(grb_obj.fill_color)
+                new_obj.outline_color = deepcopy(grb_obj.outline_color)
+
+                new_obj.apertures = deepcopy(new_apertures)
+
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+                new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
+                                                             local_use=new_obj, use_thread=False)
+
+            self.app.new_object('gerber', outname, init_func)
+        elif punch_method == 'fixed':
+            punch_size = float(self.dia_entry.get_value())
+
+            if punch_size == 0.0:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("The value of the fixed diameter is 0.0. Aborting."))
+                return 'fail'
+
+            punching_geo = []
+            for apid in grb_obj.apertures:
+                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."))
+                        return 'fail'
+                    else:
+                        for elem in grb_obj.apertures[apid]['geometry']:
+                            if 'follow' in elem:
+                                if isinstance(elem['follow'], Point):
+                                    punching_geo.append(elem['follow'].buffer(punch_size / 2))
+                elif grb_obj.apertures[apid]['type'] == 'R':
+                    if punch_size >= float(grb_obj.apertures[apid]['width']) or \
+                            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."))
+                        return 'fail'
+                    elif round(float(grb_obj.apertures[apid]['width']), self.decimals) == \
+                            round(float(grb_obj.apertures[apid]['height']), self.decimals) and \
+                            self.square_cb.get_value():
+                        for elem in grb_obj.apertures[apid]['geometry']:
+                            if 'follow' in elem:
+                                if isinstance(elem['follow'], Point):
+                                    punching_geo.append(elem['follow'].buffer(punch_size / 2))
+                    elif round(float(grb_obj.apertures[apid]['width']), self.decimals) != \
+                            round(float(grb_obj.apertures[apid]['height']), self.decimals) and \
+                            self.rectangular_cb.get_value():
+                        for elem in grb_obj.apertures[apid]['geometry']:
+                            if 'follow' in elem:
+                                if isinstance(elem['follow'], Point):
+                                    punching_geo.append(elem['follow'].buffer(punch_size / 2))
+                elif grb_obj.apertures[apid]['type'] == 'O' and self.oblong_cb.get_value():
+                    for elem in grb_obj.apertures[apid]['geometry']:
+                        if 'follow' in elem:
+                            if isinstance(elem['follow'], Point):
+                                punching_geo.append(elem['follow'].buffer(punch_size / 2))
+                elif grb_obj.apertures[apid]['type'] not in ['C', 'R', 'O'] and self.other_cb.get_value():
+                    for elem in grb_obj.apertures[apid]['geometry']:
+                        if 'follow' in elem:
+                            if isinstance(elem['follow'], Point):
+                                punching_geo.append(elem['follow'].buffer(punch_size / 2))
+
+            punching_geo = MultiPolygon(punching_geo)
+            if isinstance(grb_obj.solid_geometry, list):
+                temp_solid_geometry = MultiPolygon(grb_obj.solid_geometry)
+            else:
+                temp_solid_geometry = grb_obj.solid_geometry
+            punched_solid_geometry = temp_solid_geometry.difference(punching_geo)
+
+            if punched_solid_geometry == temp_solid_geometry:
+                self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                     _("Could not generate punched hole Gerber because the newly created object "
+                                       "geometry is the same as the one in the source object geometry..."))
+                return 'fail'
+
+            # update the gerber apertures to include the clear geometry so it can be exported successfully
+            new_apertures = deepcopy(grb_obj.apertures)
+            new_apertures_items = new_apertures.items()
+
+            # find maximum aperture id
+            new_apid = max([int(x) for x, __ in new_apertures_items])
+
+            # store here the clear geometry, the key is the drill size
+            holes_apertures = {}
+
+            for apid, val in new_apertures_items:
+                for elem in val['geometry']:
+                    # make it work only for Gerber Flashes who are Points in 'follow'
+                    if 'solid' in elem and isinstance(elem['follow'], Point):
+                        for geo in punching_geo:
+                            clear_apid_size = punch_size
+
+                            # since there may be drills that do not drill into a pad we test only for drills in a pad
+                            if geo.within(elem['solid']):
+                                geo_elem = {}
+                                geo_elem['clear'] = geo.centroid
+
+                                if clear_apid_size not in holes_apertures:
+                                    holes_apertures[clear_apid_size] = {}
+                                    holes_apertures[clear_apid_size]['type'] = 'C'
+                                    holes_apertures[clear_apid_size]['size'] = clear_apid_size
+                                    holes_apertures[clear_apid_size]['geometry'] = []
+
+                                holes_apertures[clear_apid_size]['geometry'].append(deepcopy(geo_elem))
+
+            # add the clear geometry to new apertures; it's easier than to test if there are apertures with the same
+            # size and add there the clear geometry
+            for hole_size, ap_val in holes_apertures.items():
+                new_apid += 1
+                new_apertures[str(new_apid)] = deepcopy(ap_val)
+
+            def init_func(new_obj, app_obj):
+                new_obj.options.update(new_options)
+                new_obj.options['name'] = outname
+                new_obj.fill_color = deepcopy(grb_obj.fill_color)
+                new_obj.outline_color = deepcopy(grb_obj.outline_color)
+
+                new_obj.apertures = deepcopy(new_apertures)
+
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+                new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
+                                                             local_use=new_obj, use_thread=False)
+
+            self.app.new_object('gerber', outname, init_func)
+        elif punch_method == 'ring':
+            circ_r_val = self.circular_ring_entry.get_value()
+            oblong_r_val = self.oblong_ring_entry.get_value()
+            square_r_val = self.square_ring_entry.get_value()
+            rect_r_val = self.rectangular_ring_entry.get_value()
+            other_r_val = self.other_ring_entry.get_value()
+
+            dia = None
+
+            if isinstance(grb_obj.solid_geometry, list):
+                temp_solid_geometry = MultiPolygon(grb_obj.solid_geometry)
+            else:
+                temp_solid_geometry = grb_obj.solid_geometry
+
+            punched_solid_geometry = temp_solid_geometry
+
+            new_apertures = deepcopy(grb_obj.apertures)
+            new_apertures_items = new_apertures.items()
+
+            # find maximum aperture id
+            new_apid = max([int(x) for x, __ in new_apertures_items])
+
+            # store here the clear geometry, the key is the new aperture size
+            holes_apertures = {}
+
+            for apid, apid_value in grb_obj.apertures.items():
+                ap_type = apid_value['type']
+                punching_geo = []
+
+                if ap_type == 'C' and self.circular_cb.get_value():
+                    dia = float(apid_value['size']) - (2 * circ_r_val)
+                    for elem in apid_value['geometry']:
+                        if 'follow' in elem and isinstance(elem['follow'], Point):
+                            punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                elif ap_type == 'O' and self.oblong_cb.get_value():
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    if width > height:
+                        dia = float(apid_value['height']) - (2 * oblong_r_val)
+                    else:
+                        dia = float(apid_value['width']) - (2 * oblong_r_val)
+
+                    for elem in grb_obj.apertures[apid]['geometry']:
+                        if 'follow' in elem:
+                            if isinstance(elem['follow'], Point):
+                                punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                elif ap_type == 'R':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    # if the height == width (float numbers so the reason for the following)
+                    if round(width, self.decimals) == round(height, self.decimals):
+                        if self.square_cb.get_value():
+                            dia = float(apid_value['height']) - (2 * square_r_val)
+
+                            for elem in grb_obj.apertures[apid]['geometry']:
+                                if 'follow' in elem:
+                                    if isinstance(elem['follow'], Point):
+                                        punching_geo.append(elem['follow'].buffer(dia / 2))
+                    elif self.rectangular_cb.get_value():
+                        if width > height:
+                            dia = float(apid_value['height']) - (2 * rect_r_val)
+                        else:
+                            dia = float(apid_value['width']) - (2 * rect_r_val)
+
+                        for elem in grb_obj.apertures[apid]['geometry']:
+                            if 'follow' in elem:
+                                if isinstance(elem['follow'], Point):
+                                    punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                elif self.other_cb.get_value():
+                    try:
+                        dia = float(apid_value['size']) - (2 * other_r_val)
+                    except KeyError:
+                        if ap_type == 'AM':
+                            pol = apid_value['geometry'][0]['solid']
+                            x0, y0, x1, y1 = pol.bounds
+                            dx = x1 - x0
+                            dy = y1 - y0
+                            if dx <= dy:
+                                dia = dx - (2 * other_r_val)
+                            else:
+                                dia = dy - (2 * other_r_val)
+
+                    for elem in grb_obj.apertures[apid]['geometry']:
+                        if 'follow' in elem:
+                            if isinstance(elem['follow'], Point):
+                                punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                # if dia is None then none of the above applied so we skip the following
+                if dia is None:
+                    continue
+
+                punching_geo = MultiPolygon(punching_geo)
+
+                if punching_geo is None or punching_geo.is_empty:
+                    continue
+
+                punched_solid_geometry = punched_solid_geometry.difference(punching_geo)
+
+                # update the gerber apertures to include the clear geometry so it can be exported successfully
+                for elem in apid_value['geometry']:
+                    # make it work only for Gerber Flashes who are Points in 'follow'
+                    if 'solid' in elem and isinstance(elem['follow'], Point):
+                        clear_apid_size = dia
+                        for geo in punching_geo:
+
+                            # since there may be drills that do not drill into a pad we test only for geos in a pad
+                            if geo.within(elem['solid']):
+                                geo_elem = {}
+                                geo_elem['clear'] = geo.centroid
+
+                                if clear_apid_size not in holes_apertures:
+                                    holes_apertures[clear_apid_size] = {}
+                                    holes_apertures[clear_apid_size]['type'] = 'C'
+                                    holes_apertures[clear_apid_size]['size'] = clear_apid_size
+                                    holes_apertures[clear_apid_size]['geometry'] = []
+
+                                holes_apertures[clear_apid_size]['geometry'].append(deepcopy(geo_elem))
+
+            # add the clear geometry to new apertures; it's easier than to test if there are apertures with the same
+            # size and add there the clear geometry
+            for hole_size, ap_val in holes_apertures.items():
+                new_apid += 1
+                new_apertures[str(new_apid)] = deepcopy(ap_val)
+
+            def init_func(new_obj, app_obj):
+                new_obj.options.update(new_options)
+                new_obj.options['name'] = outname
+                new_obj.fill_color = deepcopy(grb_obj.fill_color)
+                new_obj.outline_color = deepcopy(grb_obj.outline_color)
+
+                new_obj.apertures = deepcopy(new_apertures)
+
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+                new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
+                                                             local_use=new_obj, use_thread=False)
+
+            self.app.new_object('gerber', outname, init_func)
+
+        elif punch_method == 'prop':
+            prop_factor = self.factor_entry.get_value() / 100.0
+
+            dia = None
+
+            if isinstance(grb_obj.solid_geometry, list):
+                temp_solid_geometry = MultiPolygon(grb_obj.solid_geometry)
+            else:
+                temp_solid_geometry = grb_obj.solid_geometry
+
+            punched_solid_geometry = temp_solid_geometry
+
+            new_apertures = deepcopy(grb_obj.apertures)
+            new_apertures_items = new_apertures.items()
+
+            # find maximum aperture id
+            new_apid = max([int(x) for x, __ in new_apertures_items])
+
+            # store here the clear geometry, the key is the new aperture size
+            holes_apertures = {}
+
+            for apid, apid_value in grb_obj.apertures.items():
+                ap_type = apid_value['type']
+                punching_geo = []
+
+                if ap_type == 'C' and self.circular_cb.get_value():
+                    dia = float(apid_value['size']) * prop_factor
+                    for elem in apid_value['geometry']:
+                        if 'follow' in elem and isinstance(elem['follow'], Point):
+                            punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                elif ap_type == 'O' and self.oblong_cb.get_value():
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    if width > height:
+                        dia = float(apid_value['height']) * prop_factor
+                    else:
+                        dia = float(apid_value['width']) * prop_factor
+
+                    for elem in grb_obj.apertures[apid]['geometry']:
+                        if 'follow' in elem:
+                            if isinstance(elem['follow'], Point):
+                                punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                elif ap_type == 'R':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    # if the height == width (float numbers so the reason for the following)
+                    if round(width, self.decimals) == round(height, self.decimals):
+                        if self.square_cb.get_value():
+                            dia = float(apid_value['height']) * prop_factor
+
+                            for elem in grb_obj.apertures[apid]['geometry']:
+                                if 'follow' in elem:
+                                    if isinstance(elem['follow'], Point):
+                                        punching_geo.append(elem['follow'].buffer(dia / 2))
+                    elif self.rectangular_cb.get_value():
+                        if width > height:
+                            dia = float(apid_value['height']) * prop_factor
+                        else:
+                            dia = float(apid_value['width']) * prop_factor
+
+                        for elem in grb_obj.apertures[apid]['geometry']:
+                            if 'follow' in elem:
+                                if isinstance(elem['follow'], Point):
+                                    punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                elif self.other_cb.get_value():
+                    try:
+                        dia = float(apid_value['size']) * prop_factor
+                    except KeyError:
+                        if ap_type == 'AM':
+                            pol = apid_value['geometry'][0]['solid']
+                            x0, y0, x1, y1 = pol.bounds
+                            dx = x1 - x0
+                            dy = y1 - y0
+                            if dx <= dy:
+                                dia = dx * prop_factor
+                            else:
+                                dia = dy * prop_factor
+
+                    for elem in grb_obj.apertures[apid]['geometry']:
+                        if 'follow' in elem:
+                            if isinstance(elem['follow'], Point):
+                                punching_geo.append(elem['follow'].buffer(dia / 2))
+
+                # if dia is None then none of the above applied so we skip the following
+                if dia is None:
+                    continue
+
+                punching_geo = MultiPolygon(punching_geo)
+
+                if punching_geo is None or punching_geo.is_empty:
+                    continue
+
+                punched_solid_geometry = punched_solid_geometry.difference(punching_geo)
+
+                # update the gerber apertures to include the clear geometry so it can be exported successfully
+                for elem in apid_value['geometry']:
+                    # make it work only for Gerber Flashes who are Points in 'follow'
+                    if 'solid' in elem and isinstance(elem['follow'], Point):
+                        clear_apid_size = dia
+                        for geo in punching_geo:
+
+                            # since there may be drills that do not drill into a pad we test only for geos in a pad
+                            if geo.within(elem['solid']):
+                                geo_elem = {}
+                                geo_elem['clear'] = geo.centroid
+
+                                if clear_apid_size not in holes_apertures:
+                                    holes_apertures[clear_apid_size] = {}
+                                    holes_apertures[clear_apid_size]['type'] = 'C'
+                                    holes_apertures[clear_apid_size]['size'] = clear_apid_size
+                                    holes_apertures[clear_apid_size]['geometry'] = []
+
+                                holes_apertures[clear_apid_size]['geometry'].append(deepcopy(geo_elem))
+
+            # add the clear geometry to new apertures; it's easier than to test if there are apertures with the same
+            # size and add there the clear geometry
+            for hole_size, ap_val in holes_apertures.items():
+                new_apid += 1
+                new_apertures[str(new_apid)] = deepcopy(ap_val)
+
+            def init_func(new_obj, app_obj):
+                new_obj.options.update(new_options)
+                new_obj.options['name'] = outname
+                new_obj.fill_color = deepcopy(grb_obj.fill_color)
+                new_obj.outline_color = deepcopy(grb_obj.outline_color)
+
+                new_obj.apertures = deepcopy(new_apertures)
+
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+                new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
+                                                             local_use=new_obj, use_thread=False)
+
+            self.app.new_object('gerber', outname, init_func)
+
+    def reset_fields(self):
+        self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
+        self.ui_disconnect()

+ 27 - 25
flatcamTools/ToolQRCode.py

@@ -9,7 +9,7 @@ from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import Qt
 
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import RadioSet, FCTextArea, FCSpinner, FCEntry, FCCheckBox
+from flatcamGUI.GUIElements import RadioSet, FCTextArea, FCSpinner, FCEntry, FCCheckBox, FCComboBox, FCFileSaveDialog
 from flatcamParsers.ParseSVG import *
 
 from shapely.geometry.base import *
@@ -69,12 +69,13 @@ class QRCode(FlatCAMTool):
         i_grid_lay.setColumnStretch(0, 0)
         i_grid_lay.setColumnStretch(1, 1)
 
-        self.grb_object_combo = QtWidgets.QComboBox()
+        self.grb_object_combo = FCComboBox()
         self.grb_object_combo.setModel(self.app.collection)
         self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.grb_object_combo.setCurrentIndex(1)
+        self.grb_object_combo.is_last = True
+        self.grb_object_combo.obj_type = "Gerber"
 
-        self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("Object"))
         self.grbobj_label.setToolTip(
             _("Gerber Object to which the QRCode will be added.")
         )
@@ -101,7 +102,7 @@ class QRCode(FlatCAMTool):
             _("QRCode version can have values from 1 (21x21 boxes)\n"
               "to 40 (177x177 boxes).")
         )
-        self.version_entry = FCSpinner()
+        self.version_entry = FCSpinner(callback=self.confirmation_message_int)
         self.version_entry.set_range(1, 40)
         self.version_entry.setWrapping(True)
 
@@ -137,7 +138,7 @@ class QRCode(FlatCAMTool):
             _("Box size control the overall size of the QRcode\n"
               "by adjusting the size of each box in the code.")
         )
-        self.bsize_entry = FCSpinner()
+        self.bsize_entry = FCSpinner(callback=self.confirmation_message_int)
         self.bsize_entry.set_range(1, 9999)
         self.bsize_entry.setWrapping(True)
 
@@ -150,10 +151,9 @@ class QRCode(FlatCAMTool):
             _("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.")
         )
-        self.border_size_entry = FCSpinner()
+        self.border_size_entry = FCSpinner(callback=self.confirmation_message_int)
         self.border_size_entry.set_range(1, 9999)
         self.border_size_entry.setWrapping(True)
-        self.border_size_entry.set_value(4)
 
         grid_lay.addWidget(self.border_size_label, 4, 0)
         grid_lay.addWidget(self.border_size_entry, 4, 1)
@@ -382,10 +382,12 @@ class QRCode(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("QRCode Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+Q', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+Q', **kwargs)
 
     def set_tool_ui(self):
         self.units = self.app.defaults['units']
+        self.border_size_entry.set_value(4)
+
         self.version_entry.set_value(int(self.app.defaults["tools_qrcode_version"]))
         self.error_radio.set_value(self.app.defaults["tools_qrcode_error"])
         self.bsize_entry.set_value(int(self.app.defaults["tools_qrcode_box_size"]))
@@ -495,7 +497,7 @@ class QRCode(FlatCAMTool):
             mask_geo = box(a, b, c, d).buffer(buff_val, join_style=2)
 
         # update the solid geometry with the cutout (if it is the case)
-        new_solid_geometry = list()
+        new_solid_geometry = []
         offset_mask_geo = translate(mask_geo, xoff=pos[0], yoff=pos[1])
         for poly in geo_list:
             if poly.contains(offset_mask_geo):
@@ -522,7 +524,7 @@ class QRCode(FlatCAMTool):
 
         box_size = float(self.bsize_entry.get_value()) / 10.0
 
-        sort_apid = list()
+        sort_apid = []
         new_apid = '10'
         if self.grb_object.apertures:
             for k, v in list(self.grb_object.apertures.items()):
@@ -536,8 +538,8 @@ class QRCode(FlatCAMTool):
 
         # don't know if the condition is required since I already made sure above that the new_apid is a new one
         if new_apid not in self.grb_object.apertures:
-            self.grb_object.apertures[new_apid] = dict()
-            self.grb_object.apertures[new_apid]['geometry'] = list()
+            self.grb_object.apertures[new_apid] = {}
+            self.grb_object.apertures[new_apid]['geometry'] = []
             self.grb_object.apertures[new_apid]['type'] = 'R'
             # TODO: HACK
             # I've artificially added 1% to the height and width because otherwise after loading the
@@ -548,14 +550,14 @@ class QRCode(FlatCAMTool):
             self.grb_object.apertures[new_apid]['size'] = deepcopy(math.sqrt(box_size ** 2 + box_size ** 2))
 
         if '0' not in self.grb_object.apertures:
-            self.grb_object.apertures['0'] = dict()
-            self.grb_object.apertures['0']['geometry'] = list()
+            self.grb_object.apertures['0'] = {}
+            self.grb_object.apertures['0']['geometry'] = []
             self.grb_object.apertures['0']['type'] = 'REG'
             self.grb_object.apertures['0']['size'] = 0.0
 
         # in case that the QRCode geometry is dropped onto a copper region (found in the '0' aperture)
         # make sure that I place a cutout there
-        zero_elem = dict()
+        zero_elem = {}
         zero_elem['clear'] = offset_mask_geo
         self.grb_object.apertures['0']['geometry'].append(deepcopy(zero_elem))
 
@@ -570,12 +572,12 @@ class QRCode(FlatCAMTool):
 
         try:
             for geo in self.qrcode_geometry:
-                geo_elem = dict()
+                geo_elem = {}
                 geo_elem['solid'] = translate(geo, xoff=pos[0], yoff=pos[1])
                 geo_elem['follow'] = translate(geo.centroid, xoff=pos[0], yoff=pos[1])
                 self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
         except TypeError:
-            geo_elem = dict()
+            geo_elem = {}
             geo_elem['solid'] = self.qrcode_geometry
             self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
 
@@ -591,7 +593,7 @@ class QRCode(FlatCAMTool):
         # face = '#0000FF' + str(hex(int(0.2 * 255)))[2:]
         outline = '#0000FFAF'
 
-        offset_geo = list()
+        offset_geo = []
 
         # I use the len of self.qrcode_geometry instead of the utility one because the complexity of the polygons is
         # better seen in this (bit what if the sel.qrcode_geometry is just one geo element? len will fail ...
@@ -776,17 +778,17 @@ class QRCode(FlatCAMTool):
 
         _filter = "PNG File (*.png);;All Files (*.*)"
         try:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+            filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export PNG"),
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_png',
                 filter=_filter)
         except TypeError:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export PNG"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export PNG"), filter=_filter)
 
         filename = str(filename)
 
         if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL]%s' % _(" Export PNG cancelled."))
+            self.app.inform.emit('[WARNING_NOTCL]%s' % _("Cancelled."))
             return
         else:
             self.app.worker_task.emit({'fcn': job_thread_qr_png, 'params': [self.app, filename]})
@@ -823,17 +825,17 @@ class QRCode(FlatCAMTool):
 
         _filter = "SVG File (*.svg);;All Files (*.*)"
         try:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+            filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export SVG"),
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_svg',
                 filter=_filter)
         except TypeError:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export SVG"), filter=_filter)
 
         filename = str(filename)
 
         if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL]%s' % _(" Export SVG cancelled."))
+            self.app.inform.emit('[WARNING_NOTCL]%s' % _("Cancelled."))
             return
         else:
             self.app.worker_task.emit({'fcn': job_thread_qr_svg, 'params': [self.app, filename]})

+ 127 - 118
flatcamTools/ToolRulesCheck.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets
 
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCComboBox
 from copy import deepcopy
 
 from FlatCAMPool import *
@@ -69,10 +69,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.all_obj_cb, 0, 2)
 
         # Copper Top object
-        self.copper_t_object = QtWidgets.QComboBox()
+        self.copper_t_object = FCComboBox()
         self.copper_t_object.setModel(self.app.collection)
         self.copper_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.copper_t_object.setCurrentIndex(1)
+        self.copper_t_object.is_last = True
+        self.copper_t_object.obj_type = "Gerber"
 
         self.copper_t_object_lbl = QtWidgets.QLabel('%s:' % _("Top"))
         self.copper_t_object_lbl.setToolTip(
@@ -86,10 +87,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.copper_t_cb, 1, 2)
 
         # Copper Bottom object
-        self.copper_b_object = QtWidgets.QComboBox()
+        self.copper_b_object = FCComboBox()
         self.copper_b_object.setModel(self.app.collection)
         self.copper_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.copper_b_object.setCurrentIndex(1)
+        self.copper_b_object.is_last = True
+        self.copper_b_object.obj_type = "Gerber"
 
         self.copper_b_object_lbl = QtWidgets.QLabel('%s:' % _("Bottom"))
         self.copper_b_object_lbl.setToolTip(
@@ -103,10 +105,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.copper_b_cb, 2, 2)
 
         # SolderMask Top object
-        self.sm_t_object = QtWidgets.QComboBox()
+        self.sm_t_object = FCComboBox()
         self.sm_t_object.setModel(self.app.collection)
         self.sm_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.sm_t_object.setCurrentIndex(1)
+        self.sm_t_object.is_last = True
+        self.sm_t_object.obj_type = "Gerber"
 
         self.sm_t_object_lbl = QtWidgets.QLabel('%s:' % _("SM Top"))
         self.sm_t_object_lbl.setToolTip(
@@ -120,10 +123,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.sm_t_cb, 3, 2)
 
         # SolderMask Bottom object
-        self.sm_b_object = QtWidgets.QComboBox()
+        self.sm_b_object = FCComboBox()
         self.sm_b_object.setModel(self.app.collection)
         self.sm_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.sm_b_object.setCurrentIndex(1)
+        self.sm_b_object.is_last = True
+        self.sm_b_object.obj_type = "Gerber"
 
         self.sm_b_object_lbl = QtWidgets.QLabel('%s:' % _("SM Bottom"))
         self.sm_b_object_lbl.setToolTip(
@@ -137,10 +141,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.sm_b_cb, 4, 2)
 
         # SilkScreen Top object
-        self.ss_t_object = QtWidgets.QComboBox()
+        self.ss_t_object = FCComboBox()
         self.ss_t_object.setModel(self.app.collection)
         self.ss_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.ss_t_object.setCurrentIndex(1)
+        self.ss_t_object.is_last = True
+        self.ss_t_object.obj_type = "Gerber"
 
         self.ss_t_object_lbl = QtWidgets.QLabel('%s:' % _("Silk Top"))
         self.ss_t_object_lbl.setToolTip(
@@ -154,10 +159,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.ss_t_cb, 5, 2)
 
         # SilkScreen Bottom object
-        self.ss_b_object = QtWidgets.QComboBox()
+        self.ss_b_object = FCComboBox()
         self.ss_b_object.setModel(self.app.collection)
         self.ss_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.ss_b_object.setCurrentIndex(1)
+        self.ss_b_object.is_last = True
+        self.ss_b_object.obj_type = "Gerber"
 
         self.ss_b_object_lbl = QtWidgets.QLabel('%s:' % _("Silk Bottom"))
         self.ss_b_object_lbl.setToolTip(
@@ -171,10 +177,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.ss_b_cb, 6, 2)
 
         # Outline object
-        self.outline_object = QtWidgets.QComboBox()
+        self.outline_object = FCComboBox()
         self.outline_object.setModel(self.app.collection)
         self.outline_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.outline_object.setCurrentIndex(1)
+        self.outline_object.is_last = True
+        self.outline_object.obj_type = "Gerber"
 
         self.outline_object_lbl = QtWidgets.QLabel('%s:' % _("Outline"))
         self.outline_object_lbl.setToolTip(
@@ -197,10 +204,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.excellon_title_lbl, 9, 0, 1, 3)
 
         # Excellon 1 object
-        self.e1_object = QtWidgets.QComboBox()
+        self.e1_object = FCComboBox()
         self.e1_object.setModel(self.app.collection)
         self.e1_object.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.e1_object.setCurrentIndex(1)
+        self.e1_object.is_last = True
+        self.e1_object.obj_type = "Excellon"
 
         self.e1_object_lbl = QtWidgets.QLabel('%s:' % _("Excellon 1"))
         self.e1_object_lbl.setToolTip(
@@ -215,10 +223,11 @@ class RulesCheck(FlatCAMTool):
         self.grid_layout.addWidget(self.e1_cb, 10, 2)
 
         # Excellon 2 object
-        self.e2_object = QtWidgets.QComboBox()
+        self.e2_object = FCComboBox()
         self.e2_object.setModel(self.app.collection)
         self.e2_object.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.e2_object.setCurrentIndex(1)
+        self.e2_object.is_last = True
+        self.e2_object.obj_type = "Excellon"
 
         self.e2_object_lbl = QtWidgets.QLabel('%s:' % _("Excellon 2"))
         self.e2_object_lbl.setToolTip(
@@ -260,7 +269,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.trace_size_cb)
 
         # Trace size value
-        self.trace_size_entry = FCDoubleSpinner()
+        self.trace_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.trace_size_entry.set_range(0.00001, 999.99999)
         self.trace_size_entry.set_precision(self.decimals)
         self.trace_size_entry.setSingleStep(0.1)
@@ -282,7 +291,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.clearance_copper2copper_cb)
 
         # Copper2copper clearance value
-        self.clearance_copper2copper_entry = FCDoubleSpinner()
+        self.clearance_copper2copper_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_copper2copper_entry.set_range(0.00001, 999.99999)
         self.clearance_copper2copper_entry.set_precision(self.decimals)
         self.clearance_copper2copper_entry.setSingleStep(0.1)
@@ -305,7 +314,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.clearance_copper2ol_cb)
 
         # Copper2outline clearance value
-        self.clearance_copper2ol_entry = FCDoubleSpinner()
+        self.clearance_copper2ol_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_copper2ol_entry.set_range(0.00001, 999.99999)
         self.clearance_copper2ol_entry.set_precision(self.decimals)
         self.clearance_copper2ol_entry.setSingleStep(0.1)
@@ -328,7 +337,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.clearance_silk2silk_cb)
 
         # Copper2silkscreen clearance value
-        self.clearance_silk2silk_entry = FCDoubleSpinner()
+        self.clearance_silk2silk_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_silk2silk_entry.set_range(0.00001, 999.99999)
         self.clearance_silk2silk_entry.set_precision(self.decimals)
         self.clearance_silk2silk_entry.setSingleStep(0.1)
@@ -351,7 +360,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.clearance_silk2sm_cb)
 
         # Silkscreen2soldermask clearance value
-        self.clearance_silk2sm_entry = FCDoubleSpinner()
+        self.clearance_silk2sm_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_silk2sm_entry.set_range(0.00001, 999.99999)
         self.clearance_silk2sm_entry.set_precision(self.decimals)
         self.clearance_silk2sm_entry.setSingleStep(0.1)
@@ -374,7 +383,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.clearance_silk2ol_cb)
 
         # Silk2outline clearance value
-        self.clearance_silk2ol_entry = FCDoubleSpinner()
+        self.clearance_silk2ol_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_silk2ol_entry.set_range(0.00001, 999.99999)
         self.clearance_silk2ol_entry.set_precision(self.decimals)
         self.clearance_silk2ol_entry.setSingleStep(0.1)
@@ -397,7 +406,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.clearance_sm2sm_cb)
 
         # Soldermask2soldermask clearance value
-        self.clearance_sm2sm_entry = FCDoubleSpinner()
+        self.clearance_sm2sm_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_sm2sm_entry.set_range(0.00001, 999.99999)
         self.clearance_sm2sm_entry.set_precision(self.decimals)
         self.clearance_sm2sm_entry.setSingleStep(0.1)
@@ -420,7 +429,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.ring_integrity_cb)
 
         # Ring integrity value
-        self.ring_integrity_entry = FCDoubleSpinner()
+        self.ring_integrity_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.ring_integrity_entry.set_range(0.00001, 999.99999)
         self.ring_integrity_entry.set_precision(self.decimals)
         self.ring_integrity_entry.setSingleStep(0.1)
@@ -445,7 +454,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.clearance_d2d_cb)
 
         # Hole2Hole clearance value
-        self.clearance_d2d_entry = FCDoubleSpinner()
+        self.clearance_d2d_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.clearance_d2d_entry.set_range(0.00001, 999.99999)
         self.clearance_d2d_entry.set_precision(self.decimals)
         self.clearance_d2d_entry.setSingleStep(0.1)
@@ -468,7 +477,7 @@ class RulesCheck(FlatCAMTool):
         self.form_layout_1.addRow(self.drill_size_cb)
 
         # Drile holes value
-        self.drill_size_entry = FCDoubleSpinner()
+        self.drill_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.drill_size_entry.set_range(0.00001, 999.99999)
         self.drill_size_entry.set_precision(self.decimals)
         self.drill_size_entry.setSingleStep(0.1)
@@ -607,7 +616,7 @@ class RulesCheck(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Rules Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+R', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+R', **kwargs)
 
     def set_tool_ui(self):
 
@@ -655,8 +664,8 @@ class RulesCheck(FlatCAMTool):
 
         rule_title = rule
 
-        violations = list()
-        obj_violations = dict()
+        violations = []
+        obj_violations = {}
         obj_violations.update({
             'name': '',
             'points': list()
@@ -667,8 +676,8 @@ class RulesCheck(FlatCAMTool):
 
         obj_violations['name'] = gerber_obj['name']
 
-        solid_geo = list()
-        clear_geo = list()
+        solid_geo = []
+        clear_geo = []
         for apid in gerber_obj['apertures']:
             if 'geometry' in gerber_obj['apertures'][apid]:
                 geometry = gerber_obj['apertures'][apid]['geometry']
@@ -679,7 +688,7 @@ class RulesCheck(FlatCAMTool):
                         clear_geo.append(geo_el['clear'])
 
         if clear_geo:
-            total_geo = list()
+            total_geo = []
             for geo_c in clear_geo:
                 for geo_s in solid_geo:
                     if geo_c.within(geo_s):
@@ -696,7 +705,7 @@ class RulesCheck(FlatCAMTool):
             iterations = (iterations * (iterations - 1)) / 2
         log.debug("RulesCheck.check_gerber_clearance(). Iterations: %s" % str(iterations))
 
-        min_dict = dict()
+        min_dict = {}
         idx = 1
         for geo in total_geo:
             for s_geo in total_geo[idx:]:
@@ -729,8 +738,8 @@ class RulesCheck(FlatCAMTool):
         log.debug("RulesCheck.check_gerber_clearance()")
         rule_title = rule
 
-        violations = list()
-        obj_violations = dict()
+        violations = []
+        obj_violations = {}
         obj_violations.update({
             'name': '',
             'points': list()
@@ -739,7 +748,7 @@ class RulesCheck(FlatCAMTool):
         if len(gerber_list) == 2:
             gerber_1 = gerber_list[0]
             # added it so I won't have errors of using before declaring
-            gerber_2 = dict()
+            gerber_2 = {}
 
             gerber_3 = gerber_list[1]
         elif len(gerber_list) == 3:
@@ -749,7 +758,7 @@ class RulesCheck(FlatCAMTool):
         else:
             return 'Fail. Not enough Gerber objects to check Gerber 2 Gerber clearance'
 
-        total_geo_grb_1 = list()
+        total_geo_grb_1 = []
         for apid in gerber_1['apertures']:
             if 'geometry' in gerber_1['apertures'][apid]:
                 geometry = gerber_1['apertures'][apid]['geometry']
@@ -766,7 +775,7 @@ class RulesCheck(FlatCAMTool):
                         if 'solid' in geo_el and geo_el['solid'] is not None:
                             total_geo_grb_1.append(geo_el['solid'])
 
-        total_geo_grb_3 = list()
+        total_geo_grb_3 = []
         for apid in gerber_3['apertures']:
             if 'geometry' in gerber_3['apertures'][apid]:
                 geometry = gerber_3['apertures'][apid]['geometry']
@@ -795,7 +804,7 @@ class RulesCheck(FlatCAMTool):
         iterations = len_1 * len_3
         log.debug("RulesCheck.check_gerber_clearance(). Iterations: %s" % str(iterations))
 
-        min_dict = dict()
+        min_dict = {}
         for geo in total_geo_grb_1:
             for s_geo in total_geo_grb_3:
                 # minimize the number of distances by not taking into considerations those that are too small
@@ -817,7 +826,7 @@ class RulesCheck(FlatCAMTool):
             for location in min_dict[dist]:
                 points_list.add(location)
 
-        name_list = list()
+        name_list = []
         if gerber_1:
             name_list.append(gerber_1['name'])
         if gerber_2:
@@ -837,8 +846,8 @@ class RulesCheck(FlatCAMTool):
 
         rule = _("Hole Size")
 
-        violations = list()
-        obj_violations = dict()
+        violations = []
+        obj_violations = {}
         obj_violations.update({
             'name': '',
             'dia': list()
@@ -863,14 +872,14 @@ class RulesCheck(FlatCAMTool):
         log.debug("RulesCheck.check_holes_clearance()")
         rule = _("Hole to Hole Clearance")
 
-        violations = list()
-        obj_violations = dict()
+        violations = []
+        obj_violations = {}
         obj_violations.update({
             'name': '',
             'points': list()
         })
 
-        total_geo = list()
+        total_geo = []
         for elem in elements:
             for tool in elem['tools']:
                 if 'solid_geometry' in elem['tools'][tool]:
@@ -878,7 +887,7 @@ class RulesCheck(FlatCAMTool):
                     for geo in geometry:
                         total_geo.append(geo)
 
-        min_dict = dict()
+        min_dict = {}
         idx = 1
         for geo in total_geo:
             for s_geo in total_geo[idx:]:
@@ -903,7 +912,7 @@ class RulesCheck(FlatCAMTool):
                 for location in min_dict[dist]:
                     points_list.add(location)
 
-        name_list = list()
+        name_list = []
         for elem in elements:
             name_list.append(elem['name'])
 
@@ -919,8 +928,8 @@ class RulesCheck(FlatCAMTool):
 
         rule = _("Trace Size")
 
-        violations = list()
-        obj_violations = dict()
+        violations = []
+        obj_violations = {}
         obj_violations.update({
             'name': '',
             'size': list(),
@@ -957,18 +966,18 @@ class RulesCheck(FlatCAMTool):
     def check_gerber_annular_ring(obj_list, size, rule):
         rule_title = rule
 
-        violations = list()
-        obj_violations = dict()
+        violations = []
+        obj_violations = {}
         obj_violations.update({
             'name': '',
             'points': list()
         })
 
         # added it so I won't have errors of using before declaring
-        gerber_obj = dict()
-        gerber_extra_obj = dict()
-        exc_obj = dict()
-        exc_extra_obj = dict()
+        gerber_obj = {}
+        gerber_extra_obj = {}
+        exc_obj = {}
+        exc_extra_obj = {}
 
         if len(obj_list) == 2:
             gerber_obj = obj_list[0]
@@ -997,7 +1006,7 @@ class RulesCheck(FlatCAMTool):
         else:
             return 'Fail. Not enough objects to check Minimum Annular Ring'
 
-        total_geo_grb = list()
+        total_geo_grb = []
         for apid in gerber_obj['apertures']:
             if 'geometry' in gerber_obj['apertures'][apid]:
                 geometry = gerber_obj['apertures'][apid]['geometry']
@@ -1017,7 +1026,7 @@ class RulesCheck(FlatCAMTool):
         total_geo_grb = MultiPolygon(total_geo_grb)
         total_geo_grb = total_geo_grb.buffer(0)
 
-        total_geo_exc = list()
+        total_geo_exc = []
         for tool in exc_obj['tools']:
             if 'solid_geometry' in exc_obj['tools'][tool]:
                 geometry = exc_obj['tools'][tool]['solid_geometry']
@@ -1047,7 +1056,7 @@ class RulesCheck(FlatCAMTool):
         iterations = len_1 * len_2
         log.debug("RulesCheck.check_gerber_annular_ring(). Iterations: %s" % str(iterations))
 
-        min_dict = dict()
+        min_dict = {}
         dist = None
         for geo in total_geo_grb:
             for s_geo in total_geo_exc:
@@ -1075,12 +1084,12 @@ class RulesCheck(FlatCAMTool):
                     else:
                         min_dict[dist] = [s_geo.representative_point()]
 
-        points_list = list()
+        points_list = []
         for dist in min_dict.keys():
             for location in min_dict[dist]:
                 points_list.append(location)
 
-        name_list = list()
+        name_list = []
         try:
             if gerber_obj:
                 name_list.append(gerber_obj['name'])
@@ -1110,7 +1119,7 @@ class RulesCheck(FlatCAMTool):
         return rule_title, violations
 
     def execute(self):
-        self.results = list()
+        self.results = []
 
         log.debug("RuleCheck() executing")
 
@@ -1119,17 +1128,17 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Trace Size
             if self.trace_size_cb.get_value():
-                copper_list = list()
+                copper_list = []
                 copper_name_1 = self.copper_t_object.currentText()
-                if copper_name_1 is not '' and self.copper_t_cb.get_value():
-                    elem_dict = dict()
+                if copper_name_1 != '' and self.copper_t_cb.get_value():
+                    elem_dict = {}
                     elem_dict['name'] = deepcopy(copper_name_1)
                     elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_1).apertures)
                     copper_list.append(elem_dict)
 
                 copper_name_2 = self.copper_b_object.currentText()
-                if copper_name_2 is not '' and self.copper_b_cb.get_value():
-                    elem_dict = dict()
+                if copper_name_2 !='' and self.copper_b_cb.get_value():
+                    elem_dict = {}
                     elem_dict['name'] = deepcopy(copper_name_2)
                     elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures)
                     copper_list.append(elem_dict)
@@ -1151,9 +1160,9 @@ class RulesCheck(FlatCAMTool):
 
                 if self.copper_t_cb.get_value():
                     copper_t_obj = self.copper_t_object.currentText()
-                    copper_t_dict = dict()
+                    copper_t_dict = {}
 
-                    if copper_t_obj is not '':
+                    if copper_t_obj != '':
                         copper_t_dict['name'] = deepcopy(copper_t_obj)
                         copper_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_t_obj).apertures)
 
@@ -1163,8 +1172,8 @@ class RulesCheck(FlatCAMTool):
                                                                         _("TOP -> Copper to Copper clearance"))))
                 if self.copper_b_cb.get_value():
                     copper_b_obj = self.copper_b_object.currentText()
-                    copper_b_dict = dict()
-                    if copper_b_obj is not '':
+                    copper_b_dict = {}
+                    if copper_b_obj != '':
                         copper_b_dict['name'] = deepcopy(copper_b_obj)
                         copper_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_b_obj).apertures)
 
@@ -1181,22 +1190,22 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Copper to Outline Clearance
             if self.clearance_copper2ol_cb.get_value() and self.out_cb.get_value():
-                top_dict = dict()
-                bottom_dict = dict()
-                outline_dict = dict()
+                top_dict = {}
+                bottom_dict = {}
+                outline_dict = {}
 
                 copper_top = self.copper_t_object.currentText()
-                if copper_top is not '' and self.copper_t_cb.get_value():
+                if copper_top != '' and self.copper_t_cb.get_value():
                     top_dict['name'] = deepcopy(copper_top)
                     top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_top).apertures)
 
                 copper_bottom = self.copper_b_object.currentText()
-                if copper_bottom is not '' and self.copper_b_cb.get_value():
+                if copper_bottom != '' and self.copper_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(copper_bottom)
                     bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures)
 
                 copper_outline = self.outline_object.currentText()
-                if copper_outline is not '' and self.out_cb.get_value():
+                if copper_outline != '' and self.out_cb.get_value():
                     outline_dict['name'] = deepcopy(copper_outline)
                     outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
 
@@ -1235,7 +1244,7 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Silk to Silk Clearance
             if self.clearance_silk2silk_cb.get_value():
-                silk_dict = dict()
+                silk_dict = {}
 
                 try:
                     silk_silk_clearance = float(self.clearance_silk2silk_entry.get_value())
@@ -1248,7 +1257,7 @@ class RulesCheck(FlatCAMTool):
 
                 if self.ss_t_cb.get_value():
                     silk_obj = self.ss_t_object.currentText()
-                    if silk_obj is not '':
+                    if silk_obj != '':
                         silk_dict['name'] = deepcopy(silk_obj)
                         silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures)
 
@@ -1258,7 +1267,7 @@ class RulesCheck(FlatCAMTool):
                                                                         _("TOP -> Silk to Silk clearance"))))
                 if self.ss_b_cb.get_value():
                     silk_obj = self.ss_b_object.currentText()
-                    if silk_obj is not '':
+                    if silk_obj != '':
                         silk_dict['name'] = deepcopy(silk_obj)
                         silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures)
 
@@ -1275,10 +1284,10 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Silk to Solder Mask Clearance
             if self.clearance_silk2sm_cb.get_value():
-                silk_t_dict = dict()
-                sm_t_dict = dict()
-                silk_b_dict = dict()
-                sm_b_dict = dict()
+                silk_t_dict = {}
+                sm_t_dict = {}
+                silk_b_dict = {}
+                sm_b_dict = {}
 
                 top_ss = False
                 bottom_ss = False
@@ -1286,25 +1295,25 @@ class RulesCheck(FlatCAMTool):
                 bottom_sm = False
 
                 silk_top = self.ss_t_object.currentText()
-                if silk_top is not '' and self.ss_t_cb.get_value():
+                if silk_top != '' and self.ss_t_cb.get_value():
                     silk_t_dict['name'] = deepcopy(silk_top)
                     silk_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
                     top_ss = True
 
                 silk_bottom = self.ss_b_object.currentText()
-                if silk_bottom is not '' and self.ss_b_cb.get_value():
+                if silk_bottom != '' and self.ss_b_cb.get_value():
                     silk_b_dict['name'] = deepcopy(silk_bottom)
                     silk_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
                     bottom_ss = True
 
                 sm_top = self.sm_t_object.currentText()
-                if sm_top is not '' and self.sm_t_cb.get_value():
+                if sm_top != '' and self.sm_t_cb.get_value():
                     sm_t_dict['name'] = deepcopy(sm_top)
                     sm_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_top).apertures)
                     top_sm = True
 
                 sm_bottom = self.sm_b_object.currentText()
-                if sm_bottom is not '' and self.sm_b_cb.get_value():
+                if sm_bottom != '' and self.sm_b_cb.get_value():
                     sm_b_dict['name'] = deepcopy(sm_bottom)
                     sm_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_bottom).apertures)
                     bottom_sm = True
@@ -1344,22 +1353,22 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Silk to Outline Clearance
             if self.clearance_silk2ol_cb.get_value():
-                top_dict = dict()
-                bottom_dict = dict()
-                outline_dict = dict()
+                top_dict = {}
+                bottom_dict = {}
+                outline_dict = {}
 
                 silk_top = self.ss_t_object.currentText()
-                if silk_top is not '' and self.ss_t_cb.get_value():
+                if silk_top != '' and self.ss_t_cb.get_value():
                     top_dict['name'] = deepcopy(silk_top)
                     top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
 
                 silk_bottom = self.ss_b_object.currentText()
-                if silk_bottom is not '' and self.ss_b_cb.get_value():
+                if silk_bottom !=  '' and self.ss_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(silk_bottom)
                     bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
 
                 copper_outline = self.outline_object.currentText()
-                if copper_outline is not '' and self.out_cb.get_value():
+                if copper_outline !=  '' and self.out_cb.get_value():
                     outline_dict['name'] = deepcopy(copper_outline)
                     outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
 
@@ -1399,7 +1408,7 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Minimum Solder Mask Sliver
             if self.clearance_silk2silk_cb.get_value():
-                sm_dict = dict()
+                sm_dict = {}
 
                 try:
                     sm_sm_clearance = float(self.clearance_sm2sm_entry.get_value())
@@ -1412,7 +1421,7 @@ class RulesCheck(FlatCAMTool):
 
                 if self.sm_t_cb.get_value():
                     solder_obj = self.sm_t_object.currentText()
-                    if solder_obj is not '':
+                    if solder_obj !=  '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
 
@@ -1422,7 +1431,7 @@ class RulesCheck(FlatCAMTool):
                                                                         _("TOP -> Minimum Solder Mask Sliver"))))
                 if self.sm_b_cb.get_value():
                     solder_obj = self.sm_b_object.currentText()
-                    if solder_obj is not '':
+                    if solder_obj !=  '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
 
@@ -1439,29 +1448,29 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Minimum Annular Ring
             if self.ring_integrity_cb.get_value():
-                top_dict = dict()
-                bottom_dict = dict()
-                exc_1_dict = dict()
-                exc_2_dict = dict()
+                top_dict = {}
+                bottom_dict = {}
+                exc_1_dict = {}
+                exc_2_dict = {}
 
                 copper_top = self.copper_t_object.currentText()
-                if copper_top is not '' and self.copper_t_cb.get_value():
+                if copper_top != '' and self.copper_t_cb.get_value():
                     top_dict['name'] = deepcopy(copper_top)
                     top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_top).apertures)
 
                 copper_bottom = self.copper_b_object.currentText()
-                if copper_bottom is not '' and self.copper_b_cb.get_value():
+                if copper_bottom != '' and self.copper_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(copper_bottom)
                     bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures)
 
                 excellon_1 = self.e1_object.currentText()
-                if excellon_1 is not '' and self.e1_cb.get_value():
+                if excellon_1 != '' and self.e1_cb.get_value():
                     exc_1_dict['name'] = deepcopy(excellon_1)
                     exc_1_dict['tools'] = deepcopy(
                         self.app.collection.get_by_name(excellon_1).tools)
 
                 excellon_2 = self.e2_object.currentText()
-                if excellon_2 is not '' and self.e2_cb.get_value():
+                if excellon_2 != '' and self.e2_cb.get_value():
                     exc_2_dict['name'] = deepcopy(excellon_2)
                     exc_2_dict['tools'] = deepcopy(
                         self.app.collection.get_by_name(excellon_2).tools)
@@ -1504,17 +1513,17 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Hole to Hole Clearance
             if self.clearance_d2d_cb.get_value():
-                exc_list = list()
+                exc_list = []
                 exc_name_1 = self.e1_object.currentText()
-                if exc_name_1 is not '' and self.e1_cb.get_value():
-                    elem_dict = dict()
+                if exc_name_1 != '' and self.e1_cb.get_value():
+                    elem_dict = {}
                     elem_dict['name'] = deepcopy(exc_name_1)
                     elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_1).tools)
                     exc_list.append(elem_dict)
 
                 exc_name_2 = self.e2_object.currentText()
-                if exc_name_2 is not '' and self.e2_cb.get_value():
-                    elem_dict = dict()
+                if exc_name_2 != '' and self.e2_cb.get_value():
+                    elem_dict = {}
                     elem_dict['name'] = deepcopy(exc_name_2)
                     elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_2).tools)
                     exc_list.append(elem_dict)
@@ -1524,17 +1533,17 @@ class RulesCheck(FlatCAMTool):
 
             # RULE: Check Holes Size
             if self.drill_size_cb.get_value():
-                exc_list = list()
+                exc_list = []
                 exc_name_1 = self.e1_object.currentText()
-                if exc_name_1 is not '' and self.e1_cb.get_value():
-                    elem_dict = dict()
+                if exc_name_1 != '' and self.e1_cb.get_value():
+                    elem_dict = {}
                     elem_dict['name'] = deepcopy(exc_name_1)
                     elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_1).tools)
                     exc_list.append(elem_dict)
 
                 exc_name_2 = self.e2_object.currentText()
-                if exc_name_2 is not '' and self.e2_cb.get_value():
-                    elem_dict = dict()
+                if exc_name_2 != '' and self.e2_cb.get_value():
+                    elem_dict = {}
                     elem_dict['name'] = deepcopy(exc_name_2)
                     elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_2).tools)
                     exc_list.append(elem_dict)
@@ -1542,7 +1551,7 @@ class RulesCheck(FlatCAMTool):
                 drill_size = float(self.drill_size_entry.get_value())
                 self.results.append(self.pool.apply_async(self.check_holes_size, args=(exc_list, drill_size)))
 
-            output = list()
+            output = []
             for p in self.results:
                 output.append(p.get())
 

+ 144 - 20
flatcamTools/ToolShell.py

@@ -14,6 +14,8 @@ from flatcamGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit
 import html
 import sys
 
+import tkinter as tk
+
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -31,10 +33,10 @@ class TermWidget(QWidget):
     User pressed Enter. Client class should decide, if command must be executed or user may continue edit it
     """
 
-    def __init__(self, version, *args):
+    def __init__(self, version, app, *args):
         QWidget.__init__(self, *args)
 
-        self._browser = _BrowserTextEdit(version=version)
+        self._browser = _BrowserTextEdit(version=version, app=app)
         self._browser.setStyleSheet("font: 9pt \"Courier\";")
         self._browser.setReadOnly(True)
         self._browser.document().setDefaultStyleSheet(
@@ -90,27 +92,46 @@ class TermWidget(QWidget):
         """
         Convert text to HTML for inserting it to browser
         """
-        assert style in ('in', 'out', 'err', 'warning', 'success', 'selected')
+        assert style in ('in', 'out', 'err', 'warning', 'success', 'selected', 'raw')
 
-        text = html.escape(text)
-        text = text.replace('\n', '<br/>')
+        if style != 'raw':
+            text = html.escape(text)
+            text = text.replace('\n', '<br/>')
+        else:
+            text = text.replace('\n', '<br>')
+            text = text.replace('\t', '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;')
 
+        idx = text.find(']')
+        mtype = text[:idx+1].upper()
+        mtype = mtype.replace('_NOTCL', '')
+        body = text[idx+1:]
         if style == 'in':
             text = '<span style="font-weight: bold;">%s</span>' % text
         elif style == 'err':
-            text = '<span style="font-weight: bold; color: red;">%s</span>' % text
+            text = '<span style="font-weight: bold; color: red;">%s</span>'\
+                   '<span style="font-weight: bold;">%s</span>'\
+                   %(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>' % text
+            text = '<span style="font-weight: bold; color: #f4b642;">%s</span>' \
+                   '<span style="font-weight: bold;">%s</span>' \
+                   % (mtype, body)
         elif style == 'success':
-            text = '<span style="font-weight: bold; color: #084400;">%s</span>' % text
+            # text = '<span style="font-weight: bold; color: #15b300;">%s</span>' % text
+            text = '<span style="font-weight: bold; color: #15b300;">%s</span>' \
+                   '<span style="font-weight: bold;">%s</span>' \
+                   % (mtype, body)
         elif style == 'selected':
             text = ''
+        elif style == 'raw':
+            text = text
         else:
-            text = '<span>%s</span>' % text  # without span <br/> is ignored!!!
+            # without span <br/> is ignored!!!
+            text = '<span>%s</span>' % text
 
         scrollbar = self._browser.verticalScrollBar()
         old_value = scrollbar.value()
-        scrollattheend = old_value == scrollbar.maximum()
+        # scrollattheend = old_value == scrollbar.maximum()
 
         self._browser.moveCursor(QTextCursor.End)
         self._browser.insertHtml(text)
@@ -175,23 +196,29 @@ class TermWidget(QWidget):
         """
         self._append_to_browser('out', text)
 
+    def append_raw(self, text):
+        """
+        Append text to output widget as it is
+        """
+        self._append_to_browser('raw', text)
+
     def append_success(self, text):
-        """Appent text to output widget
+        """Append text to output widget
         """
         self._append_to_browser('success', text)
 
     def append_selected(self, text):
-        """Appent text to output widget
+        """Append text to output widget
         """
         self._append_to_browser('selected', text)
 
     def append_warning(self, text):
-        """Appent text to output widget
+        """Append text to output widget
         """
         self._append_to_browser('warning', text)
 
     def append_error(self, text):
-        """Appent error text to output widget. Text is drawn with red background
+        """Append error text to output widget. Text is drawn with red background
         """
         self._append_to_browser('err', text)
 
@@ -227,15 +254,22 @@ class TermWidget(QWidget):
 
 class FCShell(TermWidget):
     def __init__(self, sysShell, version, *args):
-        TermWidget.__init__(self, version, *args)
+        """
+
+        :param sysShell:    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
 
     def is_command_complete(self, text):
-        def skipQuotes(text):
-            quote = text[0]
-            text = text[1:]
-            endIndex = str(text).index(quote)
+        def skipQuotes(txt):
+            quote = txt[0]
+            text_val = txt[1:]
+            endIndex = str(text_val).index(quote)
             return text[endIndex:]
+
         while text:
             if text[0] in ('"', "'"):
                 try:
@@ -246,4 +280,94 @@ class FCShell(TermWidget):
         return True
 
     def child_exec_command(self, text):
-        self._sysShell.exec_command(text)
+        self.exec_command(text)
+
+    def exec_command(self, text, no_echo=False):
+        """
+        Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
+        Also handles execution in separated threads
+
+        :param text: FlatCAM TclCommand with parameters
+        :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
+        will create crashes of the _Expandable_Edit widget
+        :return: output if there was any
+        """
+
+        self._sysShell.report_usage('exec_command')
+
+        return self.exec_command_test(text, False, no_echo=no_echo)
+
+    def exec_command_test(self, text, reraise=True, no_echo=False):
+        """
+        Same as exec_command(...) with additional control over  exceptions.
+        Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
+
+        :param text: Input command
+        :param reraise: Re-raise TclError exceptions in Python (mostly for unittests).
+        :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
+        will create crashes of the _Expandable_Edit widget
+        :return: Output from the command
+        """
+
+        tcl_command_string = str(text)
+
+        try:
+            if no_echo is False:
+                self.open_processing()  # Disables input box.
+
+            result = self._sysShell.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'))
+            if no_echo is False:
+                self.append_error('ERROR: ' + result + '\n')
+            # Show error in console and just return or in test raise exception
+            if reraise:
+                raise e
+        finally:
+            if no_echo is False:
+                self.close_processing()
+            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")

+ 42 - 42
flatcamTools/ToolSolderPaste.py

@@ -7,7 +7,8 @@
 
 from FlatCAMTool import FlatCAMTool
 from FlatCAMCommon import LoudDict
-from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCTable, FCInputDialog, FCDoubleSpinner, FCSpinner
+from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCTable, \
+    FCInputDialog, FCDoubleSpinner, FCSpinner, FCFileSaveDialog
 from FlatCAMApp import log
 from camlib import distance
 from FlatCAMObj import FlatCAMCNCjob
@@ -61,7 +62,8 @@ class SolderPaste(FlatCAMTool):
         self.obj_combo = FCComboBox(callback=self.on_rmb_combo)
         self.obj_combo.setModel(self.app.collection)
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.obj_combo.setCurrentIndex(1)
+        self.obj_combo.is_last = True
+        self.obj_combo.obj_type = "Gerber"
 
         self.object_label = QtWidgets.QLabel("Gerber:   ")
         self.object_label.setToolTip(
@@ -105,7 +107,7 @@ class SolderPaste(FlatCAMTool):
         self.addtool_entry_lbl.setToolTip(
             _("Diameter for the new Nozzle tool to add in the Tool Table")
         )
-        self.addtool_entry = FCDoubleSpinner()
+        self.addtool_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.addtool_entry.set_range(0.0000001, 9999.9999)
         self.addtool_entry.set_precision(self.decimals)
         self.addtool_entry.setSingleStep(0.1)
@@ -174,7 +176,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_box.addLayout(self.gcode_form_layout)
 
         # Z dispense start
-        self.z_start_entry = FCDoubleSpinner()
+        self.z_start_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.z_start_entry.set_range(0.0000001, 9999.9999)
         self.z_start_entry.set_precision(self.decimals)
         self.z_start_entry.setSingleStep(0.1)
@@ -186,7 +188,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.z_start_label, self.z_start_entry)
 
         # Z dispense
-        self.z_dispense_entry = FCDoubleSpinner()
+        self.z_dispense_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.z_dispense_entry.set_range(0.0000001, 9999.9999)
         self.z_dispense_entry.set_precision(self.decimals)
         self.z_dispense_entry.setSingleStep(0.1)
@@ -198,7 +200,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.z_dispense_label, self.z_dispense_entry)
 
         # Z dispense stop
-        self.z_stop_entry = FCDoubleSpinner()
+        self.z_stop_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.z_stop_entry.set_range(0.0000001, 9999.9999)
         self.z_stop_entry.set_precision(self.decimals)
         self.z_stop_entry.setSingleStep(0.1)
@@ -210,7 +212,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.z_stop_label, self.z_stop_entry)
 
         # Z travel
-        self.z_travel_entry = FCDoubleSpinner()
+        self.z_travel_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.z_travel_entry.set_range(0.0000001, 9999.9999)
         self.z_travel_entry.set_precision(self.decimals)
         self.z_travel_entry.setSingleStep(0.1)
@@ -223,7 +225,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.z_travel_label, self.z_travel_entry)
 
         # Z toolchange location
-        self.z_toolchange_entry = FCDoubleSpinner()
+        self.z_toolchange_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.z_toolchange_entry.set_range(0.0000001, 9999.9999)
         self.z_toolchange_entry.set_precision(self.decimals)
         self.z_toolchange_entry.setSingleStep(0.1)
@@ -244,8 +246,8 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.xy_toolchange_label, self.xy_toolchange_entry)
 
         # Feedrate X-Y
-        self.frxy_entry = FCDoubleSpinner()
-        self.frxy_entry.set_range(0.0000001, 9999.9999)
+        self.frxy_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.frxy_entry.set_range(0.0000, 99999.9999)
         self.frxy_entry.set_precision(self.decimals)
         self.frxy_entry.setSingleStep(0.1)
 
@@ -256,8 +258,8 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.frxy_label, self.frxy_entry)
 
         # Feedrate Z
-        self.frz_entry = FCDoubleSpinner()
-        self.frz_entry.set_range(0.0000001, 9999.9999)
+        self.frz_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.frz_entry.set_range(0.0000, 99999.9999)
         self.frz_entry.set_precision(self.decimals)
         self.frz_entry.setSingleStep(0.1)
 
@@ -269,8 +271,8 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.frz_label, self.frz_entry)
 
         # Feedrate Z Dispense
-        self.frz_dispense_entry = FCDoubleSpinner()
-        self.frz_dispense_entry.set_range(0.0000001, 9999.9999)
+        self.frz_dispense_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.frz_dispense_entry.set_range(0.0000, 99999.9999)
         self.frz_dispense_entry.set_precision(self.decimals)
         self.frz_dispense_entry.setSingleStep(0.1)
 
@@ -282,9 +284,9 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.frz_dispense_label, self.frz_dispense_entry)
 
         # Spindle Speed Forward
-        self.speedfwd_entry = FCSpinner()
+        self.speedfwd_entry = FCSpinner(callback=self.confirmation_message_int)
         self.speedfwd_entry.set_range(0, 999999)
-        self.speedfwd_entry.setSingleStep(1000)
+        self.speedfwd_entry.set_step(1000)
 
         self.speedfwd_label = QtWidgets.QLabel('%s:' % _("Spindle Speed FWD"))
         self.speedfwd_label.setToolTip(
@@ -294,7 +296,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.speedfwd_label, self.speedfwd_entry)
 
         # Dwell Forward
-        self.dwellfwd_entry = FCDoubleSpinner()
+        self.dwellfwd_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.dwellfwd_entry.set_range(0.0000001, 9999.9999)
         self.dwellfwd_entry.set_precision(self.decimals)
         self.dwellfwd_entry.setSingleStep(0.1)
@@ -306,9 +308,9 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.dwellfwd_label, self.dwellfwd_entry)
 
         # Spindle Speed Reverse
-        self.speedrev_entry = FCSpinner()
+        self.speedrev_entry = FCSpinner(callback=self.confirmation_message_int)
         self.speedrev_entry.set_range(0, 999999)
-        self.speedrev_entry.setSingleStep(1000)
+        self.speedrev_entry.set_step(1000)
 
         self.speedrev_label = QtWidgets.QLabel('%s:' % _("Spindle Speed REV"))
         self.speedrev_label.setToolTip(
@@ -318,7 +320,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.speedrev_label, self.speedrev_entry)
 
         # Dwell Reverse
-        self.dwellrev_entry = FCDoubleSpinner()
+        self.dwellrev_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.dwellrev_entry.set_range(0.0000001, 9999.9999)
         self.dwellrev_entry.set_precision(self.decimals)
         self.dwellrev_entry.setSingleStep(0.1)
@@ -341,8 +343,8 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(pp_label, self.pp_combo)
 
         # ## Buttons
-        grid1 = QtWidgets.QGridLayout()
-        self.gcode_box.addLayout(grid1)
+        # grid1 = QtWidgets.QGridLayout()
+        # self.gcode_box.addLayout(grid1)
 
         self.solder_gcode_btn = QtWidgets.QPushButton(_("Generate GCode"))
         self.solder_gcode_btn.setToolTip(
@@ -383,7 +385,8 @@ class SolderPaste(FlatCAMTool):
         self.geo_obj_combo = FCComboBox(callback=self.on_rmb_combo)
         self.geo_obj_combo.setModel(self.app.collection)
         self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
-        self.geo_obj_combo.setCurrentIndex(1)
+        self.geo_obj_combo.is_last = True
+        self.geo_obj_combo.obj_type = "Geometry"
 
         self.geo_object_label = QtWidgets.QLabel('%s:' % _("Geo Result"))
         self.geo_object_label.setToolTip(
@@ -416,7 +419,8 @@ class SolderPaste(FlatCAMTool):
         self.cnc_obj_combo = FCComboBox(callback=self.on_rmb_combo)
         self.cnc_obj_combo.setModel(self.app.collection)
         self.cnc_obj_combo.setRootModelIndex(self.app.collection.index(3, 0, QtCore.QModelIndex()))
-        self.cnc_obj_combo.setCurrentIndex(1)
+        self.cnc_obj_combo.is_last = True
+        self.geo_obj_combo.obj_type = "CNCJob"
 
         self.cnc_object_label = QtWidgets.QLabel('%s:' % _("CNC Result"))
         self.cnc_object_label.setToolTip(
@@ -491,6 +495,8 @@ class SolderPaste(FlatCAMTool):
         self.units = ''
         self.name = ""
 
+        self.obj = None
+
         self.text_editor_tab = None
 
         # this will be used in the combobox context menu, for delete entry
@@ -547,7 +553,7 @@ class SolderPaste(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("SolderPaste Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+K', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+K', **kwargs)
 
     def on_add_tool_by_key(self):
         tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
@@ -652,10 +658,10 @@ class SolderPaste(FlatCAMTool):
             for tooluid_key, tooluid_value in self.tooltable_tools.items():
                 if float('%.*f' % (self.decimals, tooluid_value['tooldia'])) == tool_sorted:
                     tool_id += 1
-                    id = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
-                    id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+                    id_item = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
+                    id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
                     row_no = tool_id - 1
-                    self.tools_table.setItem(row_no, 0, id)  # Tool name/id
+                    self.tools_table.setItem(row_no, 0, id_item)  # Tool name/id
 
                     # Make sure that the drill diameter when in MM is with no more than 2 decimals
                     # There are no drill bits in MM with more than 2 decimals diameter
@@ -908,14 +914,12 @@ class SolderPaste(FlatCAMTool):
 
         if float('%.*f' % (self.decimals, tool_dia)) in tool_dias:
             if muted is None:
-                self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                     _("Adding Nozzle tool cancelled. Tool already in Tool Table."))
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Tool already in Tool Table."))
             self.tools_table.itemChanged.connect(self.on_tool_edit)
             return
         else:
             if muted is None:
-                self.app.inform.emit('[success] %s' %
-                                     _("New Nozzle tool added to Tool Table."))
+                self.app.inform.emit('[success] %s' % _("New Nozzle tool added to Tool Table."))
             self.tooltable_tools.update({
                 int(self.tooluid): {
                     'tooldia': float('%.*f' % (self.decimals, tool_dia)),
@@ -970,7 +974,7 @@ class SolderPaste(FlatCAMTool):
                 restore_dia_item = self.tools_table.item(row, 1)
                 restore_dia_item.setText(str(old_tool_dia))
                 self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                     _("Edit cancelled. New diameter value is already in the Tool Table."))
+                                     _("Cancelled. New diameter value is already in the Tool Table."))
         self.build_ui()
 
     def on_tool_delete(self, rows_to_delete=None, all=None):
@@ -1235,12 +1239,10 @@ class SolderPaste(FlatCAMTool):
                         if not geo_obj.tools[tooluid_key]['solid_geometry']:
                             a += 1
                     if a == len(geo_obj.tools):
-                        self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                             _('Cancelled. Empty file, it has no geometry...'))
+                        self.app.inform.emit('[ERROR_NOTCL] %s' % _('Cancelled. Empty file, it has no geometry...'))
                         return 'fail'
 
-                    app_obj.inform.emit('[success] %s...' %
-                                        _("Solder Paste geometry generated successfully"))
+                    app_obj.inform.emit('[success] %s...' % _("Solder Paste geometry generated successfully"))
                     return
 
             # if we still have geometry not processed at the end of the tools then we failed
@@ -1295,7 +1297,7 @@ class SolderPaste(FlatCAMTool):
             if obj.tools[tooluid_key]['solid_geometry'] is None:
                 a += 1
         if a == len(obj.tools):
-            self.app.inform.emit('[ERROR_NOTCL] %s...' %  _('Cancelled. Empty file, it has no geometry'))
+            self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry'))
             return 'fail'
 
         # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
@@ -1334,8 +1336,6 @@ class SolderPaste(FlatCAMTool):
             assert isinstance(job_obj, FlatCAMCNCjob), \
                 "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
 
-            tool_cnc_dict = {}
-
             # this turn on the FlatCAMCNCJob plot for multiple tools
             job_obj.multitool = True
             job_obj.multigeo = True
@@ -1489,13 +1489,13 @@ class SolderPaste(FlatCAMTool):
 
         try:
             dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+            filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export GCode ..."),
                 directory=dir_file_to_save,
                 filter=_filter_
             )
         except TypeError:
-            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export Machine Code ..."), filter=_filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_)
 
         if filename == '':
             self.app.inform.emit('[WARNING_NOTCL] %s' %

+ 28 - 22
flatcamTools/ToolSub.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import FCCheckBox, FCButton
+from flatcamGUI.GUIElements import FCCheckBox, FCButton, FCComboBox
 
 from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString
 from shapely.ops import cascaded_union
@@ -66,10 +66,12 @@ class ToolSub(FlatCAMTool):
         form_layout.addRow(self.gerber_title)
 
         # Target Gerber Object
-        self.target_gerber_combo = QtWidgets.QComboBox()
+        self.target_gerber_combo = FCComboBox()
         self.target_gerber_combo.setModel(self.app.collection)
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.target_gerber_combo.setCurrentIndex(1)
+        # self.target_gerber_combo.setCurrentIndex(1)
+        self.target_gerber_combo.is_last = True
+        self.target_gerber_combo.obj_type = "Gerber"
 
         self.target_gerber_label = QtWidgets.QLabel('%s:' % _("Target"))
         self.target_gerber_label.setToolTip(
@@ -80,10 +82,11 @@ class ToolSub(FlatCAMTool):
         form_layout.addRow(self.target_gerber_label, self.target_gerber_combo)
 
         # Substractor Gerber Object
-        self.sub_gerber_combo = QtWidgets.QComboBox()
+        self.sub_gerber_combo = FCComboBox()
         self.sub_gerber_combo.setModel(self.app.collection)
         self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.sub_gerber_combo.setCurrentIndex(1)
+        self.sub_gerber_combo.is_last = True
+        self.sub_gerber_combo.obj_type = "Gerber"
 
         self.sub_gerber_label = QtWidgets.QLabel('%s:' % _("Subtractor"))
         self.sub_gerber_label.setToolTip(
@@ -118,10 +121,12 @@ class ToolSub(FlatCAMTool):
         form_geo_layout.addRow(self.geo_title)
 
         # Target Geometry Object
-        self.target_geo_combo = QtWidgets.QComboBox()
+        self.target_geo_combo = FCComboBox()
         self.target_geo_combo.setModel(self.app.collection)
         self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
-        self.target_geo_combo.setCurrentIndex(1)
+        # self.target_geo_combo.setCurrentIndex(1)
+        self.target_geo_combo.is_last = True
+        self.target_geo_combo.obj_type = "Geometry"
 
         self.target_geo_label = QtWidgets.QLabel('%s:' % _("Target"))
         self.target_geo_label.setToolTip(
@@ -132,10 +137,11 @@ class ToolSub(FlatCAMTool):
         form_geo_layout.addRow(self.target_geo_label, self.target_geo_combo)
 
         # Substractor Geometry Object
-        self.sub_geo_combo = QtWidgets.QComboBox()
+        self.sub_geo_combo = FCComboBox()
         self.sub_geo_combo.setModel(self.app.collection)
         self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
-        self.sub_geo_combo.setCurrentIndex(1)
+        self.sub_geo_combo.is_last = True
+        self.sub_geo_combo.obj_type = "Geometry"
 
         self.sub_geo_label = QtWidgets.QLabel('%s:' % _("Subtractor"))
         self.sub_geo_label.setToolTip(
@@ -227,7 +233,7 @@ class ToolSub(FlatCAMTool):
         self.reset_button.clicked.connect(self.set_tool_ui)
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+W', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+W', **kwargs)
 
     def run(self, toggle=True):
         self.app.report_usage("ToolSub()")
@@ -254,14 +260,14 @@ class ToolSub(FlatCAMTool):
         FlatCAMTool.run(self)
         self.set_tool_ui()
 
+        self.app.ui.notebook.setTabText(2, _("Sub Tool"))
+
+    def set_tool_ui(self):
         self.new_apertures.clear()
         self.new_tools.clear()
         self.new_solid_geometry = []
         self.target_options.clear()
 
-        self.app.ui.notebook.setTabText(2, _("Sub Tool"))
-
-    def set_tool_ui(self):
         self.tools_frame.show()
         self.close_paths_cb.setChecked(self.app.defaults["tools_sub_close_paths"])
 
@@ -303,14 +309,14 @@ class ToolSub(FlatCAMTool):
 
         # crate the new_apertures dict structure
         for apid in self.target_grb_obj.apertures:
-            self.new_apertures[apid] = dict()
+            self.new_apertures[apid] = {}
             self.new_apertures[apid]['type'] = 'C'
             self.new_apertures[apid]['size'] = self.target_grb_obj.apertures[apid]['size']
-            self.new_apertures[apid]['geometry'] = list()
+            self.new_apertures[apid]['geometry'] = []
 
-        geo_solid_union_list = list()
-        geo_follow_union_list = list()
-        geo_clear_union_list = list()
+        geo_solid_union_list = []
+        geo_follow_union_list = []
+        geo_clear_union_list = []
 
         for apid1 in self.sub_grb_obj.apertures:
             if 'geometry' in self.sub_grb_obj.apertures[apid1]:
@@ -339,14 +345,14 @@ class ToolSub(FlatCAMTool):
             self.app.worker_task.emit({'fcn': self.aperture_intersection, 'params': [apid, geo]})
 
     def aperture_intersection(self, apid, geo):
-        new_geometry = list()
+        new_geometry = []
 
         log.debug("Working on promise: %s" % str(apid))
 
         with self.app.proc_container.new('%s: %s...' % (_("Parsing geometry for aperture"), str(apid))):
 
             for geo_el in geo:
-                new_el = dict()
+                new_el = {}
 
                 if 'solid' in geo_el:
                     work_geo = geo_el['solid']
@@ -513,14 +519,14 @@ class ToolSub(FlatCAMTool):
             return
 
         # create the target_options obj
-        # self.target_options = dict()
+        # self.target_options = {}
         # for k, v in self.target_geo_obj.options.items():
         #     if k != 'name':
         #         self.target_options[k] = v
 
         # crate the new_tools dict structure
         for tool in self.target_geo_obj.tools:
-            self.new_tools[tool] = dict()
+            self.new_tools[tool] = {}
             for key in self.target_geo_obj.tools[tool]:
                 if key == 'solid_geometry':
                     self.new_tools[tool][key] = []

+ 52 - 38
flatcamTools/ToolTransform.py

@@ -33,8 +33,6 @@ class ToolTransform(FlatCAMTool):
         FlatCAMTool.__init__(self, app)
         self.decimals = self.app.decimals
 
-        self.transform_lay = QtWidgets.QVBoxLayout()
-        self.layout.addLayout(self.transform_lay)
         # ## Title
         title_label = QtWidgets.QLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
@@ -44,12 +42,12 @@ class ToolTransform(FlatCAMTool):
                             font-weight: bold;
                         }
                         """)
-        self.transform_lay.addWidget(title_label)
-        self.transform_lay.addWidget(QtWidgets.QLabel(''))
+        self.layout.addWidget(title_label)
+        self.layout.addWidget(QtWidgets.QLabel(''))
 
         # ## Layout
         grid0 = QtWidgets.QGridLayout()
-        self.transform_lay.addLayout(grid0)
+        self.layout.addLayout(grid0)
         grid0.setColumnStretch(0, 0)
         grid0.setColumnStretch(1, 1)
         grid0.setColumnStretch(2, 0)
@@ -68,7 +66,7 @@ class ToolTransform(FlatCAMTool):
               "Negative numbers for CCW motion.")
         )
 
-        self.rotate_entry = FCDoubleSpinner()
+        self.rotate_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.rotate_entry.set_precision(self.decimals)
         self.rotate_entry.setSingleStep(45)
         self.rotate_entry.setWrapping(True)
@@ -77,7 +75,6 @@ class ToolTransform(FlatCAMTool):
         # self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
 
         self.rotate_button = FCButton()
-        self.rotate_button.set_value(_("Rotate"))
         self.rotate_button.setToolTip(
             _("Rotate the selected object(s).\n"
               "The point of reference is the middle of\n"
@@ -103,13 +100,12 @@ class ToolTransform(FlatCAMTool):
             _("Angle for Skew action, in degrees.\n"
               "Float number between -360 and 360.")
         )
-        self.skewx_entry = FCDoubleSpinner()
+        self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.skewx_entry.set_precision(self.decimals)
         self.skewx_entry.set_range(-360, 360)
 
         self.skewx_button = FCButton()
-        self.skewx_button.set_value(_("Skew X"))
         self.skewx_button.setToolTip(
             _("Skew/shear the selected object(s).\n"
               "The point of reference is the middle of\n"
@@ -125,13 +121,12 @@ class ToolTransform(FlatCAMTool):
             _("Angle for Skew action, in degrees.\n"
               "Float number between -360 and 360.")
         )
-        self.skewy_entry = FCDoubleSpinner()
+        self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.skewy_entry.set_precision(self.decimals)
         self.skewy_entry.set_range(-360, 360)
 
         self.skewy_button = FCButton()
-        self.skewy_button.set_value(_("Skew Y"))
         self.skewy_button.setToolTip(
             _("Skew/shear the selected object(s).\n"
               "The point of reference is the middle of\n"
@@ -155,13 +150,12 @@ class ToolTransform(FlatCAMTool):
         self.scalex_label.setToolTip(
             _("Factor for scaling on X axis.")
         )
-        self.scalex_entry = FCDoubleSpinner()
+        self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.scalex_entry.set_precision(self.decimals)
         self.scalex_entry.setMinimum(-1e6)
 
         self.scalex_button = FCButton()
-        self.scalex_button.set_value(_("Scale X"))
         self.scalex_button.setToolTip(
             _("Scale the selected object(s).\n"
               "The point of reference depends on \n"
@@ -176,13 +170,12 @@ class ToolTransform(FlatCAMTool):
         self.scaley_label.setToolTip(
             _("Factor for scaling on Y axis.")
         )
-        self.scaley_entry = FCDoubleSpinner()
+        self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.scaley_entry.set_precision(self.decimals)
         self.scaley_entry.setMinimum(-1e6)
 
         self.scaley_button = FCButton()
-        self.scaley_button.set_value(_("Scale Y"))
         self.scaley_button.setToolTip(
             _("Scale the selected object(s).\n"
               "The point of reference depends on \n"
@@ -194,7 +187,6 @@ class ToolTransform(FlatCAMTool):
         grid0.addWidget(self.scaley_button, 9, 2)
 
         self.scale_link_cb = FCCheckBox()
-        self.scale_link_cb.set_value(True)
         self.scale_link_cb.setText(_("Link"))
         self.scale_link_cb.setToolTip(
             _("Scale the selected object(s)\n"
@@ -202,7 +194,6 @@ class ToolTransform(FlatCAMTool):
         )
 
         self.scale_zero_ref_cb = FCCheckBox()
-        self.scale_zero_ref_cb.set_value(True)
         self.scale_zero_ref_cb.setText('%s' % _("Scale Reference"))
         self.scale_zero_ref_cb.setToolTip(
             _("Scale the selected object(s)\n"
@@ -213,7 +204,7 @@ class ToolTransform(FlatCAMTool):
         self.ois_scale = OptionalInputSection(self.scale_link_cb, [self.scaley_entry, self.scaley_button], logic=False)
 
         grid0.addWidget(self.scale_link_cb, 10, 0)
-        grid0.addWidget(self.scale_zero_ref_cb, 10, 1)
+        grid0.addWidget(self.scale_zero_ref_cb, 10, 1, 1, 2)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -228,13 +219,12 @@ class ToolTransform(FlatCAMTool):
         self.offx_label.setToolTip(
             _("Distance to offset on X axis. In current units.")
         )
-        self.offx_entry = FCDoubleSpinner()
+        self.offx_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.offx_entry.set_precision(self.decimals)
         self.offx_entry.setMinimum(-1e6)
 
         self.offx_button = FCButton()
-        self.offx_button.set_value(_("Offset X"))
         self.offx_button.setToolTip(
             _("Offset the selected object(s).\n"
               "The point of reference is the middle of\n"
@@ -249,13 +239,12 @@ class ToolTransform(FlatCAMTool):
         self.offy_label.setToolTip(
             _("Distance to offset on Y axis. In current units.")
         )
-        self.offy_entry = FCDoubleSpinner()
+        self.offy_entry = FCDoubleSpinner(callback=self.confirmation_message)
         # self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.offy_entry.set_precision(self.decimals)
         self.offy_entry.setMinimum(-1e6)
 
         self.offy_button = FCButton()
-        self.offy_button.set_value(_("Offset Y"))
         self.offy_button.setToolTip(
             _("Offset the selected object(s).\n"
               "The point of reference is the middle of\n"
@@ -276,13 +265,11 @@ class ToolTransform(FlatCAMTool):
         grid0.addWidget(flip_title_label, 16, 0, 1, 3)
 
         self.flipx_button = FCButton()
-        self.flipx_button.set_value(_("Flip on X"))
         self.flipx_button.setToolTip(
             _("Flip the selected object(s) over the X axis.")
         )
 
         self.flipy_button = FCButton()
-        self.flipy_button.set_value(_("Flip on Y"))
         self.flipy_button.setToolTip(
             _("Flip the selected object(s) over the X axis.")
         )
@@ -294,7 +281,6 @@ class ToolTransform(FlatCAMTool):
         hlay0.addWidget(self.flipy_button)
 
         self.flip_ref_cb = FCCheckBox()
-        self.flip_ref_cb.set_value(True)
         self.flip_ref_cb.setText('%s' % _("Mirror Reference"))
         self.flip_ref_cb.setToolTip(
             _("Flip the selected object(s)\n"
@@ -320,7 +306,6 @@ class ToolTransform(FlatCAMTool):
         # self.flip_ref_entry.setFixedWidth(70)
 
         self.flip_ref_button = FCButton()
-        self.flip_ref_button.set_value(_("Add"))
         self.flip_ref_button.setToolTip(
             _("The point coordinates can be captured by\n"
               "left click on canvas together with pressing\n"
@@ -353,14 +338,13 @@ class ToolTransform(FlatCAMTool):
               "or decreased with the 'distance'.")
         )
 
-        self.buffer_entry = FCDoubleSpinner()
+        self.buffer_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.buffer_entry.set_precision(self.decimals)
         self.buffer_entry.setSingleStep(0.1)
         self.buffer_entry.setWrapping(True)
         self.buffer_entry.set_range(-9999.9999, 9999.9999)
 
         self.buffer_button = FCButton()
-        self.buffer_button.set_value(_("Buffer D"))
         self.buffer_button.setToolTip(
             _("Create the buffer effect on each geometry,\n"
               "element from the selected object, using the distance.")
@@ -380,14 +364,13 @@ class ToolTransform(FlatCAMTool):
               "of the initial dimension.")
         )
 
-        self.buffer_factor_entry = FCDoubleSpinner(suffix='%')
+        self.buffer_factor_entry = FCDoubleSpinner(callback=self.confirmation_message, suffix='%')
         self.buffer_factor_entry.set_range(-100.0000, 1000.0000)
         self.buffer_factor_entry.set_precision(self.decimals)
         self.buffer_factor_entry.setWrapping(True)
         self.buffer_factor_entry.setSingleStep(1)
 
         self.buffer_factor_button = FCButton()
-        self.buffer_factor_button.set_value(_("Buffer F"))
         self.buffer_factor_button.setToolTip(
             _("Create the buffer effect on each geometry,\n"
               "element from the selected object, using the factor.")
@@ -410,7 +393,20 @@ class ToolTransform(FlatCAMTool):
 
         grid0.addWidget(QtWidgets.QLabel(''), 26, 0, 1, 3)
 
-        self.transform_lay.addStretch()
+        self.layout.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.reset_button)
 
         # ## Signals
         self.rotate_button.clicked.connect(self.on_rotate)
@@ -426,6 +422,8 @@ class ToolTransform(FlatCAMTool):
         self.buffer_button.clicked.connect(self.on_buffer_by_distance)
         self.buffer_factor_button.clicked.connect(self.on_buffer_by_factor)
 
+        self.reset_button.clicked.connect(self.set_tool_ui)
+
         # self.rotate_entry.returnPressed.connect(self.on_rotate)
         # self.skewx_entry.returnPressed.connect(self.on_skewx)
         # self.skewy_entry.returnPressed.connect(self.on_skewy)
@@ -463,9 +461,25 @@ class ToolTransform(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Transform Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='ALT+T', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
 
     def set_tool_ui(self):
+        self.rotate_button.set_value(_("Rotate"))
+        self.skewx_button.set_value(_("Skew X"))
+        self.skewy_button.set_value(_("Skew Y"))
+        self.scalex_button.set_value(_("Scale X"))
+        self.scaley_button.set_value(_("Scale Y"))
+        self.scale_link_cb.set_value(True)
+        self.scale_zero_ref_cb.set_value(True)
+        self.offx_button.set_value(_("Offset X"))
+        self.offy_button.set_value(_("Offset Y"))
+        self.flipx_button.set_value(_("Flip on X"))
+        self.flipy_button.set_value(_("Flip on Y"))
+        self.flip_ref_cb.set_value(True)
+        self.flip_ref_button.set_value(_("Add"))
+        self.buffer_button.set_value(_("Buffer D"))
+        self.buffer_factor_button.set_value(_("Buffer F"))
+
         # ## Initialize form
         if self.app.defaults["tools_transform_rotate"]:
             self.rotate_entry.set_value(self.app.defaults["tools_transform_rotate"])
@@ -744,7 +758,7 @@ class ToolTransform(FlatCAMTool):
                         if isinstance(sel_obj, FlatCAMCNCjob):
                             self.app.inform.emit(_("CNCJob objects can't be mirrored/flipped."))
                         else:
-                            if axis is 'X':
+                            if axis == 'X':
                                 sel_obj.mirror('X', (px, py))
                                 # add information to the object that it was changed and how much
                                 # the axis is reversed because of the reference
@@ -754,7 +768,7 @@ class ToolTransform(FlatCAMTool):
                                     sel_obj.options['mirror_y'] = True
                                 self.app.inform.emit('[success] %s...' %
                                                      _('Flip on the Y axis done'))
-                            elif axis is 'Y':
+                            elif axis == 'Y':
                                 sel_obj.mirror('Y', (px, py))
                                 # add information to the object that it was changed and how much
                                 # the axis is reversed because of the reference
@@ -804,11 +818,11 @@ class ToolTransform(FlatCAMTool):
                         if isinstance(sel_obj, FlatCAMCNCjob):
                             self.app.inform.emit(_("CNCJob objects can't be skewed."))
                         else:
-                            if axis is 'X':
+                            if axis == 'X':
                                 sel_obj.skew(num, 0, point=(xminimal, yminimal))
                                 # add information to the object that it was changed and how much
                                 sel_obj.options['skew_x'] = num
-                            elif axis is 'Y':
+                            elif axis == 'Y':
                                 sel_obj.skew(0, num, point=(xminimal, yminimal))
                                 # add information to the object that it was changed and how much
                                 sel_obj.options['skew_y'] = num
@@ -890,11 +904,11 @@ class ToolTransform(FlatCAMTool):
                         if isinstance(sel_obj, FlatCAMCNCjob):
                             self.app.inform.emit(_("CNCJob objects can't be offset."))
                         else:
-                            if axis is 'X':
+                            if axis == 'X':
                                 sel_obj.offset((num, 0))
                                 # add information to the object that it was changed and how much
                                 sel_obj.options['offset_x'] = num
-                            elif axis is 'Y':
+                            elif axis == 'Y':
                                 sel_obj.offset((0, num))
                                 # add information to the object that it was changed and how much
                                 sel_obj.options['offset_y'] = num

+ 7 - 3
flatcamTools/__init__.py

@@ -1,11 +1,12 @@
 import sys
 
-
 from flatcamTools.ToolCalculators import ToolCalculator
 from flatcamTools.ToolCalibration import ToolCalibration
 from flatcamTools.ToolCutOut import CutOut
 
 from flatcamTools.ToolDblSided import DblSidedTool
+from flatcamTools.ToolExtractDrills import ToolExtractDrills
+from flatcamTools.ToolAlignObjects import AlignObjects
 
 from flatcamTools.ToolFilm import Film
 
@@ -16,11 +17,11 @@ from flatcamTools.ToolDistanceMin import DistanceMin
 
 from flatcamTools.ToolMove import ToolMove
 
-from flatcamTools.ToolNonCopperClear import NonCopperClear
+from flatcamTools.ToolNCC import NonCopperClear
+from flatcamTools.ToolPaint import ToolPaint
 
 from flatcamTools.ToolOptimal import ToolOptimal
 
-from flatcamTools.ToolPaint import ToolPaint
 from flatcamTools.ToolPanelize import Panelize
 from flatcamTools.ToolPcbWizard import PcbWizard
 from flatcamTools.ToolPDF import ToolPDF
@@ -37,3 +38,6 @@ from flatcamTools.ToolSolderPaste import SolderPaste
 from flatcamTools.ToolSub import ToolSub
 
 from flatcamTools.ToolTransform import ToolTransform
+from flatcamTools.ToolPunchGerber import ToolPunchGerber
+
+from flatcamTools.ToolInvertGerber import ToolInvertGerber

+ 143 - 143
locale/de/LC_MESSAGES/strings.po

@@ -5318,7 +5318,7 @@ msgid "File"
 msgstr "Datei"
 
 #: flatcamGUI/FlatCAMGUI.py:69
-msgid "&New Project ...\tCTRL+N"
+msgid "&New Project ...\tCtrl+N"
 msgstr "&Neues Projekt ...\\STRG+N"
 
 #: flatcamGUI/FlatCAMGUI.py:71
@@ -5371,11 +5371,11 @@ msgid "Open &Project ..."
 msgstr "&Projekt öffnen..."
 
 #: flatcamGUI/FlatCAMGUI.py:109 flatcamGUI/FlatCAMGUI.py:4121
-msgid "Open &Gerber ...\tCTRL+G"
+msgid "Open &Gerber ...\tCtrl+G"
 msgstr "&Gerber öffnen...\\STRG+G"
 
 #: flatcamGUI/FlatCAMGUI.py:114 flatcamGUI/FlatCAMGUI.py:4126
-msgid "Open &Excellon ...\tCTRL+E"
+msgid "Open &Excellon ...\tCtrl+E"
 msgstr "&Excellon öffnen...\\STRG+E"
 
 #: flatcamGUI/FlatCAMGUI.py:118 flatcamGUI/FlatCAMGUI.py:4131
@@ -5527,7 +5527,7 @@ msgid "&Save Project ..."
 msgstr "Projekt speichern ..."
 
 #: flatcamGUI/FlatCAMGUI.py:256
-msgid "Save Project &As ...\tCTRL+S"
+msgid "Save Project &As ...\tCtrl+S"
 msgstr "Projekt speichern als ...\\STRG+S"
 
 #: flatcamGUI/FlatCAMGUI.py:261
@@ -5548,7 +5548,7 @@ msgid "Edit Object\tE"
 msgstr "Objekt bearbeiten\tE"
 
 #: flatcamGUI/FlatCAMGUI.py:285
-msgid "Close Editor\tCTRL+S"
+msgid "Close Editor\tCtrl+S"
 msgstr "Schließen Sie Editor\tSTRG+S"
 
 #: flatcamGUI/FlatCAMGUI.py:294
@@ -5626,7 +5626,7 @@ msgid "Convert Any to Gerber"
 msgstr "Konvertieren Sie Any zu Gerber"
 
 #: flatcamGUI/FlatCAMGUI.py:341
-msgid "&Copy\tCTRL+C"
+msgid "&Copy\tCtrl+C"
 msgstr "Kopieren\tSTRG+C"
 
 #: flatcamGUI/FlatCAMGUI.py:346
@@ -5646,28 +5646,28 @@ msgid "Toggle Units\tQ"
 msgstr "Einheiten umschalten\tQ"
 
 #: flatcamGUI/FlatCAMGUI.py:360
-msgid "&Select All\tCTRL+A"
+msgid "&Select All\tCtrl+A"
 msgstr "Alles auswählen\tSTRG+A"
 
 #: flatcamGUI/FlatCAMGUI.py:365
-msgid "&Preferences\tSHIFT+P"
-msgstr "Einstellungen\tSHIFT+P"
+msgid "&Preferences\tShift+P"
+msgstr "Einstellungen\tShift+P"
 
 #: flatcamGUI/FlatCAMGUI.py:371 flatcamTools/ToolProperties.py:153
 msgid "Options"
 msgstr "Optionen"
 
 #: flatcamGUI/FlatCAMGUI.py:373
-msgid "&Rotate Selection\tSHIFT+(R)"
-msgstr "Auswahl drehen\tSHIFT+(R)"
+msgid "&Rotate Selection\tShift+(R)"
+msgstr "Auswahl drehen\tShift+(R)"
 
 #: flatcamGUI/FlatCAMGUI.py:378
-msgid "&Skew on X axis\tSHIFT+X"
-msgstr "Neigung auf der X-Achse\tSHIFT+X"
+msgid "&Skew on X axis\tShift+X"
+msgstr "Neigung auf der X-Achse\tShift+X"
 
 #: flatcamGUI/FlatCAMGUI.py:380
-msgid "S&kew on Y axis\tSHIFT+Y"
-msgstr "Neigung auf der Y-Achse\tSHIFT+Y"
+msgid "S&kew on Y axis\tShift+Y"
+msgstr "Neigung auf der Y-Achse\tShift+Y"
 
 #: flatcamGUI/FlatCAMGUI.py:385
 msgid "Flip on &X axis\tX"
@@ -5678,11 +5678,11 @@ msgid "Flip on &Y axis\tY"
 msgstr "Y-Achse kippen\tY"
 
 #: flatcamGUI/FlatCAMGUI.py:392
-msgid "View source\tALT+S"
-msgstr "Quelltext anzeigen\tALT+S"
+msgid "View source\tAlt+S"
+msgstr "Quelltext anzeigen\tAlt+S"
 
 #: flatcamGUI/FlatCAMGUI.py:394
-msgid "Tools DataBase\tCTRL+D"
+msgid "Tools DataBase\tCtrl+D"
 msgstr "Werkzeugdatenbank\tSTRG+D"
 
 #: flatcamGUI/FlatCAMGUI.py:401 flatcamGUI/FlatCAMGUI.py:2060
@@ -5690,16 +5690,16 @@ msgid "View"
 msgstr "Aussicht"
 
 #: flatcamGUI/FlatCAMGUI.py:403
-msgid "Enable all plots\tALT+1"
-msgstr "Alle Diagramme aktivieren\tALT+1"
+msgid "Enable all plots\tAlt+1"
+msgstr "Alle Diagramme aktivieren\tAlt+1"
 
 #: flatcamGUI/FlatCAMGUI.py:405
-msgid "Disable all plots\tALT+2"
-msgstr "Alle Diagramme deaktivieren\tALT+2"
+msgid "Disable all plots\tAlt+2"
+msgstr "Alle Diagramme deaktivieren\tAlt+2"
 
 #: flatcamGUI/FlatCAMGUI.py:407
-msgid "Disable non-selected\tALT+3"
-msgstr "Nicht ausgewählte Diagramme deaktivieren\tALT+3"
+msgid "Disable non-selected\tAlt+3"
+msgstr "Nicht ausgewählte Diagramme deaktivieren\tAlt+3"
 
 #: flatcamGUI/FlatCAMGUI.py:411
 msgid "&Zoom Fit\tV"
@@ -5718,15 +5718,15 @@ msgid "Redraw All\tF5"
 msgstr "Alles neu zeichnen\tF5"
 
 #: flatcamGUI/FlatCAMGUI.py:424
-msgid "Toggle Code Editor\tSHIFT+E"
-msgstr "Code-Editor umschalten\tSHIFT+E"
+msgid "Toggle Code Editor\tShift+E"
+msgstr "Code-Editor umschalten\tShift+E"
 
 #: flatcamGUI/FlatCAMGUI.py:427
-msgid "&Toggle FullScreen\tALT+F10"
-msgstr "FullScreen umschalten\tALT+F10"
+msgid "&Toggle FullScreen\tAlt+F10"
+msgstr "FullScreen umschalten\tAlt+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:429
-msgid "&Toggle Plot Area\tCTRL+F10"
+msgid "&Toggle Plot Area\tCtrl+F10"
 msgstr "Plotbereich umschalten\tSTRG+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:431
@@ -5738,16 +5738,16 @@ msgid "&Toggle Grid Snap\tG"
 msgstr "Schaltet den Rasterfang ein\tG"
 
 #: flatcamGUI/FlatCAMGUI.py:437
-msgid "&Toggle Grid Lines\tALT+G"
-msgstr "Gitterlinien umschalten\tALT+G"
+msgid "&Toggle Grid Lines\tAlt+G"
+msgstr "Gitterlinien umschalten\tAlt+G"
 
 #: flatcamGUI/FlatCAMGUI.py:439
-msgid "&Toggle Axis\tSHIFT+G"
-msgstr "Achse umschalten\tSHIFT+G"
+msgid "&Toggle Axis\tShift+G"
+msgstr "Achse umschalten\tShift+G"
 
 #: flatcamGUI/FlatCAMGUI.py:441
-msgid "Toggle Workspace\tSHIFT+W"
-msgstr "Arbeitsbereich umschalten\tSHIFT+W"
+msgid "Toggle Workspace\tShift+W"
+msgstr "Arbeitsbereich umschalten\tShift+W"
 
 #: flatcamGUI/FlatCAMGUI.py:446
 msgid "Objects"
@@ -5846,8 +5846,8 @@ msgid "Paint Tool\tI"
 msgstr "Malenwerkzeug\tI"
 
 #: flatcamGUI/FlatCAMGUI.py:543
-msgid "Transform Tool\tALT+R"
-msgstr "Transformationswerkzeug\tALT+R"
+msgid "Transform Tool\tAlt+R"
+msgstr "Transformationswerkzeug\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:547
 msgid "Toggle Corner Snap\tK"
@@ -5910,8 +5910,8 @@ msgid "Add Region\tN"
 msgstr "Region hinzufügen\tN"
 
 #: flatcamGUI/FlatCAMGUI.py:598
-msgid "Poligonize\tALT+N"
-msgstr "Polygonisieren\tALT+N"
+msgid "Poligonize\tAlt+N"
+msgstr "Polygonisieren\tAlt+N"
 
 #: flatcamGUI/FlatCAMGUI.py:600
 msgid "Add SemiDisc\tE"
@@ -5930,15 +5930,15 @@ msgid "Scale\tS"
 msgstr "Skalieren\tS"
 
 #: flatcamGUI/FlatCAMGUI.py:608
-msgid "Mark Area\tALT+A"
-msgstr "Bereich markieren\tALT+A"
+msgid "Mark Area\tAlt+A"
+msgstr "Bereich markieren\tAlt+A"
 
 #: flatcamGUI/FlatCAMGUI.py:610
-msgid "Eraser\tCTRL+E"
+msgid "Eraser\tCtrl+E"
 msgstr "Radiergummi\tSTRG+E"
 
 #: flatcamGUI/FlatCAMGUI.py:612
-msgid "Transform\tALT+R"
+msgid "Transform\tAlt+R"
 msgstr "Transformationswerkzeug\tSTRG+R"
 
 #: flatcamGUI/FlatCAMGUI.py:639
@@ -14096,7 +14096,7 @@ msgid ""
 "\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 "
+"- 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"
@@ -17831,8 +17831,8 @@ msgstr ""
 #~ "&gt; Verknüpfungsliste </strong> oder über eine eigene Tastenkombination: "
 #~ "<strng>F3</strong>. </Span> </p>\n"
 
-#~ msgid "Run Script ...\tSHIFT+S"
-#~ msgstr "Skript ausführen ...\tSHIFT+S"
+#~ msgid "Run Script ...\tShift+S"
+#~ msgstr "Skript ausführen ...\tShift+S"
 
 #~ msgid ""
 #~ "<font size=8><B>FlatCAM</B></font><BR>Version {version} {beta} ({date}) - "
@@ -18001,39 +18001,39 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+A</strong></td>\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 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 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 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 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 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 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 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 height=\"20\"><strong>Ctrl+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Toggle Plot Area</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18041,39 +18041,39 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+C</strong></td>\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 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 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 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 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 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 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 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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18081,59 +18081,59 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+C</strong></td>\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 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 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 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 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 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 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 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 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 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 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 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 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 height=\"20\"><strong>Alt+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Toggle Full Screen</td>\n"
 #~ "                    </tr>                 \n"
 #~ "                    <tr height=\"20\">\n"
@@ -18141,7 +18141,7 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+ALT+X</strong></"
+#~ "                        <td height=\"20\"><strong>Ctrl+Alt+X</strong></"
 #~ "td>\n"
 #~ "                        <td>&nbsp;Abort current task (gracefully)</td>\n"
 #~ "                    </tr>                    \n"
@@ -18339,40 +18339,40 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+C</strong></td>\n"
 #~ "                        <td>&nbsp;Objektnamen kopieren</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+E</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+E</strong></td>\n"
 #~ "                        <td>&nbsp;Code-Editor umschalten</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+G</strong></td>\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 height=\"20\"><strong>Shift+P</strong></td>\n"
 #~ "                        <td>&nbsp;Öffnen Sie das Einstellungsfenster</"
 #~ "td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+R</strong></td>\n"
 #~ "                        <td>&nbsp;Um 90 Grad nach links drehen</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+S</strong></td>\n"
 #~ "                        <td>&nbsp;Führen Sie ein Skript aus</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+W</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+W</strong></td>\n"
 #~ "                        <td>&nbsp;Arbeitsbereich umschalten</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
 #~ "                        <td>&nbsp;Neigung auf der X-Achse</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Neigung auf der Y-Achse</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18380,60 +18380,60 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+C</strong></td>\n"
 #~ "                        <td>&nbsp;Rechnerwerzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+D</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+D</strong></td>\n"
 #~ "                        <td>&nbsp;2-seitiges PCBwerkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+K</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+K</strong></td>\n"
 #~ "                        <td>&nbsp;Lötpastenwerkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+L</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+L</strong></td>\n"
 #~ "                        <td>&nbsp;Film PCB Werkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
 #~ "                        <td>&nbsp;Nicht-Kupfer löschen Werkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+P</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+P</strong></td>\n"
 #~ "                        <td>&nbsp;Paint Werkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+Q</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+Q</strong></td>\n"
 #~ "                        <td>&nbsp;PDF-Importwerkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Transformationen\" Werkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+S</strong></td>\n"
 #~ "                        <td>&nbsp;Dateiquelle anzeigen</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+U</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+U</strong></td>\n"
 #~ "                        <td>&nbsp;PCB-Werkzeug ausschneiden</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+1</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+1</strong></td>\n"
 #~ "                        <td>&nbsp;Alle Plots aktivieren</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+2</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+2</strong></td>\n"
 #~ "                        <td>&nbsp;Deaktivieren Sie alle Plots</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+3</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+3</strong></td>\n"
 #~ "                        <td>&nbsp;Deaktivieren Sie nicht ausgewählte "
 #~ "Plots</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+F10</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Vollbild umschalten</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18441,7 +18441,7 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>STRG+ALT+X</strong></"
+#~ "                        <td height=\"20\"><strong>STRG+Alt+X</strong></"
 #~ "td>\n"
 #~ "                        <td>&nbsp;Aktuelle Aufgabe abbrechen "
 #~ "(ordnungsgemäß)</td>\n"
@@ -18579,11 +18579,11 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18591,15 +18591,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\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 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 height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18607,15 +18607,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\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 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 height=\"20\"><strong>Ctrl+X</strong></td>\n"
 #~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18707,7 +18707,7 @@ msgstr ""
 #~ "                        <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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -18795,11 +18795,11 @@ msgstr ""
 #~ "                        <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 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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18807,15 +18807,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                     <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+A</strong></td>\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 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 height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Transformation Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -18914,11 +18914,11 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
 #~ "                        <td>&nbsp;Neigung auf der X-Achse</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Neigung auf der Y-Achse</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18926,15 +18926,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Editor-Umwandlungstool</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+X</strong></td>\n"
 #~ "                        <td>&nbsp;Versatzform auf der X-Achse</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Versatzform auf der Y-Achse</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -19151,15 +19151,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+A</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+A</strong></td>\n"
 #~ "                        <td>&nbsp;Bereichswerkzeug markieren</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
 #~ "                        <td>&nbsp;Werkzeug \"Polygonisieren\"</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Transformations Werkzeug</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -20271,11 +20271,11 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -20283,15 +20283,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\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 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 height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -20299,15 +20299,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\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 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 height=\"20\"><strong>Ctrl+X</strong></td>\n"
 #~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -20389,7 +20389,7 @@ msgstr ""
 #~ "                        <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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -20478,11 +20478,11 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -20490,15 +20490,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\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 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 height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -20506,15 +20506,15 @@ msgstr ""
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\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 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 height=\"20\"><strong>Ctrl+X</strong></td>\n"
 #~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -20596,7 +20596,7 @@ msgstr ""
 #~ "                        <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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"

BIN
locale/en/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 326 - 266
locale/en/LC_MESSAGES/strings.po


+ 57 - 57
locale/es/LC_MESSAGES/strings.po

@@ -5267,8 +5267,8 @@ msgid "File"
 msgstr "Archivo"
 
 #: flatcamGUI/FlatCAMGUI.py:69
-msgid "&New Project ...\tCTRL+N"
-msgstr "&Nuevo proyecto ...\tCTRL+N"
+msgid "&New Project ...\tCtrl+N"
+msgstr "&Nuevo proyecto ...\tCtrl+N"
 
 #: flatcamGUI/FlatCAMGUI.py:71
 msgid "Will create a new, blank project"
@@ -5320,12 +5320,12 @@ msgid "Open &Project ..."
 msgstr "Abierto &Project ..."
 
 #: flatcamGUI/FlatCAMGUI.py:109 flatcamGUI/FlatCAMGUI.py:4121
-msgid "Open &Gerber ...\tCTRL+G"
-msgstr "Abierto &Gerber ...\tCTRL+G"
+msgid "Open &Gerber ...\tCtrl+G"
+msgstr "Abierto &Gerber ...\tCtrl+G"
 
 #: flatcamGUI/FlatCAMGUI.py:114 flatcamGUI/FlatCAMGUI.py:4126
-msgid "Open &Excellon ...\tCTRL+E"
-msgstr "Abierto &Excellon ...\tCTRL+E"
+msgid "Open &Excellon ...\tCtrl+E"
+msgstr "Abierto &Excellon ...\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:118 flatcamGUI/FlatCAMGUI.py:4131
 msgid "Open G-&Code ..."
@@ -5476,8 +5476,8 @@ msgid "&Save Project ..."
 msgstr "Salvar proyecto ..."
 
 #: flatcamGUI/FlatCAMGUI.py:256
-msgid "Save Project &As ...\tCTRL+S"
-msgstr "Guardar proyecto como...\tCTRL+S"
+msgid "Save Project &As ...\tCtrl+S"
+msgstr "Guardar proyecto como...\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:261
 msgid "Save Project C&opy ..."
@@ -5497,8 +5497,8 @@ msgid "Edit Object\tE"
 msgstr "Editar objeto\tE"
 
 #: flatcamGUI/FlatCAMGUI.py:285
-msgid "Close Editor\tCTRL+S"
-msgstr "Cerrar Editor\tCTRL+S"
+msgid "Close Editor\tCtrl+S"
+msgstr "Cerrar Editor\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:294
 msgid "Conversion"
@@ -5574,8 +5574,8 @@ msgid "Convert Any to Gerber"
 msgstr "Convertir cualquiera a Gerber"
 
 #: flatcamGUI/FlatCAMGUI.py:341
-msgid "&Copy\tCTRL+C"
-msgstr "Dupdo\tCTRL+C"
+msgid "&Copy\tCtrl+C"
+msgstr "Dupdo\tCtrl+C"
 
 #: flatcamGUI/FlatCAMGUI.py:346
 msgid "&Delete\tDEL"
@@ -5594,28 +5594,28 @@ msgid "Toggle Units\tQ"
 msgstr "Unidades de palanca\tQ"
 
 #: flatcamGUI/FlatCAMGUI.py:360
-msgid "&Select All\tCTRL+A"
-msgstr "Seleccionar todo\tCTRL+A"
+msgid "&Select All\tCtrl+A"
+msgstr "Seleccionar todo\tCtrl+A"
 
 #: flatcamGUI/FlatCAMGUI.py:365
-msgid "&Preferences\tSHIFT+P"
-msgstr "Preferencias\tSHIFT+P"
+msgid "&Preferences\tShift+P"
+msgstr "Preferencias\tShift+P"
 
 #: flatcamGUI/FlatCAMGUI.py:371 flatcamTools/ToolProperties.py:153
 msgid "Options"
 msgstr "Opciones"
 
 #: flatcamGUI/FlatCAMGUI.py:373
-msgid "&Rotate Selection\tSHIFT+(R)"
-msgstr "Rotar selección\tSHIFT+(R)"
+msgid "&Rotate Selection\tShift+(R)"
+msgstr "Rotar selección\tShift+(R)"
 
 #: flatcamGUI/FlatCAMGUI.py:378
-msgid "&Skew on X axis\tSHIFT+X"
-msgstr "Sesgo en el eje X\tSHIFT+X"
+msgid "&Skew on X axis\tShift+X"
+msgstr "Sesgo en el eje X\tShift+X"
 
 #: flatcamGUI/FlatCAMGUI.py:380
-msgid "S&kew on Y axis\tSHIFT+Y"
-msgstr "Sesgo en el eje Y\tSHIFT+Y"
+msgid "S&kew on Y axis\tShift+Y"
+msgstr "Sesgo en el eje Y\tShift+Y"
 
 #: flatcamGUI/FlatCAMGUI.py:385
 msgid "Flip on &X axis\tX"
@@ -5626,28 +5626,28 @@ msgid "Flip on &Y axis\tY"
 msgstr "Voltear en el ejeY\tY"
 
 #: flatcamGUI/FlatCAMGUI.py:392
-msgid "View source\tALT+S"
-msgstr "Ver fuente\tALT+S"
+msgid "View source\tAlt+S"
+msgstr "Ver fuente\tAlt+S"
 
 #: flatcamGUI/FlatCAMGUI.py:394
-msgid "Tools DataBase\tCTRL+D"
-msgstr "DB de Herramientas\tCTRL+D"
+msgid "Tools DataBase\tCtrl+D"
+msgstr "DB de Herramientas\tCtrl+D"
 
 #: flatcamGUI/FlatCAMGUI.py:401 flatcamGUI/FlatCAMGUI.py:2060
 msgid "View"
 msgstr "Ver"
 
 #: flatcamGUI/FlatCAMGUI.py:403
-msgid "Enable all plots\tALT+1"
-msgstr "Habilitar todas las parcelas\tALT+1"
+msgid "Enable all plots\tAlt+1"
+msgstr "Habilitar todas las parcelas\tAlt+1"
 
 #: flatcamGUI/FlatCAMGUI.py:405
-msgid "Disable all plots\tALT+2"
-msgstr "Deshabilitar todas las parcelas\tALT+2"
+msgid "Disable all plots\tAlt+2"
+msgstr "Deshabilitar todas las parcelas\tAlt+2"
 
 #: flatcamGUI/FlatCAMGUI.py:407
-msgid "Disable non-selected\tALT+3"
-msgstr "Deshabilitar no seleccionado\tALT+3"
+msgid "Disable non-selected\tAlt+3"
+msgstr "Deshabilitar no seleccionado\tAlt+3"
 
 #: flatcamGUI/FlatCAMGUI.py:411
 msgid "&Zoom Fit\tV"
@@ -5666,16 +5666,16 @@ msgid "Redraw All\tF5"
 msgstr "Redibujar todo\tF5"
 
 #: flatcamGUI/FlatCAMGUI.py:424
-msgid "Toggle Code Editor\tSHIFT+E"
-msgstr "Alternar Editor de Código\tSHIFT+E"
+msgid "Toggle Code Editor\tShift+E"
+msgstr "Alternar Editor de Código\tShift+E"
 
 #: flatcamGUI/FlatCAMGUI.py:427
-msgid "&Toggle FullScreen\tALT+F10"
-msgstr "Alternar pantalla completa\tALT+F10"
+msgid "&Toggle FullScreen\tAlt+F10"
+msgstr "Alternar pantalla completa\tAlt+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:429
-msgid "&Toggle Plot Area\tCTRL+F10"
-msgstr "Alternar área de la parcela\tCTRL+F10"
+msgid "&Toggle Plot Area\tCtrl+F10"
+msgstr "Alternar área de la parcela\tCtrl+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:431
 msgid "&Toggle Project/Sel/Tool\t`"
@@ -5686,16 +5686,16 @@ msgid "&Toggle Grid Snap\tG"
 msgstr "Activar cuadrícula\tG"
 
 #: flatcamGUI/FlatCAMGUI.py:437
-msgid "&Toggle Grid Lines\tALT+G"
-msgstr "Alternar Líneas de Cuadrícula\tALT+G"
+msgid "&Toggle Grid Lines\tAlt+G"
+msgstr "Alternar Líneas de Cuadrícula\tAlt+G"
 
 #: flatcamGUI/FlatCAMGUI.py:439
-msgid "&Toggle Axis\tSHIFT+G"
-msgstr "Eje de palanca\tSHIFT+G"
+msgid "&Toggle Axis\tShift+G"
+msgstr "Eje de palanca\tShift+G"
 
 #: flatcamGUI/FlatCAMGUI.py:441
-msgid "Toggle Workspace\tSHIFT+W"
-msgstr "Alternar espacio de trabajo\tSHIFT+W"
+msgid "Toggle Workspace\tShift+W"
+msgstr "Alternar espacio de trabajo\tShift+W"
 
 #: flatcamGUI/FlatCAMGUI.py:446
 msgid "Objects"
@@ -5794,8 +5794,8 @@ msgid "Paint Tool\tI"
 msgstr "Herramienta de pintura\tI"
 
 #: flatcamGUI/FlatCAMGUI.py:543
-msgid "Transform Tool\tALT+R"
-msgstr "Herramienta de transformación\tALT+R"
+msgid "Transform Tool\tAlt+R"
+msgstr "Herramienta de transformación\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:547
 msgid "Toggle Corner Snap\tK"
@@ -5858,8 +5858,8 @@ msgid "Add Region\tN"
 msgstr "Añadir región\tN"
 
 #: flatcamGUI/FlatCAMGUI.py:598
-msgid "Poligonize\tALT+N"
-msgstr "Poligonize\tALT+N"
+msgid "Poligonize\tAlt+N"
+msgstr "Poligonize\tAlt+N"
 
 #: flatcamGUI/FlatCAMGUI.py:600
 msgid "Add SemiDisc\tE"
@@ -5878,16 +5878,16 @@ msgid "Scale\tS"
 msgstr "Escalar\tS"
 
 #: flatcamGUI/FlatCAMGUI.py:608
-msgid "Mark Area\tALT+A"
-msgstr "Marcar area\tALT+A"
+msgid "Mark Area\tAlt+A"
+msgstr "Marcar area\tAlt+A"
 
 #: flatcamGUI/FlatCAMGUI.py:610
-msgid "Eraser\tCTRL+E"
-msgstr "Borrador\tCTRL+E"
+msgid "Eraser\tCtrl+E"
+msgstr "Borrador\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:612
-msgid "Transform\tALT+R"
-msgstr "Transformar\tALT+R"
+msgid "Transform\tAlt+R"
+msgstr "Transformar\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:639
 msgid "Enable Plot"
@@ -14007,7 +14007,7 @@ msgid ""
 "\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 "
+"- 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"
@@ -17788,8 +17788,8 @@ msgstr ""
 #~ "\n"
 #~ " "
 
-#~ msgid "Run Script ...\tSHIFT+S"
-#~ msgstr "Ejecutar Script ...\tSHIFT+S"
+#~ msgid "Run Script ...\tShift+S"
+#~ msgstr "Ejecutar Script ...\tShift+S"
 
 #~ msgid ""
 #~ "<font size=8><B>FlatCAM</B></font><BR>Version {version} {beta} ({date}) - "

+ 55 - 55
locale/fr/LC_MESSAGES/strings.po

@@ -5282,8 +5282,8 @@ msgid "File"
 msgstr "Fichier"
 
 #: flatcamGUI/FlatCAMGUI.py:69
-msgid "&New Project ...\tCTRL+N"
-msgstr "Nouveau projet ...\tCTRL+N"
+msgid "&New Project ...\tCtrl+N"
+msgstr "Nouveau projet ...\tCtrl+N"
 
 #: flatcamGUI/FlatCAMGUI.py:71
 msgid "Will create a new, blank project"
@@ -5335,12 +5335,12 @@ msgid "Open &Project ..."
 msgstr "Projet ouvert ..."
 
 #: flatcamGUI/FlatCAMGUI.py:109 flatcamGUI/FlatCAMGUI.py:4121
-msgid "Open &Gerber ...\tCTRL+G"
-msgstr "Gerber ouvert...\tCTRL+G"
+msgid "Open &Gerber ...\tCtrl+G"
+msgstr "Gerber ouvert...\tCtrl+G"
 
 #: flatcamGUI/FlatCAMGUI.py:114 flatcamGUI/FlatCAMGUI.py:4126
-msgid "Open &Excellon ...\tCTRL+E"
-msgstr "Excellon ouvert ...\tCTRL+E"
+msgid "Open &Excellon ...\tCtrl+E"
+msgstr "Excellon ouvert ...\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:118 flatcamGUI/FlatCAMGUI.py:4131
 msgid "Open G-&Code ..."
@@ -5491,8 +5491,8 @@ msgid "&Save Project ..."
 msgstr "Sauvegarder le projet ..."
 
 #: flatcamGUI/FlatCAMGUI.py:256
-msgid "Save Project &As ...\tCTRL+S"
-msgstr "Enregistrer le projet sous...\tCTRL+S"
+msgid "Save Project &As ...\tCtrl+S"
+msgstr "Enregistrer le projet sous...\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:261
 msgid "Save Project C&opy ..."
@@ -5512,8 +5512,8 @@ msgid "Edit Object\tE"
 msgstr "Editer un objet\tE"
 
 #: flatcamGUI/FlatCAMGUI.py:285
-msgid "Close Editor\tCTRL+S"
-msgstr "Fermer l'éditeur\tCTRL+S"
+msgid "Close Editor\tCtrl+S"
+msgstr "Fermer l'éditeur\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:294
 msgid "Conversion"
@@ -5589,8 +5589,8 @@ msgid "Convert Any to Gerber"
 msgstr "Convertir n'importe lequel en gerber"
 
 #: flatcamGUI/FlatCAMGUI.py:341
-msgid "&Copy\tCTRL+C"
-msgstr "Copie\tCTRL+C"
+msgid "&Copy\tCtrl+C"
+msgstr "Copie\tCtrl+C"
 
 #: flatcamGUI/FlatCAMGUI.py:346
 msgid "&Delete\tDEL"
@@ -5609,28 +5609,28 @@ msgid "Toggle Units\tQ"
 msgstr "Basculer les Unités\tQ"
 
 #: flatcamGUI/FlatCAMGUI.py:360
-msgid "&Select All\tCTRL+A"
-msgstr "Tout sélectionner\tCTRL+A"
+msgid "&Select All\tCtrl+A"
+msgstr "Tout sélectionner\tCtrl+A"
 
 #: flatcamGUI/FlatCAMGUI.py:365
-msgid "&Preferences\tSHIFT+P"
-msgstr "Préférences\tSHIFT+P"
+msgid "&Preferences\tShift+P"
+msgstr "Préférences\tShift+P"
 
 #: flatcamGUI/FlatCAMGUI.py:371 flatcamTools/ToolProperties.py:153
 msgid "Options"
 msgstr "Les options"
 
 #: flatcamGUI/FlatCAMGUI.py:373
-msgid "&Rotate Selection\tSHIFT+(R)"
-msgstr "Faire pivoter la sélection\tSHIFT+(R)"
+msgid "&Rotate Selection\tShift+(R)"
+msgstr "Faire pivoter la sélection\tShift+(R)"
 
 #: flatcamGUI/FlatCAMGUI.py:378
-msgid "&Skew on X axis\tSHIFT+X"
-msgstr "Fausser sur l'axe X\tSHIFT+X"
+msgid "&Skew on X axis\tShift+X"
+msgstr "Fausser sur l'axe X\tShift+X"
 
 #: flatcamGUI/FlatCAMGUI.py:380
-msgid "S&kew on Y axis\tSHIFT+Y"
-msgstr "Fausser sur l'axe Y\tSHIFT+Y"
+msgid "S&kew on Y axis\tShift+Y"
+msgstr "Fausser sur l'axe Y\tShift+Y"
 
 #: flatcamGUI/FlatCAMGUI.py:385
 msgid "Flip on &X axis\tX"
@@ -5641,28 +5641,28 @@ msgid "Flip on &Y axis\tY"
 msgstr "Miroir sur l'axe Y\tY"
 
 #: flatcamGUI/FlatCAMGUI.py:392
-msgid "View source\tALT+S"
-msgstr "Voir la source\tALT+S"
+msgid "View source\tAlt+S"
+msgstr "Voir la source\tAlt+S"
 
 #: flatcamGUI/FlatCAMGUI.py:394
-msgid "Tools DataBase\tCTRL+D"
-msgstr "Base de Données d'outils\tCTRL+D"
+msgid "Tools DataBase\tCtrl+D"
+msgstr "Base de Données d'outils\tCtrl+D"
 
 #: flatcamGUI/FlatCAMGUI.py:401 flatcamGUI/FlatCAMGUI.py:2060
 msgid "View"
 msgstr "Vue"
 
 #: flatcamGUI/FlatCAMGUI.py:403
-msgid "Enable all plots\tALT+1"
-msgstr "Activer tous les dessins\tALT+1"
+msgid "Enable all plots\tAlt+1"
+msgstr "Activer tous les dessins\tAlt+1"
 
 #: flatcamGUI/FlatCAMGUI.py:405
-msgid "Disable all plots\tALT+2"
-msgstr "Désactiver tous les dessins\tALT+2"
+msgid "Disable all plots\tAlt+2"
+msgstr "Désactiver tous les dessins\tAlt+2"
 
 #: flatcamGUI/FlatCAMGUI.py:407
-msgid "Disable non-selected\tALT+3"
-msgstr "Désactiver les non sélectionnés\tALT+3"
+msgid "Disable non-selected\tAlt+3"
+msgstr "Désactiver les non sélectionnés\tAlt+3"
 
 #: flatcamGUI/FlatCAMGUI.py:411
 msgid "&Zoom Fit\tV"
@@ -5681,16 +5681,16 @@ msgid "Redraw All\tF5"
 msgstr "Tout redessiner\tF5"
 
 #: flatcamGUI/FlatCAMGUI.py:424
-msgid "Toggle Code Editor\tSHIFT+E"
-msgstr "Basculer l'éditeur de code\tSHIFT+E"
+msgid "Toggle Code Editor\tShift+E"
+msgstr "Basculer l'éditeur de code\tShift+E"
 
 #: flatcamGUI/FlatCAMGUI.py:427
-msgid "&Toggle FullScreen\tALT+F10"
-msgstr "Passer en plein écran\tALT+F10"
+msgid "&Toggle FullScreen\tAlt+F10"
+msgstr "Passer en plein écran\tAlt+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:429
-msgid "&Toggle Plot Area\tCTRL+F10"
-msgstr "Basculer la zone de tracé\tCTRL+F10"
+msgid "&Toggle Plot Area\tCtrl+F10"
+msgstr "Basculer la zone de tracé\tCtrl+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:431
 msgid "&Toggle Project/Sel/Tool\t`"
@@ -5701,16 +5701,16 @@ msgid "&Toggle Grid Snap\tG"
 msgstr "Basculer la grille\tG"
 
 #: flatcamGUI/FlatCAMGUI.py:437
-msgid "&Toggle Grid Lines\tALT+G"
-msgstr "Basculer les lignes de la grille\tALT+G"
+msgid "&Toggle Grid Lines\tAlt+G"
+msgstr "Basculer les lignes de la grille\tAlt+G"
 
 #: flatcamGUI/FlatCAMGUI.py:439
-msgid "&Toggle Axis\tSHIFT+G"
-msgstr "Basculer l'axe\tSHIFT+G"
+msgid "&Toggle Axis\tShift+G"
+msgstr "Basculer l'axe\tShift+G"
 
 #: flatcamGUI/FlatCAMGUI.py:441
-msgid "Toggle Workspace\tSHIFT+W"
-msgstr "Basculer l'espace de travail\tSHIFT+W"
+msgid "Toggle Workspace\tShift+W"
+msgstr "Basculer l'espace de travail\tShift+W"
 
 #: flatcamGUI/FlatCAMGUI.py:446
 msgid "Objects"
@@ -5809,8 +5809,8 @@ msgid "Paint Tool\tI"
 msgstr "Outil de Peinture\tI"
 
 #: flatcamGUI/FlatCAMGUI.py:543
-msgid "Transform Tool\tALT+R"
-msgstr "Outil de Transformation\tALT+R"
+msgid "Transform Tool\tAlt+R"
+msgstr "Outil de Transformation\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:547
 msgid "Toggle Corner Snap\tK"
@@ -5873,8 +5873,8 @@ msgid "Add Region\tN"
 msgstr "Ajouter une Région\tN"
 
 #: flatcamGUI/FlatCAMGUI.py:598
-msgid "Poligonize\tALT+N"
-msgstr "Polygoniser\tALT+N"
+msgid "Poligonize\tAlt+N"
+msgstr "Polygoniser\tAlt+N"
 
 #: flatcamGUI/FlatCAMGUI.py:600
 msgid "Add SemiDisc\tE"
@@ -5893,16 +5893,16 @@ msgid "Scale\tS"
 msgstr "Échelle\tS"
 
 #: flatcamGUI/FlatCAMGUI.py:608
-msgid "Mark Area\tALT+A"
-msgstr "Zone de Marque\tALT+A"
+msgid "Mark Area\tAlt+A"
+msgstr "Zone de Marque\tAlt+A"
 
 #: flatcamGUI/FlatCAMGUI.py:610
-msgid "Eraser\tCTRL+E"
-msgstr "La Gomme\tCTRL+E"
+msgid "Eraser\tCtrl+E"
+msgstr "La Gomme\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:612
-msgid "Transform\tALT+R"
-msgstr "Transformation\tALT+R"
+msgid "Transform\tAlt+R"
+msgstr "Transformation\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:639
 msgid "Enable Plot"
@@ -14014,7 +14014,7 @@ msgid ""
 "\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 "
+"- 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"

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

@@ -4787,8 +4787,8 @@ msgid "File"
 msgstr "File"
 
 #: flatcamGUI/FlatCAMGUI.py:69
-msgid "&New Project ...\tCTRL+N"
-msgstr "&Nuovo progetto ...\tCTRL+N"
+msgid "&New Project ...\tCtrl+N"
+msgstr "&Nuovo progetto ...\tCtrl+N"
 
 #: flatcamGUI/FlatCAMGUI.py:71
 msgid "Will create a new, blank project"
@@ -4840,12 +4840,12 @@ msgid "Open &Project ..."
 msgstr "Progetto aperto ..."
 
 #: flatcamGUI/FlatCAMGUI.py:109 flatcamGUI/FlatCAMGUI.py:4121
-msgid "Open &Gerber ...\tCTRL+G"
-msgstr "Apri Gerber...\tCTRL+G"
+msgid "Open &Gerber ...\tCtrl+G"
+msgstr "Apri Gerber...\tCtrl+G"
 
 #: flatcamGUI/FlatCAMGUI.py:114 flatcamGUI/FlatCAMGUI.py:4126
-msgid "Open &Excellon ...\tCTRL+E"
-msgstr "Apri Excellon ...\tCTRL+E"
+msgid "Open &Excellon ...\tCtrl+E"
+msgstr "Apri Excellon ...\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:118 flatcamGUI/FlatCAMGUI.py:4131
 msgid "Open G-&Code ..."
@@ -4984,8 +4984,8 @@ msgid "&Save Project ..."
 msgstr "Salva il progetto ..."
 
 #: flatcamGUI/FlatCAMGUI.py:256
-msgid "Save Project &As ...\tCTRL+S"
-msgstr "Salva progetto con nome ...\tCTRL+S"
+msgid "Save Project &As ...\tCtrl+S"
+msgstr "Salva progetto con nome ...\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:261
 msgid "Save Project C&opy ..."
@@ -5005,7 +5005,7 @@ msgid "Edit Object\tE"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:285
-msgid "Close Editor\tCTRL+S"
+msgid "Close Editor\tCtrl+S"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:294
@@ -5070,7 +5070,7 @@ msgid "Convert Any to Gerber"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:341
-msgid "&Copy\tCTRL+C"
+msgid "&Copy\tCtrl+C"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:346
@@ -5090,11 +5090,11 @@ msgid "Toggle Units\tQ"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:360
-msgid "&Select All\tCTRL+A"
+msgid "&Select All\tCtrl+A"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:365
-msgid "&Preferences\tSHIFT+P"
+msgid "&Preferences\tShift+P"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:371 flatcamTools/ToolProperties.py:153
@@ -5102,15 +5102,15 @@ msgid "Options"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:373
-msgid "&Rotate Selection\tSHIFT+(R)"
+msgid "&Rotate Selection\tShift+(R)"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:378
-msgid "&Skew on X axis\tSHIFT+X"
+msgid "&Skew on X axis\tShift+X"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:380
-msgid "S&kew on Y axis\tSHIFT+Y"
+msgid "S&kew on Y axis\tShift+Y"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:385
@@ -5122,11 +5122,11 @@ msgid "Flip on &Y axis\tY"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:392
-msgid "View source\tALT+S"
+msgid "View source\tAlt+S"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:394
-msgid "Tools DataBase\tCTRL+D"
+msgid "Tools DataBase\tCtrl+D"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:401 flatcamGUI/FlatCAMGUI.py:2060
@@ -5134,15 +5134,15 @@ msgid "View"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:403
-msgid "Enable all plots\tALT+1"
+msgid "Enable all plots\tAlt+1"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:405
-msgid "Disable all plots\tALT+2"
+msgid "Disable all plots\tAlt+2"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:407
-msgid "Disable non-selected\tALT+3"
+msgid "Disable non-selected\tAlt+3"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:411
@@ -5162,15 +5162,15 @@ msgid "Redraw All\tF5"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:424
-msgid "Toggle Code Editor\tSHIFT+E"
+msgid "Toggle Code Editor\tShift+E"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:427
-msgid "&Toggle FullScreen\tALT+F10"
+msgid "&Toggle FullScreen\tAlt+F10"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:429
-msgid "&Toggle Plot Area\tCTRL+F10"
+msgid "&Toggle Plot Area\tCtrl+F10"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:431
@@ -5182,15 +5182,15 @@ msgid "&Toggle Grid Snap\tG"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:437
-msgid "&Toggle Grid Lines\tALT+G"
+msgid "&Toggle Grid Lines\tAlt+G"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:439
-msgid "&Toggle Axis\tSHIFT+G"
+msgid "&Toggle Axis\tShift+G"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:441
-msgid "Toggle Workspace\tSHIFT+W"
+msgid "Toggle Workspace\tShift+W"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:446
@@ -5290,7 +5290,7 @@ msgid "Paint Tool\tI"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:543
-msgid "Transform Tool\tALT+R"
+msgid "Transform Tool\tAlt+R"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:547
@@ -5354,7 +5354,7 @@ msgid "Add Region\tN"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:598
-msgid "Poligonize\tALT+N"
+msgid "Poligonize\tAlt+N"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:600
@@ -5374,15 +5374,15 @@ msgid "Scale\tS"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:608
-msgid "Mark Area\tALT+A"
+msgid "Mark Area\tAlt+A"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:610
-msgid "Eraser\tCTRL+E"
+msgid "Eraser\tCtrl+E"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:612
-msgid "Transform\tALT+R"
+msgid "Transform\tAlt+R"
 msgstr ""
 
 #: flatcamGUI/FlatCAMGUI.py:639
@@ -12253,7 +12253,7 @@ msgid ""
 "\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 "
+"- 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"

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

@@ -5241,8 +5241,8 @@ msgid "File"
 msgstr "Arquivo"
 
 #: flatcamGUI/FlatCAMGUI.py:69
-msgid "&New Project ...\tCTRL+N"
-msgstr "&Novo Projeto ...\tCTRL+N"
+msgid "&New Project ...\tCtrl+N"
+msgstr "&Novo Projeto ...\tCtrl+N"
 
 #: flatcamGUI/FlatCAMGUI.py:71
 msgid "Will create a new, blank project"
@@ -5294,12 +5294,12 @@ msgid "Open &Project ..."
 msgstr "Abrir &Projeto ..."
 
 #: flatcamGUI/FlatCAMGUI.py:109 flatcamGUI/FlatCAMGUI.py:4121
-msgid "Open &Gerber ...\tCTRL+G"
-msgstr "Abrir &Gerber ...\tCTRL+G"
+msgid "Open &Gerber ...\tCtrl+G"
+msgstr "Abrir &Gerber ...\tCtrl+G"
 
 #: flatcamGUI/FlatCAMGUI.py:114 flatcamGUI/FlatCAMGUI.py:4126
-msgid "Open &Excellon ...\tCTRL+E"
-msgstr "Abrir &Excellon ...\tCTRL+E"
+msgid "Open &Excellon ...\tCtrl+E"
+msgstr "Abrir &Excellon ...\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:118 flatcamGUI/FlatCAMGUI.py:4131
 msgid "Open G-&Code ..."
@@ -5450,8 +5450,8 @@ msgid "&Save Project ..."
 msgstr "&Salvar Projeto ..."
 
 #: flatcamGUI/FlatCAMGUI.py:256
-msgid "Save Project &As ...\tCTRL+S"
-msgstr "S&alvar Projeto Como ...\tCTRL+S"
+msgid "Save Project &As ...\tCtrl+S"
+msgstr "S&alvar Projeto Como ...\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:261
 msgid "Save Project C&opy ..."
@@ -5471,8 +5471,8 @@ msgid "Edit Object\tE"
 msgstr "Editar Objeto\tE"
 
 #: flatcamGUI/FlatCAMGUI.py:285
-msgid "Close Editor\tCTRL+S"
-msgstr "Fechar Editor\tCTRL+S"
+msgid "Close Editor\tCtrl+S"
+msgstr "Fechar Editor\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:294
 msgid "Conversion"
@@ -5545,8 +5545,8 @@ msgid "Convert Any to Gerber"
 msgstr "Converter Qualquer para Gerber"
 
 #: flatcamGUI/FlatCAMGUI.py:341
-msgid "&Copy\tCTRL+C"
-msgstr "&Copiar\tCTRL+C"
+msgid "&Copy\tCtrl+C"
+msgstr "&Copiar\tCtrl+C"
 
 #: flatcamGUI/FlatCAMGUI.py:346
 msgid "&Delete\tDEL"
@@ -5565,28 +5565,28 @@ msgid "Toggle Units\tQ"
 msgstr "Alternar Unidades\tQ"
 
 #: flatcamGUI/FlatCAMGUI.py:360
-msgid "&Select All\tCTRL+A"
-msgstr "&Selecionar Tudo\tCTRL+A"
+msgid "&Select All\tCtrl+A"
+msgstr "&Selecionar Tudo\tCtrl+A"
 
 #: flatcamGUI/FlatCAMGUI.py:365
-msgid "&Preferences\tSHIFT+P"
-msgstr "&Preferências\tSHIFT+P"
+msgid "&Preferences\tShift+P"
+msgstr "&Preferências\tShift+P"
 
 #: flatcamGUI/FlatCAMGUI.py:371 flatcamTools/ToolProperties.py:153
 msgid "Options"
 msgstr "Opções"
 
 #: flatcamGUI/FlatCAMGUI.py:373
-msgid "&Rotate Selection\tSHIFT+(R)"
-msgstr "Gi&rar Seleção\tSHIFT+(R)"
+msgid "&Rotate Selection\tShift+(R)"
+msgstr "Gi&rar Seleção\tShift+(R)"
 
 #: flatcamGUI/FlatCAMGUI.py:378
-msgid "&Skew on X axis\tSHIFT+X"
-msgstr "Inclinar no eixo X\tSHIFT+X"
+msgid "&Skew on X axis\tShift+X"
+msgstr "Inclinar no eixo X\tShift+X"
 
 #: flatcamGUI/FlatCAMGUI.py:380
-msgid "S&kew on Y axis\tSHIFT+Y"
-msgstr "Inclinar no eixo Y\tSHIFT+Y"
+msgid "S&kew on Y axis\tShift+Y"
+msgstr "Inclinar no eixo Y\tShift+Y"
 
 #: flatcamGUI/FlatCAMGUI.py:385
 msgid "Flip on &X axis\tX"
@@ -5597,28 +5597,28 @@ msgid "Flip on &Y axis\tY"
 msgstr "Espelhar no eixo &Y\tY"
 
 #: flatcamGUI/FlatCAMGUI.py:392
-msgid "View source\tALT+S"
-msgstr "Ver fonte\tALT+S"
+msgid "View source\tAlt+S"
+msgstr "Ver fonte\tAlt+S"
 
 #: flatcamGUI/FlatCAMGUI.py:394
-msgid "Tools DataBase\tCTRL+D"
-msgstr "Banco de Dados de Ferramentas\tCTRL+D"
+msgid "Tools DataBase\tCtrl+D"
+msgstr "Banco de Dados de Ferramentas\tCtrl+D"
 
 #: flatcamGUI/FlatCAMGUI.py:401 flatcamGUI/FlatCAMGUI.py:2060
 msgid "View"
 msgstr "Ver"
 
 #: flatcamGUI/FlatCAMGUI.py:403
-msgid "Enable all plots\tALT+1"
-msgstr "Habilitar todos os gráficos\tALT+1"
+msgid "Enable all plots\tAlt+1"
+msgstr "Habilitar todos os gráficos\tAlt+1"
 
 #: flatcamGUI/FlatCAMGUI.py:405
-msgid "Disable all plots\tALT+2"
-msgstr "Desabilitar todos os gráficos\tALT+2"
+msgid "Disable all plots\tAlt+2"
+msgstr "Desabilitar todos os gráficos\tAlt+2"
 
 #: flatcamGUI/FlatCAMGUI.py:407
-msgid "Disable non-selected\tALT+3"
-msgstr "Desabilitar os não selecionados\tALT+3"
+msgid "Disable non-selected\tAlt+3"
+msgstr "Desabilitar os não selecionados\tAlt+3"
 
 #: flatcamGUI/FlatCAMGUI.py:411
 msgid "&Zoom Fit\tV"
@@ -5637,16 +5637,16 @@ msgid "Redraw All\tF5"
 msgstr "Redesenha Todos\tF5"
 
 #: flatcamGUI/FlatCAMGUI.py:424
-msgid "Toggle Code Editor\tSHIFT+E"
-msgstr "Alternar o Editor de Códigos\tSHIFT+E"
+msgid "Toggle Code Editor\tShift+E"
+msgstr "Alternar o Editor de Códigos\tShift+E"
 
 #: flatcamGUI/FlatCAMGUI.py:427
-msgid "&Toggle FullScreen\tALT+F10"
-msgstr "Alternar &Tela Cheia\tALT+F10"
+msgid "&Toggle FullScreen\tAlt+F10"
+msgstr "Alternar &Tela Cheia\tAlt+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:429
-msgid "&Toggle Plot Area\tCTRL+F10"
-msgstr "Al&ternar Área de Gráficos\tCTRL+F10"
+msgid "&Toggle Plot Area\tCtrl+F10"
+msgstr "Al&ternar Área de Gráficos\tCtrl+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:431
 msgid "&Toggle Project/Sel/Tool\t`"
@@ -5657,16 +5657,16 @@ msgid "&Toggle Grid Snap\tG"
 msgstr "Al&ternar Encaixe na Grade\tG"
 
 #: flatcamGUI/FlatCAMGUI.py:437
-msgid "&Toggle Grid Lines\tALT+G"
-msgstr "Al&ternar Encaixe na Grade\tALT+G"
+msgid "&Toggle Grid Lines\tAlt+G"
+msgstr "Al&ternar Encaixe na Grade\tAlt+G"
 
 #: flatcamGUI/FlatCAMGUI.py:439
-msgid "&Toggle Axis\tSHIFT+G"
-msgstr "Al&ternar Eixo\tSHIFT+G"
+msgid "&Toggle Axis\tShift+G"
+msgstr "Al&ternar Eixo\tShift+G"
 
 #: flatcamGUI/FlatCAMGUI.py:441
-msgid "Toggle Workspace\tSHIFT+W"
-msgstr "Alternar Área de Trabalho\tSHIFT+W"
+msgid "Toggle Workspace\tShift+W"
+msgstr "Alternar Área de Trabalho\tShift+W"
 
 #: flatcamGUI/FlatCAMGUI.py:446
 msgid "Objects"
@@ -5765,8 +5765,8 @@ msgid "Paint Tool\tI"
 msgstr "Ferramenta de Pintura\tI"
 
 #: flatcamGUI/FlatCAMGUI.py:543
-msgid "Transform Tool\tALT+R"
-msgstr "Ferramenta de Transformação\tALT+R"
+msgid "Transform Tool\tAlt+R"
+msgstr "Ferramenta de Transformação\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:547
 msgid "Toggle Corner Snap\tK"
@@ -5829,8 +5829,8 @@ msgid "Add Region\tN"
 msgstr "Adicionar Região\tN"
 
 #: flatcamGUI/FlatCAMGUI.py:598
-msgid "Poligonize\tALT+N"
-msgstr "Poligonizar\tALT+N"
+msgid "Poligonize\tAlt+N"
+msgstr "Poligonizar\tAlt+N"
 
 #: flatcamGUI/FlatCAMGUI.py:600
 msgid "Add SemiDisc\tE"
@@ -5849,16 +5849,16 @@ msgid "Scale\tS"
 msgstr "Escala\tS"
 
 #: flatcamGUI/FlatCAMGUI.py:608
-msgid "Mark Area\tALT+A"
-msgstr "Marcar Área\tALT+A"
+msgid "Mark Area\tAlt+A"
+msgstr "Marcar Área\tAlt+A"
 
 #: flatcamGUI/FlatCAMGUI.py:610
-msgid "Eraser\tCTRL+E"
-msgstr "Borracha\tCTRL+E"
+msgid "Eraser\tCtrl+E"
+msgstr "Borracha\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:612
-msgid "Transform\tALT+R"
-msgstr "Transformar\tALT+R"
+msgid "Transform\tAlt+R"
+msgstr "Transformar\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:639
 msgid "Enable Plot"
@@ -13846,7 +13846,7 @@ msgid ""
 "\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 "
+"- 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"
@@ -17554,8 +17554,8 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "\n"
 #~ "        "
 
-#~ msgid "Run Script ...\tSHIFT+S"
-#~ msgstr "Executar Script ...\tSHIFT+S"
+#~ msgid "Run Script ...\tShift+S"
+#~ msgstr "Executar Script ...\tShift+S"
 
 #~ msgid ""
 #~ "<font size=8><B>FlatCAM</B></font><BR>Version {version} {beta} ({date}) - "
@@ -17724,39 +17724,39 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+A</strong></td>\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 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 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 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 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 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 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 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 height=\"20\"><strong>Ctrl+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Toggle Plot Area</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -17764,39 +17764,39 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+C</strong></td>\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 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 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 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 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 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 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 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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -17804,59 +17804,59 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+C</strong></td>\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 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 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 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 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 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 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 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 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 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 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 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 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 height=\"20\"><strong>Alt+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Toggle Full Screen</td>\n"
 #~ "                    </tr>                 \n"
 #~ "                    <tr height=\"20\">\n"
@@ -17864,7 +17864,7 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+ALT+X</strong></"
+#~ "                        <td height=\"20\"><strong>Ctrl+Alt+X</strong></"
 #~ "td>\n"
 #~ "                        <td>&nbsp;Abort current task (gracefully)</td>\n"
 #~ "                    </tr>                    \n"
@@ -18016,39 +18016,39 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+A</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+A</strong></td>\n"
 #~ "                        <td>&nbsp;Seleciona Todos</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+C</strong></td>\n"
 #~ "                        <td>&nbsp;Copiar Objeto</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+E</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
 #~ "                        <td>&nbsp;Abrir Arquivo Excellon</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+G</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+G</strong></td>\n"
 #~ "                        <td>&nbsp;Abrir Arquivo Gerber</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+N</strong></td>\n"
 #~ "                        <td>&nbsp;Novo Projeto</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta de Medição</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+O</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+O</strong></td>\n"
 #~ "                        <td>&nbsp;Abrir Projeto</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Salvar Projeto Como</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+F10</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Alternar Área de Gráfico</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18056,39 +18056,39 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+C</strong></td>\n"
 #~ "                        <td>&nbsp;Copiar Obj_Name</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+E</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+E</strong></td>\n"
 #~ "                        <td>&nbsp;Alterna Editor de Código</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+G</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+G</strong></td>\n"
 #~ "                        <td>&nbsp;Alterna o Eixo</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+P</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+P</strong></td>\n"
 #~ "                        <td>&nbsp;Abre Janela de Preferências</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+R</strong></td>\n"
 #~ "                        <td>&nbsp;Gira 90 graus antihorário</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+S</strong></td>\n"
 #~ "                        <td>&nbsp;Executa um Script</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+W</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+W</strong></td>\n"
 #~ "                        <td>&nbsp;Alterna o Local de Trabalho</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
 #~ "                        <td>&nbsp;Inclina no Eixo X</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Inclina no Eixo Y</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18096,60 +18096,60 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+C</strong></td>\n"
 #~ "                        <td>&nbsp;Calculadoras</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+D</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+D</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta PCB 2-Faces</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+K</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+K</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Pasta de Solda</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+L</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+L</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Filme PCB</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Retirar Cobre (NCC)</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+P</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+P</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Pintura de Área</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+Q</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+Q</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Importar PDF</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Transformações</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+S</strong></td>\n"
 #~ "                        <td>&nbsp;Ver Arquivo Fonte</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+U</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+U</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Recorte PCB</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+1</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+1</strong></td>\n"
 #~ "                        <td>&nbsp;Habilita todos os Gráficos</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+2</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+2</strong></td>\n"
 #~ "                        <td>&nbsp;Desabilita todos os Gráficos</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+3</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+3</strong></td>\n"
 #~ "                        <td>&nbsp;Desabilita todos os Gráficos não "
 #~ "selecionados</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+F10</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Alterna Tela Cheia</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18283,11 +18283,11 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18295,15 +18295,15 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\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 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 height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18311,15 +18311,15 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\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 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 height=\"20\"><strong>Ctrl+X</strong></td>\n"
 #~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18411,7 +18411,7 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -18499,11 +18499,11 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <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 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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18511,15 +18511,15 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                     <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+A</strong></td>\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 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 height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Transformation Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -18620,11 +18620,11 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
 #~ "                        <td>&nbsp;Inclina a forma no eixo X</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Inclina a forma no eixo Y</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18632,16 +18632,16 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Editor de Transformação</"
 #~ "td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+X</strong></td>\n"
 #~ "                        <td>&nbsp;Desloca a forma no eixo X</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Desloca a forma no eixo Y</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18649,15 +18649,15 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta de Medição</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Salvar Objeto e Sair do Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+X</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta de Corte de Polígono</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18754,7 +18754,7 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;Abortar e retornar para a Seleção</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Salvar Objeto e Sair do Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -18843,11 +18843,11 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;Abortar e retornar para a Seleção</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+E</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Apagador</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Salvar Objeto e Sair do Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18855,15 +18855,15 @@ msgstr "Nenhum nome de geometria nos argumentos. Altere e tente novamente."
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                     <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+A</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+A</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Marcar Área</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Poligonizar</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Ferramenta Transformação</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"

BIN
locale/ro/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 326 - 266
locale/ro/LC_MESSAGES/strings.po


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

@@ -5208,8 +5208,8 @@ msgid "File"
 msgstr "Файл"
 
 #: flatcamGUI/FlatCAMGUI.py:69
-msgid "&New Project ...\tCTRL+N"
-msgstr "&Новый проект ...\tCTRL+N"
+msgid "&New Project ...\tCtrl+N"
+msgstr "&Новый проект ...\tCtrl+N"
 
 #: flatcamGUI/FlatCAMGUI.py:71
 msgid "Will create a new, blank project"
@@ -5261,12 +5261,12 @@ msgid "Open &Project ..."
 msgstr "Открыть &проект..."
 
 #: flatcamGUI/FlatCAMGUI.py:109 flatcamGUI/FlatCAMGUI.py:4121
-msgid "Open &Gerber ...\tCTRL+G"
-msgstr "Открыть &Gerber...\tCTRL+G"
+msgid "Open &Gerber ...\tCtrl+G"
+msgstr "Открыть &Gerber...\tCtrl+G"
 
 #: flatcamGUI/FlatCAMGUI.py:114 flatcamGUI/FlatCAMGUI.py:4126
-msgid "Open &Excellon ...\tCTRL+E"
-msgstr "Открыть &Excellon ...\tCTRL+E"
+msgid "Open &Excellon ...\tCtrl+E"
+msgstr "Открыть &Excellon ...\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:118 flatcamGUI/FlatCAMGUI.py:4131
 msgid "Open G-&Code ..."
@@ -5417,8 +5417,8 @@ msgid "&Save Project ..."
 msgstr "&Сохранить проект ..."
 
 #: flatcamGUI/FlatCAMGUI.py:256
-msgid "Save Project &As ...\tCTRL+S"
-msgstr "Сохранить проект &как ...\tCTRL+S"
+msgid "Save Project &As ...\tCtrl+S"
+msgstr "Сохранить проект &как ...\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:261
 msgid "Save Project C&opy ..."
@@ -5438,8 +5438,8 @@ msgid "Edit Object\tE"
 msgstr "Редактировать объект\tE"
 
 #: flatcamGUI/FlatCAMGUI.py:285
-msgid "Close Editor\tCTRL+S"
-msgstr "Закрыть редактор\tCTRL+S"
+msgid "Close Editor\tCtrl+S"
+msgstr "Закрыть редактор\tCtrl+S"
 
 #: flatcamGUI/FlatCAMGUI.py:294
 msgid "Conversion"
@@ -5515,8 +5515,8 @@ msgid "Convert Any to Gerber"
 msgstr "Конвертировать любой объект в Gerber"
 
 #: flatcamGUI/FlatCAMGUI.py:341
-msgid "&Copy\tCTRL+C"
-msgstr "&Копировать\tCTRL+C"
+msgid "&Copy\tCtrl+C"
+msgstr "&Копировать\tCtrl+C"
 
 #: flatcamGUI/FlatCAMGUI.py:346
 msgid "&Delete\tDEL"
@@ -5535,28 +5535,28 @@ msgid "Toggle Units\tQ"
 msgstr "Единицы измерения\tQ"
 
 #: flatcamGUI/FlatCAMGUI.py:360
-msgid "&Select All\tCTRL+A"
-msgstr "&Выбрать все\tCTRL+A"
+msgid "&Select All\tCtrl+A"
+msgstr "&Выбрать все\tCtrl+A"
 
 #: flatcamGUI/FlatCAMGUI.py:365
-msgid "&Preferences\tSHIFT+P"
-msgstr "&Настройки\tSHIFT+P"
+msgid "&Preferences\tShift+P"
+msgstr "&Настройки\tShift+P"
 
 #: flatcamGUI/FlatCAMGUI.py:371 flatcamTools/ToolProperties.py:153
 msgid "Options"
 msgstr "Опции"
 
 #: flatcamGUI/FlatCAMGUI.py:373
-msgid "&Rotate Selection\tSHIFT+(R)"
-msgstr "&Вращение\tSHIFT+(R)"
+msgid "&Rotate Selection\tShift+(R)"
+msgstr "&Вращение\tShift+(R)"
 
 #: flatcamGUI/FlatCAMGUI.py:378
-msgid "&Skew on X axis\tSHIFT+X"
-msgstr "&Наклон по оси X\tSHIFT+X"
+msgid "&Skew on X axis\tShift+X"
+msgstr "&Наклон по оси X\tShift+X"
 
 #: flatcamGUI/FlatCAMGUI.py:380
-msgid "S&kew on Y axis\tSHIFT+Y"
-msgstr "Н&аклон по оси Y\tSHIFT+Y"
+msgid "S&kew on Y axis\tShift+Y"
+msgstr "Н&аклон по оси Y\tShift+Y"
 
 #: flatcamGUI/FlatCAMGUI.py:385
 msgid "Flip on &X axis\tX"
@@ -5567,28 +5567,28 @@ msgid "Flip on &Y axis\tY"
 msgstr "Отразить по оси &Y\tY"
 
 #: flatcamGUI/FlatCAMGUI.py:392
-msgid "View source\tALT+S"
-msgstr "Просмотреть код\tALT+S"
+msgid "View source\tAlt+S"
+msgstr "Просмотреть код\tAlt+S"
 
 #: flatcamGUI/FlatCAMGUI.py:394
-msgid "Tools DataBase\tCTRL+D"
-msgstr "База данных\tCTRL+D"
+msgid "Tools DataBase\tCtrl+D"
+msgstr "База данных\tCtrl+D"
 
 #: flatcamGUI/FlatCAMGUI.py:401 flatcamGUI/FlatCAMGUI.py:2060
 msgid "View"
 msgstr "Вид"
 
 #: flatcamGUI/FlatCAMGUI.py:403
-msgid "Enable all plots\tALT+1"
-msgstr "Включить все участки\tALT+1"
+msgid "Enable all plots\tAlt+1"
+msgstr "Включить все участки\tAlt+1"
 
 #: flatcamGUI/FlatCAMGUI.py:405
-msgid "Disable all plots\tALT+2"
-msgstr "Отключить все участки\tALT+2"
+msgid "Disable all plots\tAlt+2"
+msgstr "Отключить все участки\tAlt+2"
 
 #: flatcamGUI/FlatCAMGUI.py:407
-msgid "Disable non-selected\tALT+3"
-msgstr "Отключить не выбранные\tALT+3"
+msgid "Disable non-selected\tAlt+3"
+msgstr "Отключить не выбранные\tAlt+3"
 
 #: flatcamGUI/FlatCAMGUI.py:411
 msgid "&Zoom Fit\tV"
@@ -5607,16 +5607,16 @@ msgid "Redraw All\tF5"
 msgstr "Перерисовать всё\tF5"
 
 #: flatcamGUI/FlatCAMGUI.py:424
-msgid "Toggle Code Editor\tSHIFT+E"
-msgstr "Переключить редактор кода\tSHIFT+E"
+msgid "Toggle Code Editor\tShift+E"
+msgstr "Переключить редактор кода\tShift+E"
 
 #: flatcamGUI/FlatCAMGUI.py:427
-msgid "&Toggle FullScreen\tALT+F10"
-msgstr "&Во весь экран\tALT+F10"
+msgid "&Toggle FullScreen\tAlt+F10"
+msgstr "&Во весь экран\tAlt+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:429
-msgid "&Toggle Plot Area\tCTRL+F10"
-msgstr "&Рабочая область\tCTRL+F10"
+msgid "&Toggle Plot Area\tCtrl+F10"
+msgstr "&Рабочая область\tCtrl+F10"
 
 #: flatcamGUI/FlatCAMGUI.py:431
 msgid "&Toggle Project/Sel/Tool\t`"
@@ -5627,16 +5627,16 @@ msgid "&Toggle Grid Snap\tG"
 msgstr "&Привязка к сетке\tG"
 
 #: flatcamGUI/FlatCAMGUI.py:437
-msgid "&Toggle Grid Lines\tALT+G"
-msgstr "&Переключить линии сетки \tALT+G"
+msgid "&Toggle Grid Lines\tAlt+G"
+msgstr "&Переключить линии сетки \tAlt+G"
 
 #: flatcamGUI/FlatCAMGUI.py:439
-msgid "&Toggle Axis\tSHIFT+G"
-msgstr "&Оси\tSHIFT+G"
+msgid "&Toggle Axis\tShift+G"
+msgstr "&Оси\tShift+G"
 
 #: flatcamGUI/FlatCAMGUI.py:441
-msgid "Toggle Workspace\tSHIFT+W"
-msgstr "Границы рабочего пространства\tSHIFT+W"
+msgid "Toggle Workspace\tShift+W"
+msgstr "Границы рабочего пространства\tShift+W"
 
 #: flatcamGUI/FlatCAMGUI.py:446
 msgid "Objects"
@@ -5735,8 +5735,8 @@ msgid "Paint Tool\tI"
 msgstr "Рисование\tI"
 
 #: flatcamGUI/FlatCAMGUI.py:543
-msgid "Transform Tool\tALT+R"
-msgstr "Трансформация\tALT+R"
+msgid "Transform Tool\tAlt+R"
+msgstr "Трансформация\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:547
 msgid "Toggle Corner Snap\tK"
@@ -5799,8 +5799,8 @@ msgid "Add Region\tN"
 msgstr "Добавить регион\tN"
 
 #: flatcamGUI/FlatCAMGUI.py:598
-msgid "Poligonize\tALT+N"
-msgstr "Полигонизация\tALT+N"
+msgid "Poligonize\tAlt+N"
+msgstr "Полигонизация\tAlt+N"
 
 #: flatcamGUI/FlatCAMGUI.py:600
 msgid "Add SemiDisc\tE"
@@ -5819,16 +5819,16 @@ msgid "Scale\tS"
 msgstr "Масштабировать\tS"
 
 #: flatcamGUI/FlatCAMGUI.py:608
-msgid "Mark Area\tALT+A"
-msgstr "Обозначить области\tALT+A"
+msgid "Mark Area\tAlt+A"
+msgstr "Обозначить области\tAlt+A"
 
 #: flatcamGUI/FlatCAMGUI.py:610
-msgid "Eraser\tCTRL+E"
-msgstr "Ластик\tCTRL+E"
+msgid "Eraser\tCtrl+E"
+msgstr "Ластик\tCtrl+E"
 
 #: flatcamGUI/FlatCAMGUI.py:612
-msgid "Transform\tALT+R"
-msgstr "Трансформировать\tALT+R"
+msgid "Transform\tAlt+R"
+msgstr "Трансформировать\tAlt+R"
 
 #: flatcamGUI/FlatCAMGUI.py:639
 msgid "Enable Plot"
@@ -13901,7 +13901,7 @@ msgid ""
 "\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 "
+"- 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"
@@ -17736,8 +17736,8 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ msgid "[success] Paint done."
 #~ msgstr "[success] Окраска выполнена."
 
-#~ msgid "Run Script ...\tSHIFT+S"
-#~ msgstr "Выполнить сценарий ...\tSHIFT+S"
+#~ msgid "Run Script ...\tShift+S"
+#~ msgstr "Выполнить сценарий ...\tShift+S"
 
 #~ msgid "About"
 #~ msgstr "О программе"
@@ -17853,39 +17853,39 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+A</strong></td>\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 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 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 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 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 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 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 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 height=\"20\"><strong>Ctrl+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Toggle Plot Area</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -17893,39 +17893,39 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+C</strong></td>\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 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 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 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 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 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 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 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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -17933,59 +17933,59 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+C</strong></td>\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 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 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 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 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 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 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 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 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 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 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 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 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 height=\"20\"><strong>Alt+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Toggle Full Screen</td>\n"
 #~ "                    </tr>                 \n"
 #~ "                    <tr height=\"20\">\n"
@@ -17993,7 +17993,7 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+ALT+X</strong></"
+#~ "                        <td height=\"20\"><strong>Ctrl+Alt+X</strong></"
 #~ "td>\n"
 #~ "                        <td>&nbsp;Abort current task (gracefully)</td>\n"
 #~ "                    </tr>                    \n"
@@ -18151,39 +18151,39 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+A</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+A</strong></td>\n"
 #~ "                        <td>&nbsp;Выбрать всё</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+C</strong></td>\n"
 #~ "                        <td>&nbsp;Копировать</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+E</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
 #~ "                        <td>&nbsp;Открыть Excellon</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+G</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+G</strong></td>\n"
 #~ "                        <td>&nbsp;Открыть Gerber</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+N</strong></td>\n"
 #~ "                        <td>&nbsp;Новый проект</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
 #~ "                        <td>&nbsp;Измеритель</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+O</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+O</strong></td>\n"
 #~ "                        <td>&nbsp;Открыть проект</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Сохранить проект как</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+F10</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Переключить рабочую область</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18191,40 +18191,40 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+C</strong></td>\n"
 #~ "                        <td>&nbsp;Копировать имя объекта</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+E</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+E</strong></td>\n"
 #~ "                        <td>&nbsp;Редактор кода</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+G</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+G</strong></td>\n"
 #~ "                        <td>&nbsp;Оси</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+P</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+P</strong></td>\n"
 #~ "                        <td>&nbsp;Настройки</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+R</strong></td>\n"
 #~ "                        <td>&nbsp;Поворот на 90 градусов против часовой "
 #~ "стрелки</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+S</strong></td>\n"
 #~ "                        <td>&nbsp;Выполнить сценарий</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+W</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+W</strong></td>\n"
 #~ "                        <td>&nbsp;Границы рабочего пространства</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
 #~ "                        <td>&nbsp;Наклон по оси X</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Наклон по оси Y</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18232,59 +18232,59 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+C</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+C</strong></td>\n"
 #~ "                        <td>&nbsp;Калькуляторы</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+D</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+D</strong></td>\n"
 #~ "                        <td>&nbsp;2-х сторонняя плата</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+K</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+K</strong></td>\n"
 #~ "                        <td>&nbsp;Паяльная паста</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+L</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+L</strong></td>\n"
 #~ "                        <td>&nbsp;Плёнка</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
 #~ "                        <td>&nbsp;Очистка от меди</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+P</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+P</strong></td>\n"
 #~ "                        <td>&nbsp;Область рисования</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+Q</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+Q</strong></td>\n"
 #~ "                        <td>&nbsp;Импорт PDF</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Трансформация</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+S</strong></td>\n"
 #~ "                        <td>&nbsp;Просмотреть код</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+U</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+U</strong></td>\n"
 #~ "                        <td>&nbsp;Обрезка платы</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+1</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+1</strong></td>\n"
 #~ "                        <td>&nbsp;Включить все участки</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+2</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+2</strong></td>\n"
 #~ "                        <td>&nbsp;Отключить все участки</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+3</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+3</strong></td>\n"
 #~ "                        <td>&nbsp;Отключить не выбранные</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+F10</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+F10</strong></td>\n"
 #~ "                        <td>&nbsp;Во весь экран</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18418,11 +18418,11 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\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 height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Skew shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18430,15 +18430,15 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\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 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 height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Offset shape on Y axis</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18446,15 +18446,15 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\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 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 height=\"20\"><strong>Ctrl+X</strong></td>\n"
 #~ "                        <td>&nbsp;Polygon Cut Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18546,7 +18546,7 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -18634,11 +18634,11 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <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 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 height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Save Object and Exit Editor</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18646,15 +18646,15 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                     <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+A</strong></td>\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 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 height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Transformation Tool</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"
@@ -18753,11 +18753,11 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+X</strong></td>\n"
 #~ "                        <td>&nbsp;Наклонить форму по оси X</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>SHIFT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Shift+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Наклонить форму по оси Y</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18765,15 +18765,15 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Трансформация</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+X</strong></td>\n"
 #~ "                        <td>&nbsp;Смещение формы по оси X</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+Y</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+Y</strong></td>\n"
 #~ "                        <td>&nbsp;Смещение формы по оси Y</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18781,16 +18781,16 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+M</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+M</strong></td>\n"
 #~ "                        <td>&nbsp;Измеритель</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Сохранить объект и закрыть редактор</"
 #~ "td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+X</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+X</strong></td>\n"
 #~ "                        <td>&nbsp;Обрезка полигонов</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
@@ -18886,7 +18886,7 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;Прервать и вернуться к выбору</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Сохранить объект и закрыть редактор</"
 #~ "td>\n"
 #~ "                    </tr>\n"
@@ -18976,11 +18976,11 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;Прервать и вернуться к выбору</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+E</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+E</strong></td>\n"
 #~ "                        <td>&nbsp;Ластик</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>CTRL+S</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Ctrl+S</strong></td>\n"
 #~ "                        <td>&nbsp;Сохранить объект и закрыть редактор</"
 #~ "td>\n"
 #~ "                    </tr>\n"
@@ -18989,15 +18989,15 @@ msgstr "Нет имени геометрии в аргументах. Укажи
 #~ "                        <td>&nbsp;</td>\n"
 #~ "                    </tr>\n"
 #~ "                     <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+A</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+A</strong></td>\n"
 #~ "                        <td>&nbsp;Обозначить области</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+N</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+N</strong></td>\n"
 #~ "                        <td>&nbsp;Полигонизация</td>\n"
 #~ "                    </tr>\n"
 #~ "                    <tr height=\"20\">\n"
-#~ "                        <td height=\"20\"><strong>ALT+R</strong></td>\n"
+#~ "                        <td height=\"20\"><strong>Alt+R</strong></td>\n"
 #~ "                        <td>&nbsp;Трансформация</td>\n"
 #~ "                    </tr>\n"
 #~ "                </tbody>\n"

Plik diff jest za duży
+ 423 - 480
locale_template/strings.pot


+ 2 - 2
make_freezed.py

@@ -108,9 +108,9 @@ exe = Executable("FlatCAM.py", icon='share/flatcam_icon48.ico', base=base, targe
 
 setup(
     name="FlatCAM",
-    author="Juan Pablo Caram",
+    author="Community effort",
     version="8.9",
-    description="FlatCAM: 2D Computer Aided PCB Manufacturing",
+    description="FlatCAM Evo: 2D Computer Aided PCB Manufacturing",
     options=dict(build_exe=buildOptions),
     executables=[exe]
 )

+ 49 - 16
preprocessors/Berta_CNC.py

@@ -22,7 +22,7 @@ class Berta_CNC(FlatCAMPostProc):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         coords_xy = p['xy_toolchange']
-        gcode = ''
+        gcode = '(This preprocessor is used with a BERTA CNC router.)\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -35,24 +35,57 @@ class Berta_CNC(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
         if str(p['options']['type']) == 'Geometry':
+            gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
             gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
-
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
             if p['multidepth'] is True:
                 gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
                          str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
+
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
+
+            if p['multidepth'] is True:
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
+
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
@@ -80,7 +113,7 @@ class Berta_CNC(FlatCAMPostProc):
         gcode += 'G54\n'
         gcode += 'G0\n'
         gcode += '(Berta)\n'
-        gcode += 'G94\n'
+        gcode += 'G94'
 
         return gcode
 
@@ -194,10 +227,10 @@ M0""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolcha
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
 
         gcode += '(Berta)\n'

+ 17 - 11
preprocessors/grbl_laser.py → preprocessors/GRBL_laser.py

@@ -12,7 +12,7 @@ from FlatCAMPostProc import *
 # is compatible with almost any version of Grbl.
 
 
-class grbl_laser(FlatCAMPostProc):
+class GRBL_laser(FlatCAMPostProc):
 
     include_header = True
     coordinate_format = "%.*f"
@@ -20,7 +20,8 @@ class grbl_laser(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        gcode = ''
+        gcode = '(This preprocessor is used with a motion controller loaded with GRBL firmware. )\n'
+        gcode += '(It is for the case when it is used together with a LASER connected on the SPINDLE connector.)\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -28,7 +29,9 @@ class grbl_laser(FlatCAMPostProc):
         ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
 
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+        gcode += '(Feedrate rapids: ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+
+        gcode += '(Z Focus: ' + str(p['z_move']) + units + ')\n'
 
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
@@ -40,10 +43,12 @@ class grbl_laser(FlatCAMPostProc):
         gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
         gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
 
+        gcode += '(Laser Power (Spindle Speed): ' + str(p['spindlespeed']) + ')\n\n'
+
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += 'G90\n'
         gcode += 'G17\n'
-        gcode += 'G94\n'
+        gcode += 'G94'
 
         return gcode
 
@@ -51,19 +56,20 @@ class grbl_laser(FlatCAMPostProc):
         return ''
 
     def lift_code(self, p):
-        return 'M05 S0'
+        return 'M5'
 
     def down_code(self, p):
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         if p.spindlespeed:
-            return 'M03 S%d' % p.spindlespeed
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
-            return 'M03'
+            return sdir
 
     def toolchange_code(self, p):
         return ''
 
     def up_to_zero_code(self, p):
-        return 'M05'
+        return 'M5'
 
     def position_code(self, p):
         return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
@@ -77,10 +83,10 @@ class grbl_laser(FlatCAMPostProc):
                ' F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 
@@ -101,4 +107,4 @@ class grbl_laser(FlatCAMPostProc):
         return ''
 
     def spindle_stop_code(self, p):
-        return 'M05'
+        return 'M5'

+ 46 - 18
preprocessors/ISEL_CNC.py

@@ -17,7 +17,7 @@ class ISEL_CNC(FlatCAMPostProc):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         coords_xy = p['xy_toolchange']
-        gcode = ''
+        gcode = '(This preprocessor is used with a ISEL CNC router.)\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -26,28 +26,56 @@ class ISEL_CNC(FlatCAMPostProc):
 
         if str(p['options']['type']) == 'Geometry':
             gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            if p['multidepth'] is True:
+                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
+                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
 
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
 
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
@@ -129,10 +157,10 @@ M01""".format(tool=int(p.tool), toolC=toolC_formatted)
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 61 - 26
preprocessors/ISEL_ICP_CNC.py

@@ -15,7 +15,7 @@ class ISEL_ICP_CNC(FlatCAMPostProc):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         coords_xy = p['xy_toolchange']
-        gcode = ''
+        gcode = '; This preprocessor is used with a ISEL ICP CNC router.\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -25,36 +25,71 @@ class ISEL_ICP_CNC(FlatCAMPostProc):
         gcode += 'IMF_PBL flatcam\n\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '; TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n'
-        gcode += '; Spindle Speed: %s RPM\n' % str(p['spindlespeed'])
-        gcode += '; Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '; Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + '\n'
-        gcode += '\n'
-        gcode += '; Z_Cut: ' + str(p['z_cut']) + units + '\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n'
+            gcode += ';Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + '\n'
+            gcode += ';Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + '\n'
+            gcode += ';Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
+            gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
             if p['multidepth'] is True:
-                gcode += '; DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
+                gcode += ';DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
                          str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + '\n'
-        gcode += '; Z_Move: ' + str(p['z_move']) + units + '\n'
-        gcode += '; Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
-        if coords_xy is not None:
-            gcode += '; X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                            p.decimals, coords_xy[1]) + units + '\n'
-        else:
-            gcode += '; X,Y Toolchange: ' + "None" + units + '\n'
-        gcode += '; Z Start: ' + str(p['startz']) + units + '\n'
-        gcode += '; Z End: ' + str(p['z_end']) + units + '\n'
-        gcode += '; Steps per circle: ' + str(p['steps_per_circle']) + '\n'
+            gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
+
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n;TOOLS DIAMETER: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + '\n'
+
+            gcode += '\n;FEEDRATE Z: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + '\n'
+
+            gcode += '\n;FEEDRATE RAPIDS: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + '\n'
+
+            gcode += '\n;Z_CUT: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + '\n'
+
+            gcode += '\n;Tools Offset: \n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + '\n'
+
+            if p['multidepth'] is True:
+                gcode += '\n;DEPTH_PER_CUT: \n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += ';Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + '\n'
+
+            gcode += '\n;Z_MOVE: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + '\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += ';Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
+
+            if coords_xy is not None:
+                gcode += ';X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + '\n'
+            else:
+                gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
+
+        gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
+        gcode += ';Z End: ' + str(p['z_end']) + units + '\n'
+        gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
+
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += '; Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
+            gcode += ';Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n' + '\n'
         else:
-            gcode += '; Preprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n'
-        gcode += '\n'
+            gcode += ';Preprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' + '\n'
+
+        gcode += ';X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
+        gcode += ';Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n\n'
 
-        gcode += '; X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
-        gcode += '; Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n'
+        gcode += ';Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
 
         return gcode
 

+ 56 - 26
preprocessors/marlin.py → preprocessors/Marlin.py

@@ -9,7 +9,7 @@
 from FlatCAMPostProc import *
 
 
-class marlin(FlatCAMPostProc):
+class Marlin(FlatCAMPostProc):
 
     include_header = True
     coordinate_format = "%.*f"
@@ -27,47 +27,74 @@ class marlin(FlatCAMPostProc):
         ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n' + '\n'
-
-        gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n'
+            gcode += ';Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + '\n'
             gcode += ';Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + '\n'
-
-        gcode += ';Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
-        gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += ';Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
+            gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
             if p['multidepth'] is True:
                 gcode += ';DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
                          str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + '\n'
+            gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
 
-        gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
-        gcode += ';Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n;TOOLS DIAMETER: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + '\n'
 
-        if coords_xy is not None:
-            gcode += ';X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + '\n'
-        else:
-            gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
+            gcode += '\n;FEEDRATE Z: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + '\n'
+
+            gcode += '\n;FEEDRATE RAPIDS: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + '\n'
+
+            gcode += '\n;Z_CUT: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + '\n'
+
+            gcode += '\n;Tools Offset: \n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + '\n'
+
+            if p['multidepth'] is True:
+                gcode += '\n;DEPTH_PER_CUT: \n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += ';Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + '\n'
+
+            gcode += '\n;Z_MOVE: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + '\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += ';Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
+
+            if coords_xy is not None:
+                gcode += ';X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + '\n'
+            else:
+                gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
 
         gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
         gcode += ';Z End: ' + str(p['z_end']) + units + '\n'
         gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += ';Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
+            gcode += ';Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n' + '\n'
         else:
             gcode += ';Preprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' + '\n'
 
         gcode += ';X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
         gcode += ';Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n\n'
 
-        gcode += ';Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + '\n' + '\n'
+        gcode += ';Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
 
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
-        gcode += 'G90\n'
-        gcode += 'G94\n'
+        gcode += 'G90'
 
         return gcode
 
@@ -188,10 +215,10 @@ G0 Z{z_toolchange}
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
 
         return gcode
@@ -219,8 +246,11 @@ G0 Z{z_toolchange}
             return sdir
 
     def dwell_code(self, p):
+        gcode = 'G4 P' + str(p.dwelltime)
         if p.dwelltime:
-            return 'G4 P' + str(p.dwelltime)
+            return gcode
 
     def spindle_stop_code(self, p):
-        return 'M5'
+        gcode = 'M400\n'
+        gcode += 'M5'
+        return gcode

+ 120 - 0
preprocessors/Marlin_laser_FAN_pin.py

@@ -0,0 +1,120 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# Website:      http://flatcam.org                         #
+# File Author:  Marius Adrian Stanciu (c)                  #
+# Date:         8-Feb-2020                                 #
+# License:      MIT Licence                                #
+# ##########################################################
+
+from FlatCAMPostProc import *
+
+
+class Marlin_laser_FAN_pin(FlatCAMPostProc):
+
+    include_header = True
+    coordinate_format = "%.*f"
+    feedrate_format = '%.*f'
+    feedrate_rapid_format = feedrate_format
+
+    def start_code(self, p):
+        units = ' ' + str(p['units']).lower()
+        coords_xy = p['xy_toolchange']
+        gcode = ';This preprocessor is used with a motion controller loaded with MARLIN firmware.\n'
+        gcode += ';It is for the case when it is used together with a LASER connected on one of the FAN pins.\n\n'
+
+        xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
+        xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
+        ymin = '%.*f' % (p.coords_decimals, p['options']['ymin'])
+        ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
+
+        gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
+        gcode += ';Feedrate rapids: ' + str(p['feedrate_rapid']) + units + '/min' + '\n\n'
+
+        gcode += ';Z Focus: ' + str(p['z_move']) + units + '\n'
+
+        gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
+
+        if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            gcode += ';Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
+        else:
+            gcode += ';Preprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' + '\n'
+
+        gcode += ';X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
+        gcode += ';Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n\n'
+
+        gcode += ';Laser Power (Spindle Speed): ' + str(p['spindlespeed']) + '\n' + '\n'
+
+        gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
+        gcode += 'G90'
+
+        return gcode
+
+    def startz_code(self, p):
+        if p.startz is not None:
+            return 'G0 Z' + self.coordinate_format % (p.coords_decimals, p.z_move)
+        else:
+            return ''
+
+    def lift_code(self, p):
+        gcode = 'M400\n'
+        gcode += 'M107'
+        return gcode
+
+    def down_code(self, p):
+        if p.spindlespeed:
+            return '%s S%s' % ('M106', str(p.spindlespeed))
+        else:
+            return 'M106'
+
+    def toolchange_code(self, p):
+        return ''
+
+    def up_to_zero_code(self, p):
+        gcode = 'M400\n'
+        gcode += 'M107'
+        return gcode
+
+    def position_code(self, p):
+        return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
+               (p.coords_decimals, p.x, p.coords_decimals, p.y)
+
+    def rapid_code(self, p):
+        return ('G0 ' + self.position_code(p)).format(**p) + " " + self.feedrate_rapid_code(p)
+
+    def linear_code(self, p):
+        return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
+
+    def end_code(self, p):
+        coords_xy = p['xy_end']
+        gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
+
+        if coords_xy and coords_xy != '':
+            gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
+
+        return gcode
+
+    def feedrate_code(self, p):
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
+
+    def z_feedrate_code(self, p):
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
+
+    def inline_feedrate_code(self, p):
+        return 'F' + self.feedrate_format % (p.fr_decimals, p.feedrate)
+
+    def feedrate_rapid_code(self, p):
+        return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
+
+    def spindle_code(self, p):
+        if p.spindlespeed:
+            return 'M106 S%s' % str(p.spindlespeed)
+        else:
+            return 'M106'
+
+    def dwell_code(self, p):
+        return ''
+
+    def spindle_stop_code(self, p):
+        gcode = 'M400\n'
+        gcode += 'M106 S0'
+        return gcode

+ 122 - 0
preprocessors/Marlin_laser_Spindle_pin.py

@@ -0,0 +1,122 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# Website:      http://flatcam.org                         #
+# File Author:  Marius Adrian Stanciu (c)                  #
+# Date:         8-Feb-2020                                 #
+# License:      MIT Licence                                #
+# ##########################################################
+
+from FlatCAMPostProc import *
+
+
+class Marlin_laser_Spindle_pin(FlatCAMPostProc):
+
+    include_header = True
+    coordinate_format = "%.*f"
+    feedrate_format = '%.*f'
+    feedrate_rapid_format = feedrate_format
+
+    def start_code(self, p):
+        units = ' ' + str(p['units']).lower()
+        coords_xy = p['xy_toolchange']
+        gcode = ';This preprocessor is used with a motion controller loaded with MARLIN firmware.\n'
+        gcode += ';It is for the case when it is used together with a LASER connected on the SPINDLE connector.\n\n'
+
+        xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
+        xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
+        ymin = '%.*f' % (p.coords_decimals, p['options']['ymin'])
+        ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
+
+        gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
+        gcode += ';Feedrate rapids: ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
+
+        gcode += ';Z Focus: ' + str(p['z_move']) + units + '\n'
+
+        gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
+
+        if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            gcode += ';Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
+        else:
+            gcode += ';Preprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' + '\n'
+
+        gcode += ';X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
+        gcode += ';Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n\n'
+
+        gcode += ';Laser Power (Spindle Speed): ' + str(p['spindlespeed']) + '\n' + '\n'
+
+        gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
+        gcode += 'G90'
+
+        return gcode
+
+    def startz_code(self, p):
+        if p.startz is not None:
+            return 'G0 Z' + self.coordinate_format % (p.coords_decimals, p.z_move)
+        else:
+            return ''
+
+    def lift_code(self, p):
+        gcode = 'M400\n'
+        gcode += 'M5'
+        return gcode
+
+    def down_code(self, p):
+        sdir = {'CW': 'M3', 'CCW': 'M4'}[p.spindledir]
+        if p.spindlespeed:
+            return '%s S%s' % (sdir, str(p.spindlespeed))
+        else:
+            return sdir
+
+    def toolchange_code(self, p):
+        return ''
+
+    def up_to_zero_code(self, p):
+        gcode = 'M400\n'
+        gcode += 'M5'
+        return gcode
+
+    def position_code(self, p):
+        return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
+               (p.coords_decimals, p.x, p.coords_decimals, p.y)
+
+    def rapid_code(self, p):
+        return ('G0 ' + self.position_code(p)).format(**p) + " " + self.feedrate_rapid_code(p)
+
+    def linear_code(self, p):
+        return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
+
+    def end_code(self, p):
+        coords_xy = p['xy_end']
+        gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
+
+        if coords_xy and coords_xy != '':
+            gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
+
+        return gcode
+
+    def feedrate_code(self, p):
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
+
+    def z_feedrate_code(self, p):
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
+
+    def inline_feedrate_code(self, p):
+        return 'F' + self.feedrate_format % (p.fr_decimals, p.feedrate)
+
+    def feedrate_rapid_code(self, p):
+        return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
+
+    def spindle_code(self, p):
+        sdir = {'CW': 'M3', 'CCW': 'M4'}[p.spindledir]
+        if p.spindlespeed:
+            return '%s S%s' % (sdir, str(p.spindlespeed))
+        else:
+            return sdir
+
+    def dwell_code(self, p):
+        return ''
+
+    def spindle_stop_code(self, p):
+        gcode = 'M400\n'
+        gcode += 'M5'
+        return gcode

+ 2 - 2
preprocessors/Paste_1.py

@@ -122,10 +122,10 @@ G00 Z{z_toolchange}
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",") if a != '']
+        coords_xy = [float(eval(a)) for a in p['xy_end'].split(",") if a != '']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, float(p['z_toolchange'])) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 50 - 22
preprocessors/Repetier.py

@@ -19,7 +19,7 @@ class Repetier(FlatCAMPostProc):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         coords_xy = p['xy_toolchange']
-        gcode = ''
+        gcode = ';This preprocessor is used with a motion controller loaded with REPETIER firmware.\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -27,43 +27,71 @@ class Repetier(FlatCAMPostProc):
         ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n' + '\n'
-
-        gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n'
+            gcode += ';Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + '\n'
             gcode += ';Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + '\n'
-
-        gcode += ';Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
-        gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += ';Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
+            gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
             if p['multidepth'] is True:
                 gcode += ';DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
                          str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + '\n'
+            gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
 
-        gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
-        gcode += ';Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n;TOOLS DIAMETER: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + '\n'
 
-        if coords_xy is not None:
-            gcode += ';X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + '\n'
-        else:
-            gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
+            gcode += '\n;FEEDRATE Z: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + '\n'
+
+            gcode += '\n;FEEDRATE RAPIDS: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + '\n'
+
+            gcode += '\n;Z_CUT: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + '\n'
+
+            gcode += '\n;Tools Offset: \n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + '\n'
+
+            if p['multidepth'] is True:
+                gcode += '\n;DEPTH_PER_CUT: \n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += ';Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + '\n'
+
+            gcode += '\n;Z_MOVE: \n'
+            for tool, val in p['exc_tools'].items():
+                gcode += ';Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + '\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += ';Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
+
+            if coords_xy is not None:
+                gcode += ';X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + '\n'
+            else:
+                gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
 
         gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
         gcode += ';Z End: ' + str(p['z_end']) + units + '\n'
         gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += ';Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
+            gcode += ';Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n' + '\n'
         else:
             gcode += ';Preprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' + '\n'
 
         gcode += ';X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
         gcode += ';Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n\n'
 
-        gcode += ';Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + '\n' + '\n'
+        gcode += ';Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
 
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += 'G90\n'
@@ -178,10 +206,10 @@ G0 Z{z_toolchange}
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
 
         return gcode

+ 46 - 18
preprocessors/Toolchange_Custom.py

@@ -27,35 +27,63 @@ class Toolchange_Custom(FlatCAMPostProc):
 
         if str(p['options']['type']) == 'Geometry':
             gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            if p['multidepth'] is True:
+                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
+                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
 
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
 
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
+            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
         else:
             gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
 
@@ -145,10 +173,10 @@ M6
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 51 - 20
preprocessors/Toolchange_manual.py → preprocessors/Toolchange_Manual.py

@@ -9,7 +9,7 @@
 from FlatCAMPostProc import *
 
 
-class Toolchange_manual(FlatCAMPostProc):
+class Toolchange_Manual(FlatCAMPostProc):
 
     include_header = True
     coordinate_format = "%.*f"
@@ -27,27 +27,57 @@ class Toolchange_manual(FlatCAMPostProc):
 
         if str(p['options']['type']) == 'Geometry':
             gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            if p['multidepth'] is True:
+                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
+                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
 
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
+
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@@ -119,6 +149,7 @@ M0
 G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
+(MSG, Drilling with Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 """.format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
            y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
@@ -139,6 +170,7 @@ M0
 G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
+(MSG, Milling with Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 """.format(
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
@@ -203,12 +235,11 @@ M0
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
-        if coords_xy is not None:
+
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
-        else:
-            gcode += 'G00 X0 Y0' + "\n"
         return gcode
 
     def feedrate_code(self, p):

+ 48 - 22
preprocessors/Toolchange_Probe_MACH3.py

@@ -18,7 +18,7 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         coords_xy = p['xy_toolchange']
-        gcode = ''
+        gcode = '(This preprocessor is used with MACH3 with probing height.)\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -27,37 +27,63 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
 
         if str(p['options']['type']) == 'Geometry':
             gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
-
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
             gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
-
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Feedrate Probe ' + str(p['feedrate_probe']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
             if p['multidepth'] is True:
                 gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
                          str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
+
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
+
+            if p['multidepth'] is True:
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
+
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
-        gcode += '(Z Probe Depth: ' + str(p['z_pdepth']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
+            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
         else:
             gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
 
@@ -246,10 +272,10 @@ M0
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 55 - 26
preprocessors/default.py

@@ -18,7 +18,8 @@ class default(FlatCAMPostProc):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         coords_xy = p['xy_toolchange']
-        gcode = ''
+        gcode = '(This preprocessor is the default preprocessor used by FlatCAM.)\n'
+        gcode += '(It is made to work with MACH3 compatible motion controllers.)\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -27,28 +28,56 @@ class default(FlatCAMPostProc):
 
         if str(p['options']['type']) == 'Geometry':
             gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
-
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
             gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
-
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
-
-        if str(p['options']['type']) == 'Geometry':
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
             if p['multidepth'] is True:
                 gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
                          str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
+
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
+
+            if p['multidepth'] is True:
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
+
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
@@ -66,7 +95,7 @@ class default(FlatCAMPostProc):
 
         gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n')
         gcode += 'G90\n'
-        gcode += 'G94\n'
+        gcode += 'G94'
 
         return gcode
 
@@ -117,11 +146,11 @@ M6
 M0
 G00 Z{z_toolchange}
 """.format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
-             y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
-             z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
-             tool=int(p.tool),
-             t_drills=no_drills,
-             toolC=toolC_formatted)
+           y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
+           z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
+           tool=int(p.tool),
+           t_drills=no_drills,
+           toolC=toolC_formatted)
             else:
                 gcode = """
 M5       
@@ -188,11 +217,11 @@ G00 Z{z_toolchange}
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        end_coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
-            gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
+        if end_coords_xy and end_coords_xy != '':
+            gcode += 'G00 X{x} Y{y}'.format(x=end_coords_xy[0], y=end_coords_xy[1]) + "\n"
         return gcode
 
     def feedrate_code(self, p):

+ 52 - 21
preprocessors/grbl_11.py

@@ -18,7 +18,8 @@ class grbl_11(FlatCAMPostProc):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         coords_xy = p['xy_toolchange']
-        gcode = ''
+        gcode = '(This preprocessor is used with a motion controller loaded with GRBL firmware.)\n'
+        gcode += '(It is configured to be compatible with almost any version of GRBL firmware.)\n\n'
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
@@ -26,41 +27,71 @@ class grbl_11(FlatCAMPostProc):
         ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n' + '\n'
+            gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            if p['multidepth'] is True:
+                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
+                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
 
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
+
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
+            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
         else:
             gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
 
         gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
         gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
 
-        gcode += '(Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + ')\n' + '\n'
+        gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
 
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += 'G90\n'
@@ -187,10 +218,10 @@ G00 Z{z_toolchange}
                ' F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy and coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 48 - 18
preprocessors/line_xyz.py

@@ -27,33 +27,63 @@ class line_xyz(FlatCAMPostProc):
 
         if str(p['options']['type']) == 'Geometry':
             gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
+            gcode += '(Feedrate_XY: ' + str(p['feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
+            gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            if p['multidepth'] is True:
+                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
+                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+            gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
 
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
+        elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
+            gcode += '\n(TOOLS DIAMETER: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + ')\n'
+            gcode += '\n(FEEDRATE Z: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate: %s' % str(val['data']["feedrate_z"]) + ')\n'
 
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+            gcode += '\n(FEEDRATE RAPIDS: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Feedrate Rapids: %s' % \
+                         str(val['data']["feedrate_rapid"]) + ')\n'
+
+            gcode += '\n(Z_CUT: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Cut: %s' % str(val['data']["cutz"]) + ')\n'
+
+            gcode += '\n(Tools Offset: )\n'
+            for tool, val in p['exc_cnc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(val['tool']) + 'Offset Z: %s' % str(val['offset_z']) + ')\n'
 
-        if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + ')\n'
+                gcode += '\n(DEPTH_PER_CUT: )\n'
+                for tool, val in p['exc_tools'].items():
+                    gcode += '(Tool: %s -> ' % str(tool) + 'DeptPerCut: %s' % \
+                             str(val['data']["depthperpass"]) + ')\n'
+
+            gcode += '\n(Z_MOVE: )\n'
+            for tool, val in p['exc_tools'].items():
+                gcode += '(Tool: %s -> ' % str(tool) + 'Z_Move: %s' % str(val['data']["travelz"]) + ')\n'
+            gcode += '\n'
+
+        if p['toolchange'] is True:
+            gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
+
+            if coords_xy is not None:
+                gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                               p.decimals, coords_xy[1]) + units + ')\n'
+            else:
+                gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
-                                                           p.decimals, coords_xy[1]) + units + ')\n'
-        else:
-            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['z_end']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
+            gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
         else:
             gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
 
@@ -176,8 +206,8 @@ M0""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolcha
         return g
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
-        if coords_xy is not None:
+        coords_xy = p['xy_end']
+        if coords_xy and coords_xy != '':
             g = 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         else:
             g = ('G00 ' + self.position_code(p)).format(**p)

+ 7 - 3
requirements.txt

@@ -1,7 +1,10 @@
 # This file contains python only requirements to be installed with pip
 # Python packages that cannot be installed with pip (e.g. PyQt5, GDAL) are not included.
+# For Windows GDAL wheel can be found here: https://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal
 # Usage: pip3 install -r requirements.txt
-numpy >=1.16
+
+pyqt5>=5.12.1
+numpy>=1.16
 matplotlib>=3.1
 cycler>=0.10
 python-dateutil>=2.1
@@ -12,7 +15,7 @@ dill
 rtree
 pyopengl
 vispy
-ortools
+ortools>=7.0
 svg.path
 simplejson
 shapely>=1.3
@@ -23,4 +26,5 @@ lxml
 ezdxf
 qrcode>=6.1
 reportlab>=3.5
-svglib
+svglib
+gdal

+ 3 - 2
setup_ubuntu.sh

@@ -1,8 +1,9 @@
 #!/bin/bash
 sudo apt install --reinstall libpng-dev libfreetype6 libfreetype6-dev libgeos-dev libspatialindex-dev
 sudo apt install --reinstall python3-dev python3-pyqt5 python3-pyqt5.qtopengl python3-gdal python3-simplejson
-sudo apt install --reinstall python3-pip python3-tk python3-imaging
+sudo apt install --reinstall python3-pip python3-tk
 
-sudo python3 -m pip install --upgrade pip numpy scipy shapely rtree tk lxml cycler python-dateutil kiwisolver dill
+sudo python3 -m pip install --upgrade pyqt5
+sudo python3 -m pip install --upgrade pip numpy shapely rtree tk lxml cycler python-dateutil kiwisolver dill
 sudo python3 -m pip install --upgrade vispy pyopengl setuptools svg.path ortools freetype-py fontTools rasterio ezdxf
 sudo python3 -m pip install --upgrade matplotlib qrcode reportlab svglib

BIN
share/align16.png


BIN
share/align32.png


BIN
share/black32.png


+ 296 - 0
share/dark_resources/Makefile

@@ -0,0 +1,296 @@
+#addarray16.png
+#addarray20.png
+#addarray32.png
+#aero_arc.png
+#aero_array.png
+#aero_buffer.png
+#aero_circle_geo.png
+#aero_circle.png
+#aero_disc.png
+#aero_drill_array.png
+#aero_drill.png
+#aero_path1.png
+#aero_path2.png
+#aero_path3.png
+#aero_path4.png
+#aero_path5.png
+#aero.png
+#aero_semidisc.png
+#aero_slot.png
+#aero_text.png
+#align_center32.png
+#align_justify32.png
+#align_left32.png
+#align_right32.png
+#aperture16.png
+#aperture32.png
+#arc16.png
+#arc24.png
+#arc32.png
+#axis32.png
+#backup24.png
+#backup_export24.png
+#backup_import24.png
+#blocked16.png
+#bold32.png
+#bookmarks16.png
+#bookmarks32.png
+#buffer16-2.png
+#buffer16.png
+#buffer20.png
+#buffer24.png
+#bug16.png
+#bug32.png
+#calculator16.png
+#calculator24.png
+#calibrate_16.png
+#calibrate_32.png
+#cancel_edit16.png
+#cancel_edit32.png
+#circle32.png
+#clear_plot16.png
+#clear_plot32.png
+#close_edit_file16.png
+#close_edit_file32.png
+#cnc16.png
+#cnc32.png
+#code_editor32.png
+#code.png
+#convert24.png
+#copperfill16.png
+#copperfill32.png
+#copy_16.png
+#copy16.png
+#copy32.png
+#copy_file16.png
+#copy_file32.png
+#copy_geo.png
+#copy.png
+#corner32.png
+#cut16_bis.png
+#cut16.png
+#cut32_bis.png
+#cut32.png
+#cutpath16.png
+#cutpath24.png
+#cutpath32.png
+#database32.png
+#defaults.png
+#delete32.png
+#delete_file16.png
+#delete_file32.png
+#deleteshape16.png
+#deleteshape24.png
+#deleteshape32.png
+#deselect_all32.png
+#disable16.png
+#disable32.png
+#disc32.png
+#distance16.png
+#distance32.png
+#distance_min16.png
+#distance_min32.png
+#doubleside16.png
+#doubleside32.png
+#draw32.png
+#drill16.png
+#drill32.png
+#dxf16.png
+#edit16.png
+#edit32.png
+#edit_file16.png
+#edit_file32.png
+#edit_ok16.png
+#edit_ok32_bis.png
+#edit_ok32.png
+#eraser26.png
+#explode32.png
+#export.png
+#export_png32.png
+#fiducials_32.png
+#file16.png
+#file32.png
+#film16.png
+#film32.png
+#flatcam_icon128.png
+#flatcam_icon16.png
+#flatcam_icon24.png
+#flatcam_icon256.png
+#flatcam_icon32.png
+#flatcam_icon48.png
+#flipx.png
+#flipy.png
+#floppy16.png
+#floppy32.png
+#folder16.png
+#folder32_bis.png
+#folder32_Excellon.png
+#folder32_gerber.png
+#folder32.png
+#fscreen32.png
+#gear32.png
+#gear48.png
+#geometry16.png
+#globe16.png
+#goemetry32.png
+#graylight12.png
+#grid16.png
+#grid32_menu.png
+#grid32.png
+#help.png
+#home16.png
+#icons.txt
+#image16.png
+#image32.png
+#import.png
+#info16.png
+#intersection16.png
+#intersection24.png
+#intersection32.png
+#italic32.png
+#join16.png
+#join32.png
+#jump_to16.png
+#jump_to32.png
+#language32.png
+#letter_t_32.png
+#link16.png
+#machine16.png
+#markarea32.png
+#move16.png
+#move32_bis.png
+#move32.png
+#ncc16.png
+#new_exc32.png
+#new_file16.png
+#new_file32.png
+#new_file_exc16.png
+#new_file_exc32.png
+#new_file_geo16.png
+#new_file_geo32.png
+#new_file_grb16.png
+#new_file_grb32.png
+#new_geo16.png
+#new_geo32_bis.png
+#new_geo32.png
+#notebook16.png
+#notebook32.png
+#notes16_1.png
+#notes16.png
+#offset32.png
+#offsetx32.png
+#offsety32.png
+#open_excellon32.png
+#open_script32.png
+#origin16.png
+#origin32.png
+#origin.png
+#padarray32.png
+#paint16.png
+#paint20_1.png
+#paint20.png
+#panel16.png
+#panel32.png
+#panelize16.png
+#panelize32.png
+#path32.png
+#pdf32.png
+#pdf_link16.png
+#plot32.png
+#plus16.png
+#plus32.png
+#pointer32.png
+#pointer.png
+#poligonize32.png
+#polygon32.png
+#power16.png
+#pref.png
+#printer16.png
+#printer32.png
+#project16.png
+#project_save16.png
+#project_save32.png
+#properties32.png
+#qrcode32.png
+#recent_files.png
+#rectangle32.png
+#recycle16.png
+#replot16.png
+#replot32.png
+#resize16.png
+#rotate.png
+#rules32.png
+#save_as.png
+#scale32.png
+#script14.png
+#script16.png
+#script_new16.png
+#script_new24.png
+#script_open16.png
+#script_open18.png
+#script_open24.png
+#select_all.png
+#semidisc32.png
+#shell16.png
+#shell32.png
+#shortcuts24.png
+#skewX.png
+#skewY.png
+#slot26.png
+#slot_array26.png
+#snap_16.png
+#solderpaste32.png
+#solderpastebis32.png
+#source32.png
+#splash.png
+#sub32.png
+#subtract16.png
+#subtract24.png
+#subtract32.png
+#svg16.png
+#svg32.png
+#text32.png
+#toggle_units16.png
+#toggle_units32.png
+#track32.png
+#transform.png
+#trash16.png
+#trash32.png
+#tv16.png
+#underline32.png
+#union16.png
+#union32.png
+#videohelp24.png
+#view64.png
+#workspace24.png
+#zoom_fit32.png
+#zoom_in32.png
+#zoom_out32.png
+
+images = $(shell find -name "*.png")
+
+ls: 
+	@ echo $(images) | tr " " "\n"
+
+.PHONY: all
+
+all: $(images)
+
+%.png:
+	@ echo "\nProcessing $@ $(prefix ../,$@)"
+	@ convert $(addprefix ../,$@) -fuzz 20% -transparent white $@
+	@ convert $@ -channel RGB -negate $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque white $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#f7f7f7" $@
+	# # teste converte cinzas intermediarias
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#000000" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#040404" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#212121" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#262626" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#3b3b3b" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#3e3e3e" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#4c4c4c" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#505050" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#555555" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#575757" $@
+	@ convert $@ -fuzz 10% -fill "#F3F3F3" -opaque "#919191" $@

BIN
share/dark_resources/active_static.png


BIN
share/dark_resources/addarray16.png


BIN
share/dark_resources/addarray20.png


BIN
share/dark_resources/addarray32.png


BIN
share/dark_resources/aero.png


BIN
share/dark_resources/aero_arc.png


BIN
share/dark_resources/aero_array.png


BIN
share/dark_resources/aero_buffer.png


BIN
share/dark_resources/aero_circle.png


BIN
share/dark_resources/aero_circle_geo.png


BIN
share/dark_resources/aero_disc.png


BIN
share/dark_resources/aero_drill.png


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików