Просмотр исходного кода

Merged in test_beta_8.911 (pull request #135)

Test beta 8.911
Marius Stanciu 6 лет назад
Родитель
Сommit
3d25ba2838

Разница между файлами не показана из-за своего большого размера
+ 360 - 134
FlatCAMApp.py


+ 6 - 7
FlatCAMEditor.py

@@ -346,7 +346,7 @@ class TextInputTool(FlatCAMTool):
                     font_name=self.font_name,
                     font_size=font_to_geo_size,
                     font_type=font_to_geo_type,
-                    units=self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper())
+                    units=self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper())
 
     def font_family(self, font):
         self.text_input_entry.selectAll()
@@ -1483,7 +1483,7 @@ class TransformEditorTool(FlatCAMTool):
                 "[WARNING_NOTCL] Geometry shape rotate cancelled...")
 
     def on_offx_key(self):
-        units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower()
+        units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
 
         val_box = FCInputDialog(title="Offset on X axis ...",
                                 text=('Enter a distance Value (%s):' % str(units)),
@@ -1502,7 +1502,7 @@ class TransformEditorTool(FlatCAMTool):
                 "[WARNING_NOTCL] Geometry shape offset X cancelled...")
 
     def on_offy_key(self):
-        units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower()
+        units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
 
         val_box = FCInputDialog(title="Offset on Y axis ...",
                                 text=('Enter a distance Value (%s):' % str(units)),
@@ -3849,7 +3849,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
         geo = self.active_tool.utility_geometry(data=(x, y))
 
         if isinstance(geo, DrawToolShape) and geo.geo is not None:
-
             # Remove any previous utility shape
             self.tool_shape.clear(update=True)
             self.draw_utility_geometry(geo=geo)
@@ -4985,7 +4984,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.move_timer.setSingleShot(True)
 
         ## Current application units in Upper Case
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         self.key = None  # Currently pressed key
         self.modifiers = None
@@ -5059,7 +5058,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
     def set_ui(self):
         # updated units
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         self.olddia_newdia.clear()
         self.tool2tooldia.clear()
@@ -5099,7 +5098,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             pass
 
         # updated units
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         # make a new name for the new Excellon object (the one with edited content)
         self.edited_obj_name = self.exc_obj.options['name']

+ 241 - 33
FlatCAMGUI.py

@@ -83,6 +83,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.menufileopenproject = QtWidgets.QAction(QtGui.QIcon('share/folder16.png'), 'Open &Project ...', self)
         self.menufile_open.addAction(self.menufileopenproject)
 
+        self.menufile_open.addSeparator()
+
+        # Open Config File...
+        self.menufileopenconfig = QtWidgets.QAction(QtGui.QIcon('share/folder16.png'), 'Open Config ...', self)
+        self.menufile_open.addAction(self.menufileopenconfig)
+
         # Recent
         self.recent = self.menufile.addMenu(QtGui.QIcon('share/recent_files.png'), "Recent files")
 
@@ -299,7 +305,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.menuview_zoom_fit = self.menuview.addAction(QtGui.QIcon('share/zoom_fit32.png'), "&Zoom Fit\tV")
         self.menuview_zoom_in = self.menuview.addAction(QtGui.QIcon('share/zoom_in32.png'), "&Zoom In\t-")
         self.menuview_zoom_out = self.menuview.addAction(QtGui.QIcon('share/zoom_out32.png'), "&Zoom Out\t=")
+        self.menuview.addSeparator()
 
+        self.menuview_toggle_code_editor = self.menuview.addAction(QtGui.QIcon('share/code_editor32.png'),
+                                                                   'Toggle Code Editor\tCTRL+E')
         self.menuview.addSeparator()
         self.menuview_toggle_fscreen = self.menuview.addAction(
             QtGui.QIcon('share/fscreen32.png'), "&Toggle FullScreen\tALT+F10")
@@ -457,18 +466,27 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.toolbarfile = QtWidgets.QToolBar('File Toolbar')
         self.toolbarfile.setObjectName('File_TB')
         self.addToolBar(self.toolbarfile)
+
         self.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
         self.toolbargeo.setObjectName('Edit_TB')
         self.addToolBar(self.toolbargeo)
+
         self.toolbarview = QtWidgets.QToolBar('View Toolbar')
         self.toolbarview.setObjectName('View_TB')
         self.addToolBar(self.toolbarview)
+
+        self.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
+        self.toolbarshell.setObjectName('Shell_TB')
+        self.addToolBar(self.toolbarshell)
+
         self.toolbartools = QtWidgets.QToolBar('Tools Toolbar')
         self.toolbartools.setObjectName('Tools_TB')
         self.addToolBar(self.toolbartools)
+
         self.exc_edit_toolbar = QtWidgets.QToolBar('Excellon Editor Toolbar')
         self.exc_edit_toolbar.setObjectName('ExcEditor_TB')
         self.addToolBar(self.exc_edit_toolbar)
+
         self.geo_edit_toolbar = QtWidgets.QToolBar('Geometry Editor Toolbar')
         self.geo_edit_toolbar.setObjectName('GeoEditor_TB')
         self.addToolBar(self.geo_edit_toolbar)
@@ -516,8 +534,23 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         # self.toolbarview.setVisible(False)
 
+        ### Shell Toolbar ###
+        self.shell_btn = self.toolbarshell.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line")
+
         ### Tools Toolbar ###
-        self.shell_btn = self.toolbartools.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line")
+        self.dblsided_btn = self.toolbartools.addAction(QtGui.QIcon('share/doubleside32.png'), "2Sided Tool")
+        self.cutout_btn = self.toolbartools.addAction(QtGui.QIcon('share/cut16.png'), "&Cutout Tool")
+        self.ncc_btn = self.toolbartools.addAction(QtGui.QIcon('share/flatcam_icon32.png'), "NCC Tool")
+        self.paint_btn = self.toolbartools.addAction(QtGui.QIcon('share/paint20_1.png'), "Paint Tool")
+        self.toolbartools.addSeparator()
+
+        self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panel16.png'), "Panel Tool")
+        self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'), "Film Tool")
+        self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'), "SolderPaste Tool")
+        self.toolbartools.addSeparator()
+
+        self.calculators_btn = self.toolbartools.addAction(QtGui.QIcon('share/calculator24.png'), "Calculators Tool")
+        self.transform_btn = self.toolbartools.addAction(QtGui.QIcon('share/transform.png'), "Transform Tool")
 
         ### Drill Editor Toolbar ###
         self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), "Select")
@@ -715,6 +748,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.geometry_tab_lay.addWidget(self.geometry_scroll_area)
 
         self.cncjob_tab = QtWidgets.QWidget()
+        self.cncjob_tab.setObjectName("cncjob_tab")
         self.pref_tab_area.addTab(self.cncjob_tab, "CNC-JOB")
         self.cncjob_tab_lay = QtWidgets.QVBoxLayout()
         self.cncjob_tab_lay.setContentsMargins(2, 2, 2, 2)
@@ -941,6 +975,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 			<td height="20"><strong>SHIFT+C</strong></td>
 			<td>&nbsp;Copy Obj_Name</td>
 		</tr>
+		<tr height="20">
+			<td height="20"><strong>SHIFT+E</strong></td>
+			<td>&nbsp;Toggle Code Editor</td>
+		</tr>
 		<tr height="20">
 			<td height="20"><strong>SHIFT+G</strong></td>
 			<td>&nbsp;Toggle the axis</td>
@@ -1540,8 +1578,23 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         # self.toolbarview.setVisible(False)
 
+        ### Shell Toolbar ###
+        self.shell_btn = self.toolbarshell.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line")
+
         ### Tools Toolbar ###
-        self.shell_btn = self.toolbartools.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line")
+        self.dblsided_btn = self.toolbartools.addAction(QtGui.QIcon('share/doubleside32.png'), "2Sided Tool")
+        self.cutout_btn = self.toolbartools.addAction(QtGui.QIcon('share/cut16.png'), "&Cutout Tool")
+        self.ncc_btn = self.toolbartools.addAction(QtGui.QIcon('share/flatcam_icon32.png'), "NCC Tool")
+        self.paint_btn = self.toolbartools.addAction(QtGui.QIcon('share/paint20_1.png'), "Paint Tool")
+        self.toolbartools.addSeparator()
+
+        self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panel16.png'), "Panel Tool")
+        self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'), "Film Tool")
+        self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'), "SolderPaste Tool")
+        self.toolbartools.addSeparator()
+
+        self.calculators_btn = self.toolbartools.addAction(QtGui.QIcon('share/calculator24.png'), "Calculators Tool")
+        self.transform_btn = self.toolbartools.addAction(QtGui.QIcon('share/transform.png'), "Transform Tool")
 
         ### Drill Editor Toolbar ###
         self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), "Select")
@@ -1692,11 +1745,14 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 return
             elif modifiers == QtCore.Qt.ShiftModifier:
 
-                # Copy Object Name
                 # Copy Object Name
                 if key == QtCore.Qt.Key_C:
                     self.app.on_copy_name()
 
+                # Toggle Code Editor
+                if key == QtCore.Qt.Key_E:
+                    self.app.on_toggle_code_editor()
+
                 # Toggle axis
                 if key == QtCore.Qt.Key_G:
                     if self.app.toggle_axis is False:
@@ -1847,6 +1903,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 # Escape = Deselect All
                 if key == QtCore.Qt.Key_Escape or key == 'Escape':
                     self.app.on_deselect_all()
+                    # try to disconnect the slot from Set Origin
+                    try:
+                        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_set_zero_click)
+                    except:
+                        pass
                     self.app.inform.emit("")
 
                 # Space = Toggle Active/Inactive
@@ -1891,10 +1952,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
                 # Change Units
                 if key == QtCore.Qt.Key_Q:
-                    if self.app.options["units"] == 'MM':
-                        self.app.ui.general_options_form.general_app_group.units_radio.set_value("IN")
+                    if self.app.defaults["units"] == 'MM':
+                        self.app.ui.general_defaults_form.general_app_group.units_radio.set_value("IN")
                     else:
-                        self.app.ui.general_options_form.general_app_group.units_radio.set_value("MM")
+                        self.app.ui.general_defaults_form.general_app_group.units_radio.set_value("MM")
                     self.app.on_toggle_units()
 
                 # Rotate Object by 90 degree CW
@@ -2515,10 +2576,14 @@ class GerberPreferencesUI(QtWidgets.QWidget):
         self.gerber_gen_group = GerberGenPrefGroupUI()
         self.gerber_gen_group.setFixedWidth(250)
         self.gerber_opt_group = GerberOptPrefGroupUI()
-        self.gerber_opt_group.setFixedWidth(250)
+        self.gerber_opt_group.setFixedWidth(230)
+        self.gerber_adv_opt_group = GerberAdvOptPrefGroupUI()
+        self.gerber_adv_opt_group.setFixedWidth(200)
 
         self.layout.addWidget(self.gerber_gen_group)
         self.layout.addWidget(self.gerber_opt_group)
+        self.layout.addWidget(self.gerber_adv_opt_group)
+
         self.layout.addStretch()
 
 
@@ -2639,9 +2704,13 @@ class CNCJobPreferencesUI(QtWidgets.QWidget):
         self.cncjob_gen_group.setFixedWidth(270)
         self.cncjob_opt_group = CNCJobOptPrefGroupUI()
         self.cncjob_opt_group.setFixedWidth(260)
+        self.cncjob_adv_opt_group = CNCJobAdvOptPrefGroupUI()
+        self.cncjob_adv_opt_group.setFixedWidth(260)
 
         self.layout.addWidget(self.cncjob_gen_group)
         self.layout.addWidget(self.cncjob_opt_group)
+        self.layout.addWidget(self.cncjob_adv_opt_group)
+
         self.layout.addStretch()
 
 
@@ -3027,6 +3096,7 @@ class GeneralGUISetGroupUI(OptionsGroupUI):
             del settings
             self.app.inform.emit("[success] GUI settings deleted ...")
 
+
 class GeneralAppPrefGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
         super(GeneralAppPrefGroupUI, self).__init__(self)
@@ -3227,26 +3297,27 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
 
         grid0 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0)
-        # Plot CB
-        self.plot_cb = FCCheckBox(label='Plot')
-        self.plot_options_label.setToolTip(
-            "Plot (show) this object."
-        )
-        grid0.addWidget(self.plot_cb, 0, 0)
 
         # Solid CB
         self.solid_cb = FCCheckBox(label='Solid')
         self.solid_cb.setToolTip(
             "Solid color polygons."
         )
-        grid0.addWidget(self.solid_cb, 0, 1)
+        grid0.addWidget(self.solid_cb, 0, 0)
 
         # Multicolored CB
         self.multicolored_cb = FCCheckBox(label='M-Color')
         self.multicolored_cb.setToolTip(
             "Draw polygons in different colors."
         )
-        grid0.addWidget(self.multicolored_cb, 0, 2)
+        grid0.addWidget(self.multicolored_cb, 0, 1)
+
+        # Plot CB
+        self.plot_cb = FCCheckBox(label='Plot')
+        self.plot_options_label.setToolTip(
+            "Plot (show) this object."
+        )
+        grid0.addWidget(self.plot_cb, 0, 2)
 
         # Number of circle steps for circular aperture linear approximation
         self.circle_steps_label = QtWidgets.QLabel("Circle Steps:")
@@ -3386,6 +3457,73 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
 
 
+class GerberAdvOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
+        super(GerberAdvOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str("Gerber Adv. Options"))
+
+
+        ## Advanced Gerber Parameters
+        self.adv_param_label = QtWidgets.QLabel("<b>Advanced Param.:</b>")
+        self.adv_param_label.setToolTip(
+            "A list of Gerber advanced parameters.\n"
+            "Those parameters are available only for\n"
+            "Advanced App. Level."
+        )
+        self.layout.addWidget(self.adv_param_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Follow Attribute
+        self.follow_cb = FCCheckBox(label='"Follow"')
+        self.follow_cb.setToolTip(
+            "Generate a 'Follow' geometry.\n"
+            "This means that it will cut through\n"
+            "the middle of the trace."
+
+        )
+        grid0.addWidget(self.follow_cb, 0, 0)
+
+        # Aperture Table Visibility CB
+        self.aperture_table_visibility_cb = FCCheckBox(label='Table Show/Hide')
+        self.aperture_table_visibility_cb.setToolTip(
+            "Toggle the display of the Gerber Apertures Table.\n"
+            "Also, on hide, it will delete all mark shapes\n"
+            "that are drawn on canvas."
+
+        )
+        grid0.addWidget(self.aperture_table_visibility_cb, 1, 0)
+
+        # Scale Aperture Factor
+        self.scale_aperture_label = QtWidgets.QLabel('Ap. Scale Factor:')
+        self.scale_aperture_label.setToolTip(
+            "Change the size of the selected apertures.\n"
+            "Factor by which to multiply\n"
+            "geometric features of this object."
+        )
+        grid0.addWidget(self.scale_aperture_label, 2, 0)
+
+        self.scale_aperture_entry = FloatEntry2()
+        grid0.addWidget(self.scale_aperture_entry, 2, 1)
+
+        # Buffer Aperture Factor
+        self.buffer_aperture_label = QtWidgets.QLabel('Ap. Buffer Factor:')
+        self.buffer_aperture_label.setToolTip(
+            "Change the size of the selected apertures.\n"
+            "Factor by which to expand/shrink\n"
+            "geometric features of this object."
+        )
+        grid0.addWidget(self.buffer_aperture_label, 3, 0)
+
+        self.buffer_aperture_entry = FloatEntry2()
+        grid0.addWidget(self.buffer_aperture_entry, 3, 1)
+
+        self.layout.addStretch()
+
+
 class ExcellonGenPrefGroupUI(OptionsGroupUI):
 
     def __init__(self, parent=None):
@@ -4485,6 +4623,89 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
 
 
+class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "CNC Job Advanced Options Preferences", parent=None)
+        super(CNCJobAdvOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str("CNC Job Adv. Options"))
+
+        ## Export G-Code
+        self.export_gcode_label = QtWidgets.QLabel("<b>Export G-Code:</b>")
+        self.export_gcode_label.setToolTip(
+            "Export and save G-Code to\n"
+            "make this object to a file."
+        )
+        self.layout.addWidget(self.export_gcode_label)
+
+        # Prepend to G-Code
+        toolchangelabel = QtWidgets.QLabel('Toolchange G-Code:')
+        toolchangelabel.setToolTip(
+            "Type here any G-Code commands you would\n"
+            "like to be executed when Toolchange event is encountered.\n"
+            "This will constitute a Custom Toolchange GCode,\n"
+            "or a Toolchange Macro."
+        )
+        self.layout.addWidget(toolchangelabel)
+
+        self.toolchange_text = FCTextArea()
+        self.layout.addWidget(self.toolchange_text)
+
+        hlay = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay)
+
+        # Toolchange Replacement GCode
+        self.toolchange_cb = FCCheckBox(label='Use Toolchange Macro')
+        self.toolchange_cb.setToolTip(
+            "Check this box if you want to use\n"
+            "a Custom Toolchange GCode (macro)."
+        )
+        hlay.addWidget(self.toolchange_cb)
+        hlay.addStretch()
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay1)
+
+        # Variable list
+        self.tc_variable_combo = FCComboBox()
+        self.tc_variable_combo.setToolTip(
+            "A list of the FlatCAM variables that can be used\n"
+            "in the Toolchange event.\n"
+            "They have to be surrounded by the '%' symbol"
+        )
+        hlay1.addWidget(self.tc_variable_combo)
+
+        # Populate the Combo Box
+        variables = ['Parameters', 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange',
+                     'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime']
+        self.tc_variable_combo.addItems(variables)
+        self.tc_variable_combo.setItemData(0, "FlatCAM CNC parameters", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(1, "tool = tool number", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(2, "tooldia = tool diameter", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(3, "t_drills = for Excellon, total number of drills", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(4, "x_toolchange = X coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(5, "y_toolchange = Y coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(6, "z_toolchange = Z coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(7, "z_cut = Z coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(8, "z_move = Z coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(9, "z_depthpercut = the step value for multidepth cut", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(10, "spindlesspeed = the value for the spindle speed", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(11, "dwelltime = time to dwell to allow the spindle to reach it's set RPM",
+                                           Qt.ToolTipRole)
+
+        hlay1.addStretch()
+
+        # Insert Variable into the Toolchange G-Code Text Box
+        # self.tc_insert_buton = FCButton("Insert")
+        # self.tc_insert_buton.setToolTip(
+        #     "Insert the variable in the GCode Box\n"
+        #     "surrounded by the '%' symbol."
+        # )
+        # hlay1.addWidget(self.tc_insert_buton)
+
+        self.layout.addStretch()
+
+
 class ToolsNCCPrefGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
         # OptionsGroupUI.__init__(self, "NCC Tool Options", parent=parent)
@@ -4632,22 +4853,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         self.cutout_gap_entry = LengthEntry()
         grid0.addWidget(self.cutout_gap_entry, 2, 1)
 
-        gapslabel = QtWidgets.QLabel('Gaps Rect:')
-        gapslabel.setToolTip(
-            "Where to place the gaps when doing a Rectangular Cutout:\n"
-            " - 2 (T/B) --> Top/Bottom\n"
-            " - 2 (L/R) --> Left/Rigt\n"
-            " - 4       --> on each of all 4 sides."
-        )
-        grid0.addWidget(gapslabel, 3, 0)
-        self.gaps_radio = RadioSet([{'label': '2 (T/B)', 'value': 'tb'},
-                                    {'label': '2 (L/R)', 'value': 'lr'},
-                                    {'label': '4', 'value': '4'}])
-        grid0.addWidget(self.gaps_radio, 3, 1)
-
-        gaps_ff_label = QtWidgets.QLabel('Gaps FF:')
-        gaps_ff_label.setToolTip(
-            "Number of gaps used for the FreeForm cutout.\n"
+        gaps_label = QtWidgets.QLabel('Gaps:')
+        gaps_label.setToolTip(
+            "Number of bridge gaps used for the cutout.\n"
             "There can be maximum 8 bridges/gaps.\n"
             "The choices are:\n"
             "- lr    - left + right\n"
@@ -4657,9 +4865,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
             "- 2tb  - 2*top + 2*bottom\n"
             "- 8     - 2*left + 2*right +2*top + 2*bottom"
         )
-        grid0.addWidget(gaps_ff_label, 4, 0)
+        grid0.addWidget(gaps_label, 3, 0)
         self.gaps_combo = FCComboBox()
-        grid0.addWidget(self.gaps_combo, 4, 1)
+        grid0.addWidget(self.gaps_combo, 3, 1)
 
         gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
         for it in gaps_items:

+ 443 - 100
FlatCAMObj.py

@@ -70,6 +70,8 @@ class FlatCAMObj(QtCore.QObject):
         # self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene)
         self.shapes = self.app.plotcanvas.new_shape_group()
 
+        self.mark_shapes = self.app.plotcanvas.new_shape_collection(layers=2)
+
         self.item = None  # Link with project view item
 
         self.muted_ui = False
@@ -77,6 +79,9 @@ class FlatCAMObj(QtCore.QObject):
 
         self._drawing_tolerance = 0.01
 
+        self.isHovering = False
+        self.notHovering = True
+
         # assert isinstance(self.ui, ObjectUI)
         # self.ui.name_entry.returnPressed.connect(self.on_name_activate)
         # self.ui.offset_button.clicked.connect(self.on_offset_button_click)
@@ -311,6 +316,13 @@ class FlatCAMObj(QtCore.QObject):
             key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs)
         return key
 
+    def add_mark_shape(self, **kwargs):
+        if self.deleted:
+            raise ObjectDeleted()
+        else:
+            key = self.mark_shapes.add(tolerance=self.drawing_tolerance, **kwargs)
+        return key
+
     @property
     def visible(self):
         return self.shapes.visible
@@ -356,6 +368,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
     Represents Gerber code.
     """
     optionChanged = QtCore.pyqtSignal(str)
+    replotApertures = QtCore.pyqtSignal()
+
     ui_type = GerberObjectUI
 
     @staticmethod
@@ -373,26 +387,39 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             grb_final.solid_geometry = []
             grb_final.follow_geometry = []
 
+        if not grb_final.apertures:
+            grb_final.apertures = {}
+
         if type(grb_final.solid_geometry) is not list:
             grb_final.solid_geometry = [grb_final.solid_geometry]
             grb_final.follow_geometry = [grb_final.follow_geometry]
 
         for grb in grb_list:
-            for option in grb.options:
-                if option is not 'name':
-                    try:
-                        grb_final.options[option] = grb.options[option]
-                    except:
-                        log.warning("Failed to copy option.", option)
 
             # Expand lists
             if type(grb) is list:
                 FlatCAMGerber.merge(grb, grb_final)
             else:   # If not list, just append
+                for option in grb.options:
+                    if option is not 'name':
+                        try:
+                            grb_final.options[option] = grb.options[option]
+                        except:
+                            log.warning("Failed to copy option.", option)
+
                 for geos in grb.solid_geometry:
                     grb_final.solid_geometry.append(geos)
                     grb_final.follow_geometry.append(geos)
 
+                for ap in grb.apertures:
+                    if ap not in grb_final.apertures:
+                        grb_final.apertures[ap] = grb.apertures[ap]
+                    else:
+                        if 'solid_geometry' not in grb_final.apertures[ap]:
+                            grb_final.apertures[ap]['solid_geometry'] = []
+                        for geo in grb.apertures[ap]['solid_geometry']:
+                            grb_final.apertures[ap]['solid_geometry'].append(geo)
+
         grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
         grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
 
@@ -416,7 +443,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             "noncoppermargin": 0.0,
             "noncopperrounded": False,
             "bboxmargin": 0.0,
-            "bboxrounded": False
+            "bboxrounded": False,
+            "aperture_display": False,
+            "aperture_scale_factor": 1.0,
+            "aperture_buffer_factor": 0.0,
+            "follow": False
         })
 
         # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
@@ -431,14 +462,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         # store the source file here
         self.source_file = ""
 
-        # assert isinstance(self.ui, GerberObjectUI)
-        # self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
-        # self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
-        # self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
-        # self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
-        # self.ui.generate_cutout_button.clicked.connect(self.on_generatecutout_button_click)
-        # self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
-        # self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
+        # list of rows with apertures plotted
+        self.marked_rows = []
 
         # Attributes to be included in serialization
         # Always append to it because it carries contents
@@ -455,9 +480,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return: None
         """
         FlatCAMObj.set_ui(self, ui)
-
         FlatCAMApp.App.log.debug("FlatCAMGerber.set_ui()")
 
+        self.replotApertures.connect(self.on_mark_cb_click_table)
+
         self.form_fields.update({
             "plot": self.ui.plot_cb,
             "multicolored": self.ui.multicolored_cb,
@@ -470,7 +496,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             "noncoppermargin": self.ui.noncopper_margin_entry,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "bboxmargin": self.ui.bbmargin_entry,
-            "bboxrounded": self.ui.bbrounded_cb
+            "bboxrounded": self.ui.bbrounded_cb,
+            "aperture_display": self.ui.aperture_table_visibility_cb,
+            "aperture_scale_factor": self.ui.scale_aperture_entry,
+            "aperture_buffer_factor": self.ui.buffer_aperture_entry,
+            "follow": self.ui.follow_cb
         })
 
         # Fill form fields only on object create
@@ -489,6 +519,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
         self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
         self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
+        self.ui.scale_aperture_button.clicked.connect(self.on_scale_aperture_click)
+        self.ui.buffer_aperture_button.clicked.connect(self.on_buffer_aperture_click)
+        self.ui.new_grb_button.clicked.connect(self.on_new_modified_gerber)
 
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
@@ -499,10 +532,14 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.ui.milling_type_radio.hide()
             self.ui.generate_ext_iso_button.hide()
             self.ui.generate_int_iso_button.hide()
+            self.ui.follow_cb.hide()
 
         else:
             self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
 
+        # set initial state of the aperture table and associated widgets
+        self.on_aperture_table_visibility_change()
+
         self.build_ui()
 
     def build_ui(self):
@@ -514,9 +551,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         except:
             pass
 
-        n = len(self.apertures) + len(self.aperture_macros)
-        self.ui.apertures_table.setRowCount(n)
-
         self.apertures_row = 0
         aper_no = self.apertures_row + 1
         sort = []
@@ -529,6 +563,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             sort.append(k)
         sorted_macros = sorted(sort)
 
+        n = len(sorted_apertures) + len(sorted_macros)
+        self.ui.apertures_table.setRowCount(n)
+
         for ap_code in sorted_apertures:
             ap_code = str(ap_code)
 
@@ -570,17 +607,17 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                 ap_size_item = QtWidgets.QTableWidgetItem('')
             ap_size_item.setFlags(QtCore.Qt.ItemIsEnabled)
 
-            plot_item = FCCheckBox()
-            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
-            if self.ui.plot_cb.isChecked():
-                plot_item.setChecked(True)
+            mark_item = FCCheckBox()
+            mark_item.setLayoutDirection(QtCore.Qt.RightToLeft)
+            # if self.ui.aperture_table_visibility_cb.isChecked():
+            #     mark_item.setChecked(True)
 
             self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item)  # Aperture Code
             self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item)  # Aperture Type
             self.ui.apertures_table.setItem(self.apertures_row, 3, ap_size_item)   # Aperture Dimensions
             self.ui.apertures_table.setItem(self.apertures_row, 4, ap_dim_item)   # Aperture Dimensions
 
-            self.ui.apertures_table.setCellWidget(self.apertures_row, 5, plot_item)
+            self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item)
 
             self.apertures_row += 1
 
@@ -596,19 +633,18 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             ap_type_item = QtWidgets.QTableWidgetItem('AM')
             ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
 
-            plot_item = FCCheckBox()
-            plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
-            if self.ui.plot_cb.isChecked():
-                plot_item.setChecked(True)
+            mark_item = FCCheckBox()
+            mark_item.setLayoutDirection(QtCore.Qt.RightToLeft)
+            # if self.ui.aperture_table_visibility_cb.isChecked():
+            #     mark_item.setChecked(True)
 
             self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item)  # Aperture Code
             self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item)  # Aperture Type
-            self.ui.apertures_table.setCellWidget(self.apertures_row, 5, plot_item)
+            self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item)
 
             self.apertures_row += 1
 
         self.ui.apertures_table.selectColumn(0)
-        #
         self.ui.apertures_table.resizeColumnsToContents()
         self.ui.apertures_table.resizeRowsToContents()
 
@@ -633,8 +669,32 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         self.ui.apertures_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
         self.ui.apertures_table.setSortingEnabled(False)
         self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight())
+        self.ui.apertures_table.setMaximumHeight(self.ui.apertures_table.getHeight())
+
+        # update the 'mark' checkboxes state according with what is stored in the self.marked_rows list
+        if self.marked_rows:
+            for row in range(self.ui.apertures_table.rowCount()):
+                self.ui.apertures_table.cellWidget(row, 5).set_value(self.marked_rows[row])
+
+        self.ui_connect()
+
+    def ui_connect(self):
+        for row in range(self.ui.apertures_table.rowCount()):
+            self.ui.apertures_table.cellWidget(row, 5).clicked.connect(self.on_mark_cb_click_table)
+
+        self.ui.mark_all_cb.clicked.connect(self.on_mark_all_click)
+
+    def ui_disconnect(self):
+        for row in range(self.ui.apertures_table.rowCount()):
+            try:
+                self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect()
+            except:
+                pass
 
-        # self.ui_connect()
+        try:
+            self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click)
+        except:
+            pass
 
     def on_generatenoncopper_button_click(self, *args):
         self.app.report_usage("gerber_on_generatenoncopper_button")
@@ -895,6 +955,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         if self.muted_ui:
             return
         self.read_form_item('plot')
+        self.plot()
 
     def on_solid_cb_click(self, *args):
         if self.muted_ui:
@@ -916,8 +977,156 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
     def on_aperture_table_visibility_change(self):
         if self.ui.aperture_table_visibility_cb.isChecked():
             self.ui.apertures_table.setVisible(True)
+            self.ui.scale_aperture_label.setVisible(True)
+            self.ui.scale_aperture_entry.setVisible(True)
+            self.ui.scale_aperture_button.setVisible(True)
+
+            self.ui.buffer_aperture_label.setVisible(True)
+            self.ui.buffer_aperture_entry.setVisible(True)
+            self.ui.buffer_aperture_button.setVisible(True)
+
+            self.ui.new_grb_label.setVisible(True)
+            self.ui.new_grb_button.setVisible(True)
+            self.ui.mark_all_cb.setVisible(True)
+            self.ui.mark_all_cb.setChecked(False)
         else:
             self.ui.apertures_table.setVisible(False)
+            self.ui.scale_aperture_label.setVisible(False)
+            self.ui.scale_aperture_entry.setVisible(False)
+            self.ui.scale_aperture_button.setVisible(False)
+
+            self.ui.buffer_aperture_label.setVisible(False)
+            self.ui.buffer_aperture_entry.setVisible(False)
+            self.ui.buffer_aperture_button.setVisible(False)
+
+            self.ui.new_grb_label.setVisible(False)
+            self.ui.new_grb_button.setVisible(False)
+            self.ui.mark_all_cb.setVisible(False)
+
+            # on hide disable all mark plots
+            for row in range(self.ui.apertures_table.rowCount()):
+                self.ui.apertures_table.cellWidget(row, 5).set_value(False)
+            self.clear_plot_apertures()
+
+    def on_scale_aperture_click(self, signal):
+        try:
+            factor = self.ui.scale_aperture_entry.get_value()
+        except Exception as e:
+            log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
+            self.app.inform.emit("[ERROR_NOTCL] The aperture scale factor value is missing or wrong format.")
+            return
+
+        def scale_recursion(geom):
+            if type(geom) == list or type(geom) is MultiPolygon:
+                geoms=list()
+                for local_geom in geom:
+                    geoms.append(scale_recursion(local_geom))
+                return geoms
+            else:
+                return  affinity.scale(geom, factor, factor, origin='center')
+
+        if not self.ui.apertures_table.selectedItems():
+            self.app.inform.emit("[WARNING_NOTCL] No aperture to scale. Select at least one aperture and try again.")
+            return
+
+        for x in self.ui.apertures_table.selectedItems():
+            try:
+                apid = self.ui.apertures_table.item(x.row(), 1).text()
+            except Exception as e:
+                log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
+
+            self.apertures[apid]['solid_geometry'] = scale_recursion(self.apertures[apid]['solid_geometry'])
+
+        self.on_mark_cb_click_table()
+
+    def on_buffer_aperture_click(self, signal):
+        try:
+            buff_value = self.ui.buffer_aperture_entry.get_value()
+        except Exception as e:
+            log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
+            self.app.inform.emit("[ERROR_NOTCL] The aperture buffer value is missing or wrong format.")
+            return
+
+        def buffer_recursion(geom):
+            if type(geom) == list or type(geom) is MultiPolygon:
+                geoms=list()
+                for local_geom in geom:
+                    geoms.append(buffer_recursion(local_geom))
+                return geoms
+            else:
+                return  geom.buffer(buff_value, join_style=2)
+
+        if not self.ui.apertures_table.selectedItems():
+            self.app.inform.emit("[WARNING_NOTCL] No aperture to scale. Select at least one aperture and try again.")
+            return
+
+        for x in self.ui.apertures_table.selectedItems():
+            try:
+                apid = self.ui.apertures_table.item(x.row(), 1).text()
+            except Exception as e:
+                log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
+
+            self.apertures[apid]['solid_geometry'] = buffer_recursion(self.apertures[apid]['solid_geometry'])
+
+        self.on_mark_cb_click_table()
+
+    def on_new_modified_gerber(self, signal):
+
+        name = '%s_ap_mod' % str(self.options['name'])
+        apertures = deepcopy(self.apertures)
+        options = self.options
+
+        # geometry storage
+        poly_buff = []
+
+        # How the object should be initialized
+        def obj_init(gerber_obj, app_obj):
+            assert isinstance(gerber_obj, FlatCAMGerber), \
+                "Expected to initialize a FlatCAMGerber but got %s" % type(gerber_obj)
+
+            gerber_obj.source_file = self.source_file
+            gerber_obj.multigeo = False
+            gerber_obj.follow = False
+
+            gerber_obj.apertures = apertures
+            for option in options:
+                # we don't want to overwrite the new name and we don't want to share the 'plot' state
+                # because the new object should ve visible even if the source is not visible
+                if option != 'name' and option != 'plot':
+                    gerber_obj.options[option] = options[option]
+
+            # regenerate solid_geometry
+            app_obj.log.debug("Creating new Gerber object. Joining %s polygons.")
+            # for ap in apertures:
+                # for geo in apertures[ap]['solid_geometry']:
+                #     poly_buff.append(geo)
+            poly_buff = [geo for ap in apertures for geo in apertures[ap]['solid_geometry']]
+
+            # buffering the poly_buff
+            new_geo = MultiPolygon(poly_buff)
+            new_geo = new_geo.buffer(0.0000001)
+            new_geo = new_geo.buffer(-0.0000001)
+
+            gerber_obj.solid_geometry = new_geo
+
+            app_obj.log.debug("Finished creation of a new Gerber object. Polygons joined.")
+
+        log.debug("on_new_modified_gerber()")
+
+        with self.app.proc_container.new("Generating Gerber") as proc:
+
+            self.app.progress.emit(10)
+
+            ### Object creation ###
+            ret = self.app.new_object("gerber", name, obj_init, autoselected=False)
+            if ret == 'fail':
+                self.app.inform.emit('[ERROR_NOTCL] Cretion of Gerber failed.')
+                return
+
+            self.app.progress.emit(100)
+
+            # GUI feedback
+            self.app.inform.emit("[success] Created: " + name)
 
     def convert_units(self, units):
         """
@@ -1005,70 +1214,149 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.shapes.clear(update=True)
 
     # experimental plot() when the solid_geometry is stored in the self.apertures
-    # def plot_apertures(self, **kwargs):
-    #     """
-    #
-    #     :param kwargs: color and face_color
-    #     :return:
-    #     """
-    #
-    #     FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot()")
-    #
-    #     # Does all the required setup and returns False
-    #     # if the 'ptint' option is set to False.
-    #     if not FlatCAMObj.plot(self):
-    #         return
-    #
-    #     if 'color' in kwargs:
-    #         color = kwargs['color']
-    #     else:
-    #         color = self.app.defaults['global_plot_line']
-    #     if 'face_color' in kwargs:
-    #         face_color = kwargs['face_color']
-    #     else:
-    #         face_color = self.app.defaults['global_plot_fill']
-    #
-    #     geometry = {}
-    #     for ap in self.apertures:
-    #         geometry[ap] = self.apertures[ap]['solid_geometry']
-    #         try:
-    #             _ = iter(geometry[ap])
-    #         except TypeError:
-    #             geometry[ap] = [geometry[ap]]
-    #
-    #     def random_color():
-    #         color = np.random.rand(4)
-    #         color[3] = 1
-    #         return color
-    #
-    #     try:
-    #         if self.options["solid"]:
-    #             for geo in geometry:
-    #                 for g in geometry[geo]:
-    #                     if type(g) == Polygon or type(g) == LineString:
-    #                         self.add_shape(shape=g, color=color,
-    #                                        face_color=random_color() if self.options['multicolored']
-    #                                        else face_color, visible=self.options['plot'])
-    #                     else:
-    #                         for el in g:
-    #                             self.add_shape(shape=el, color=color,
-    #                                            face_color=random_color() if self.options['multicolored']
-    #                                            else face_color, visible=self.options['plot'])
-    #         else:
-    #             for geo in geometry:
-    #                 for g in geometry[geo]:
-    #                     if type(g) == Polygon or type(g) == LineString:
-    #                         self.add_shape(shape=g,
-    #                                        color=random_color() if self.options['multicolored'] else 'black',
-    #                                        visible=self.options['plot'])
-    #                     else:
-    #                         for el in g:
-    #                             self.add_shape(shape=el,
-    #                                            color=random_color() if self.options['multicolored'] else 'black',
-    #                                            visible=self.options['plot'])
-    #         self.shapes.redraw()
-    #     except (ObjectDeleted, AttributeError):
-    #         self.shapes.clear(update=True)
+    def plot_apertures(self, **kwargs):
+        """
+
+        :param kwargs: color and face_color
+        :return:
+        """
+
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot_apertures()")
+
+        # Does all the required setup and returns False
+        # if the 'ptint' option is set to False.
+        if not FlatCAMObj.plot(self):
+            return
+
+        # for marking apertures, line color and fill color are the same
+        if 'color' in kwargs:
+            color = kwargs['color']
+        else:
+            color = self.app.defaults['global_plot_fill']
+
+        if 'marked_aperture' not in kwargs:
+            return
+        else:
+            aperture_to_plot_mark = kwargs['marked_aperture']
+            if aperture_to_plot_mark is None:
+                return
+
+        if 'visible' not in kwargs:
+            visibility = True
+        else:
+            visibility = kwargs['visible']
+
+        with self.app.proc_container.new("Plotting Apertures") as proc:
+            self.app.progress.emit(30)
+
+            def job_thread(app_obj):
+                self.app.progress.emit(30)
+
+                try:
+                    if aperture_to_plot_mark in self.apertures:
+                        if type(self.apertures[aperture_to_plot_mark]['solid_geometry']) is not list:
+                            self.apertures[aperture_to_plot_mark]['solid_geometry'] = \
+                                [self.apertures[aperture_to_plot_mark]['solid_geometry']]
+                        for geo in self.apertures[aperture_to_plot_mark]['solid_geometry']:
+                            if type(geo) == Polygon or type(geo) == LineString:
+                                self.add_mark_shape(shape=geo, color=color, face_color=color, visible=visibility)
+                            else:
+                                for el in geo:
+                                    self.add_mark_shape(shape=el, color=color, face_color=color, visible=visibility)
+
+                    self.mark_shapes.redraw()
+                    self.app.progress.emit(100)
+
+                except (ObjectDeleted, AttributeError):
+                    self.clear_plot_apertures()
+
+            self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
+
+    def clear_plot_apertures(self):
+        self.mark_shapes.clear(update=True)
+
+    def clear_mark_all(self):
+        self.ui.mark_all_cb.set_value(False)
+        self.marked_rows[:] = []
+
+    def on_mark_cb_click_table(self):
+        self.ui_disconnect()
+        # cw = self.sender()
+        # cw_index = self.ui.apertures_table.indexAt(cw.pos())
+        # cw_row = cw_index.row()
+        check_row = 0
+
+        self.clear_plot_apertures()
+        self.marked_rows[:] = []
+
+        for row in range(self.ui.apertures_table.rowCount()):
+            if self.ui.apertures_table.cellWidget(row, 5).isChecked():
+                self.marked_rows.append(True)
+
+                aperture = self.ui.apertures_table.item(row, 1).text()
+                # self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
+                self.plot_apertures(color='#FD6A02', marked_aperture=aperture, visible=True)
+            else:
+                self.marked_rows.append(False)
+
+        self.mark_shapes.redraw()
+
+        # make sure that the Mark All is disabled if one of the row mark's are disabled and
+        # if all the row mark's are enabled also enable the Mark All checkbox
+        cb_cnt = 0
+        total_row = self.ui.apertures_table.rowCount()
+        for row in range(total_row):
+            if self.ui.apertures_table.cellWidget(row, 5).isChecked():
+                cb_cnt += 1
+            else:
+                cb_cnt -= 1
+        if cb_cnt < total_row:
+            self.ui.mark_all_cb.setChecked(False)
+        else:
+            self.ui.mark_all_cb.setChecked(True)
+        self.ui_connect()
+
+    def on_mark_all_click(self, signal):
+        self.ui_disconnect()
+        mark_all = self.ui.mark_all_cb.isChecked()
+        for row in range(self.ui.apertures_table.rowCount()):
+            # update the mark_rows list
+            if mark_all:
+                self.marked_rows.append(True)
+            else:
+                self.marked_rows[:] = []
+
+            mark_cb = self.ui.apertures_table.cellWidget(row, 5)
+            mark_cb.setChecked(mark_all)
+
+        if mark_all:
+            for aperture in self.apertures:
+                # self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
+                self.plot_apertures(color='#FD6A02', marked_aperture=aperture, visible=True)
+        else:
+            self.clear_plot_apertures()
+
+        self.ui_connect()
+
+    def mirror(self, axis, point):
+        Gerber.mirror(self, axis=axis, point=point)
+        self.replotApertures.emit()
+
+    def offset(self, vect):
+        Gerber.offset(self, vect=vect)
+        self.replotApertures.emit()
+
+    def rotate(self, angle, point):
+        Gerber.rotate(self, angle=angle, point=point)
+        self.replotApertures.emit()
+
+    def scale(self, xfactor, yfactor=None, point=None):
+        Gerber.scale(self, xfactor=xfactor, yfactor=yfactor, point=point)
+        self.replotApertures.emit()
+
+    def skew(self, angle_x, angle_y, point):
+        Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
+        self.replotApertures.emit()
 
     def serialize(self):
         return {
@@ -2288,7 +2576,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             if self.options["solid"]:
                 for tool in self.tools:
                     for geo in self.tools[tool]['solid_geometry']:
-                        self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF', visible=self.options['plot'],
+                        self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF',
+                                       visible=self.options['plot'],
                                        layer=2)
             else:
                 for tool in self.tools:
@@ -3533,7 +3822,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.geo_tools_table.setCurrentItem(self.ui.geo_tools_table.item(row, 0))
 
     def export_dxf(self):
-        units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
         dwg = None
         try:
             dwg = ezdxf.new('R2010')
@@ -4382,6 +4671,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry'])
         else:
             self.solid_geometry=scale_recursion(self.solid_geometry)
+
         self.app.inform.emit("[success]Geometry Scale done.")
 
     def offset(self, vect):
@@ -4651,7 +4941,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             "prepend": "",
             "dwell": False,
             "dwelltime": 1,
-            "type": 'Geometry'
+            "type": 'Geometry',
+            "toolchange_macro": '',
+            "toolchange_macro_enable": False
         })
 
         '''
@@ -4848,11 +5140,17 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         assert isinstance(self.ui, CNCObjectUI), \
             "Expected a CNCObjectUI, got %s" % type(self.ui)
 
+        # this signal has to be connected to it's slot before the defaults are populated
+        # the decision done in the slot has to override the default value set bellow
+        self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked)
+
         self.form_fields.update({
             "plot": self.ui.plot_cb,
             # "tooldia": self.ui.tooldia_entry,
             "append": self.ui.append_text,
             "prepend": self.ui.prepend_text,
+            "toolchange_macro": self.ui.toolchange_text,
+            "toolchange_macro_enable": self.ui.toolchange_cb
         })
 
         # Fill form fields only on object create
@@ -4877,21 +5175,30 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         if self.app.defaults["global_app_level"] == 'b':
             self.ui.level.setText('<span style="color:green;"><b>Basic</b></span>')
 
+            self.ui.cnc_frame.hide()
         else:
             self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
+            self.ui.cnc_frame.show()
 
         self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
         self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
         self.ui.modify_gcode_button.clicked.connect(self.on_modifygcode_button_click)
 
+        self.ui.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters)
+
         self.ui.cncplot_method_combo.activated_custom.connect(self.on_plot_kind_change)
 
+    def on_cnc_custom_parameters(self, signal_text):
+        if signal_text == 'Parameters':
+            return
+        else:
+            self.ui.toolchange_text.insertPlainText('%%%s%%' % signal_text)
+
     def ui_connect(self):
         for row in range(self.ui.cnc_tools_table.rowCount()):
             self.ui.cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
 
-
     def ui_disconnect(self):
         for row in range(self.ui.cnc_tools_table.rowCount()):
             self.ui.cnc_tools_table.cellWidget(row, 6).clicked.disconnect(self.on_plot_cb_click_table)
@@ -5146,6 +5453,19 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
             g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble
 
+        # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box
+        if self.ui.toolchange_cb.get_value() is True:
+            # match = self.re_toolchange.search(g)
+            if 'M6' in g:
+                m6_code = self.parse_custom_toolchange_code(self.ui.toolchange_text.get_value())
+                if m6_code is None or m6_code == '':
+                    self.app.inform.emit("[ERROR_NOTCL] Cancelled. The Toolchange Custom code is enabled "
+                                         "but it's empty.")
+                    return 'fail'
+
+                g = g.replace('M6', m6_code)
+                self.app.inform.emit("[success] Toolchange G-code was replaced by a custom code.")
+
         # lines = StringIO(self.gcode)
         lines = StringIO(g)
 
@@ -5168,6 +5488,29 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         else:
             return lines
 
+    def on_toolchange_custom_clicked(self, signal):
+        try:
+            if 'toolchange_custom' not in str(self.options['ppname_e']).lower():
+                print(self.options['ppname_e'])
+                if self.ui.toolchange_cb.get_value():
+                    self.ui.toolchange_cb.set_value(False)
+                    self.app.inform.emit(
+                        "[WARNING_NOTCL] The used postprocessor file has to have in it's name: 'toolchange_custom'")
+        except KeyError:
+            try:
+                for key in self.cnc_tools:
+                    ppg = self.cnc_tools[key]['data']['ppname_g']
+                    if 'toolchange_custom' not in str(ppg).lower():
+                        print(ppg)
+                        if self.ui.toolchange_cb.get_value():
+                            self.ui.toolchange_cb.set_value(False)
+                            self.app.inform.emit(
+                                "[WARNING_NOTCL] The used postprocessor file has to have in it's name: "
+                                "'toolchange_custom'")
+            except KeyError:
+                self.app.inform.emit(
+                    "[ERROR] There is no postprocessor file.")
+
     def get_gcode(self, preamble='', postamble=''):
         #we need this to be able get_gcode separatelly for shell command export_gcode
         return preamble + '\n' + self.gcode + "\n" + postamble

+ 1 - 1
FlatCAMPostProc.py

@@ -109,7 +109,7 @@ class FlatCAMPostProc_Tools(object, metaclass=ABCPostProcRegister):
         pass
 
     @abstractmethod
-    def feedrate_z_code(self, p):
+    def z_feedrate_code(self, p):
         pass
 
     @abstractmethod

+ 6 - 3
GUIElements.py

@@ -229,7 +229,7 @@ class FloatEntry(QtWidgets.QLineEdit):
 
     def set_value(self, val):
         if val is not None:
-            self.setText("%.6f" % val)
+            self.setText("%.4f" % val)
         else:
             self.setText("")
 
@@ -267,7 +267,7 @@ class FloatEntry2(QtWidgets.QLineEdit):
         return float(evaled)
 
     def set_value(self, val):
-        self.setText("%.6f" % val)
+        self.setText("%.4f" % val)
 
     def sizeHint(self):
         default_hint_size = super(FloatEntry2, self).sizeHint()
@@ -337,7 +337,10 @@ class FCEntry(QtWidgets.QLineEdit):
         return str(self.text())
 
     def set_value(self, val):
-        self.setText(str(val))
+        if type(val) is float:
+            self.setText('%.4f' % val)
+        else:
+            self.setText(str(val))
 
     def sizeHint(self):
         default_hint_size = super(FCEntry, self).sizeHint()

+ 0 - 233
ObjectCollection.py

@@ -263,239 +263,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
     def has_promises(self):
         return len(self.promises) > 0
 
-    # def on_key(self, key):
-    #     modifiers = QtWidgets.QApplication.keyboardModifiers()
-    #     active = self.get_active()
-    #     selected = self.get_selected()
-    #
-    #     if modifiers == QtCore.Qt.ControlModifier:
-    #         if key == QtCore.Qt.Key_A:
-    #             self.app.on_selectall()
-    #
-    #         if key == QtCore.Qt.Key_C:
-    #             self.app.on_copy_object()
-    #
-    #         if key == QtCore.Qt.Key_E:
-    #             self.app.on_fileopenexcellon()
-    #
-    #         if key == QtCore.Qt.Key_G:
-    #             self.app.on_fileopengerber()
-    #
-    #         if key == QtCore.Qt.Key_N:
-    #             self.app.on_file_new_click()
-    #
-    #         if key == QtCore.Qt.Key_M:
-    #             self.app.measurement_tool.run()
-    #         if key == QtCore.Qt.Key_O:
-    #             self.app.on_file_openproject()
-    #
-    #         if key == QtCore.Qt.Key_S:
-    #             self.app.on_file_saveproject()
-    #
-    #         # Toggle Plot Area
-    #         if key == QtCore.Qt.Key_F10:
-    #             self.app.on_toggle_plotarea()
-    #
-    #         return
-    #     elif modifiers == QtCore.Qt.ShiftModifier:
-    #
-    #         # Copy Object Name
-    #         # Copy Object Name
-    #         if key == QtCore.Qt.Key_C:
-    #             self.app.on_copy_name()
-    #
-    #         # Toggle axis
-    #         if key == QtCore.Qt.Key_G:
-    #             if self.toggle_axis is False:
-    #                 self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
-    #                 self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
-    #                 self.app.plotcanvas.redraw()
-    #                 self.app.toggle_axis = True
-    #             else:
-    #                 self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
-    #
-    #                 self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
-    #                 self.appplotcanvas.redraw()
-    #                 self.app.toggle_axis = False
-    #
-    #         # Open Preferences Window
-    #         if key == QtCore.Qt.Key_P:
-    #             self.app.on_preferences()
-    #             return
-    #
-    #         # Rotate Object by 90 degree CCW
-    #         if key == QtCore.Qt.Key_R:
-    #             self.app.on_rotate(silent=True, preset=-90)
-    #             return
-    #
-    #         # Run a Script
-    #         if key == QtCore.Qt.Key_S:
-    #             self.app.on_filerunscript()
-    #             return
-    #
-    #         # Toggle Workspace
-    #         if key == QtCore.Qt.Key_W:
-    #             self.app.on_workspace_menu()
-    #             return
-    #
-    #         # Skew on X axis
-    #         if key == QtCore.Qt.Key_X:
-    #             self.app.on_skewx()
-    #             return
-    #
-    #         # Skew on Y axis
-    #         if key == QtCore.Qt.Key_Y:
-    #             self.app.on_skewy()
-    #             return
-    #
-    #     elif modifiers == QtCore.Qt.AltModifier:
-    #         # Eanble all plots
-    #         if key == Qt.Key_1:
-    #             self.app.enable_all_plots()
-    #
-    #         # Disable all plots
-    #         if key == Qt.Key_2:
-    #             self.app.disable_all_plots()
-    #
-    #         # Disable all other plots
-    #         if key == Qt.Key_3:
-    #             self.app.disable_other_plots()
-    #
-    #         # 2-Sided PCB Tool
-    #         if key == QtCore.Qt.Key_D:
-    #             self.app.dblsidedtool.run()
-    #             return
-    #
-    #         # Non-Copper Clear Tool
-    #         if key == QtCore.Qt.Key_N:
-    #             self.app.ncclear_tool.run()
-    #             return
-    #
-    #         # Transformation Tool
-    #         if key == QtCore.Qt.Key_R:
-    #             self.app.transform_tool.run()
-    #             return
-    #
-    #         # Cutout Tool
-    #         if key == QtCore.Qt.Key_U:
-    #             self.app.cutout_tool.run()
-    #             return
-    #
-    #     else:
-    #         # Open Manual
-    #         if key == QtCore.Qt.Key_F1:
-    #             webbrowser.open(self.app.manual_url)
-    #
-    #         # Open Video Help
-    #         if key == QtCore.Qt.Key_F2:
-    #             webbrowser.open(self.app.video_url)
-    #
-    #         # Switch to Project Tab
-    #         if key == QtCore.Qt.Key_1:
-    #             self.app.on_select_tab('project')
-    #
-    #         # Switch to Selected Tab
-    #         if key == QtCore.Qt.Key_2:
-    #             self.app.on_select_tab('selected')
-    #
-    #         # Switch to Tool Tab
-    #         if key == QtCore.Qt.Key_3:
-    #             self.app.on_select_tab('tool')
-    #
-    #         # Delete
-    #         if key == QtCore.Qt.Key_Delete and active:
-    #             # Delete via the application to
-    #             # ensure cleanup of the GUI
-    #             active.app.on_delete()
-    #
-    #         # Space = Toggle Active/Inactive
-    #         if key == QtCore.Qt.Key_Space:
-    #             for select in selected:
-    #                 select.ui.plot_cb.toggle()
-    #             self.app.delete_selection_shape()
-    #
-    #         # Copy Object Name
-    #         if key == QtCore.Qt.Key_E:
-    #             self.app.object2editor()
-    #
-    #         # Grid toggle
-    #         if key == QtCore.Qt.Key_G:
-    #             self.app.ui.grid_snap_btn.trigger()
-    #
-    #         # Jump to coords
-    #         if key == QtCore.Qt.Key_J:
-    #             self.app.on_jump_to()
-    #
-    #         # New Excellon
-    #         if key == QtCore.Qt.Key_L:
-    #             self.app.new_excellon_object()
-    #
-    #         # Move tool toggle
-    #         if key == QtCore.Qt.Key_M:
-    #             self.app.move_tool.toggle()
-    #
-    #         # New Geometry
-    #         if key == QtCore.Qt.Key_N:
-    #             self.app.on_new_geometry()
-    #
-    #         # Set Origin
-    #         if key == QtCore.Qt.Key_O:
-    #             self.app.on_set_origin()
-    #             return
-    #
-    #         # Set Origin
-    #         if key == QtCore.Qt.Key_P:
-    #             self.app.properties_tool.run()
-    #             return
-    #
-    #         # Change Units
-    #         if key == QtCore.Qt.Key_Q:
-    #             if self.app.options["units"] == 'MM':
-    #                 self.app.ui.general_options_form.general_app_group.units_radio.set_value("IN")
-    #             else:
-    #                 self.app.ui.general_options_form.general_app_group.units_radio.set_value("MM")
-    #             self.app.on_toggle_units()
-    #
-    #         # Rotate Object by 90 degree CW
-    #         if key == QtCore.Qt.Key_R:
-    #             self.app.on_rotate(silent=True, preset=90)
-    #
-    #         # Shell toggle
-    #         if key == QtCore.Qt.Key_S:
-    #             self.app.on_toggle_shell()
-    #
-    #         # Transform Tool
-    #         if key == QtCore.Qt.Key_T:
-    #             self.app.transform_tool.run()
-    #
-    #         # Zoom Fit
-    #         if key == QtCore.Qt.Key_V:
-    #             self.app.on_zoom_fit(None)
-    #
-    #         # Mirror on X the selected object(s)
-    #         if key == QtCore.Qt.Key_X:
-    #             self.app.on_flipx()
-    #
-    #         # Mirror on Y the selected object(s)
-    #         if key == QtCore.Qt.Key_Y:
-    #             self.app.on_flipy()
-    #
-    #         # Zoom In
-    #         if key == QtCore.Qt.Key_Equal:
-    #             self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse)
-    #
-    #         # Zoom Out
-    #         if key == QtCore.Qt.Key_Minus:
-    #             self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse)
-    #
-    #         # Show shortcut list
-    #         if key == QtCore.Qt.Key_Ampersand:
-    #             self.app.on_shortcut_list()
-    #
-    #         if key == QtCore.Qt.Key_QuoteLeft:
-    #             self.app.on_shortcut_list()
-    #         return
-
     def on_mouse_down(self, event):
         FlatCAMApp.App.log.debug("Mouse button pressed on list")
 

+ 211 - 35
ObjectUI.py

@@ -1,7 +1,8 @@
 import sys
-from PyQt5 import QtGui, QtCore, QtWidgets, QtWidgets
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5.QtCore import Qt
 from GUIElements import FCEntry, FloatEntry, EvalEntry, FCCheckBox, FCTable, \
-    LengthEntry, FCTextArea, IntEntry, RadioSet, OptionalInputSection, FCComboBox, FloatEntry2, EvalEntry2
+    LengthEntry, FCTextArea, IntEntry, RadioSet, OptionalInputSection, FCComboBox, FloatEntry2, EvalEntry2, FCButton
 from camlib import Excellon
 
 
@@ -127,6 +128,8 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(grid0)
 
         self.plot_options_label = QtWidgets.QLabel("<b>Plot Options:</b>")
+        self.plot_options_label.setFixedWidth(90)
+
         grid0.addWidget(self.plot_options_label, 0, 0)
 
         # Solid CB
@@ -145,6 +148,14 @@ class GerberObjectUI(ObjectUI):
         self.multicolored_cb.setFixedWidth(55)
         grid0.addWidget(self.multicolored_cb, 0, 2)
 
+        # Plot CB
+        self.plot_cb = FCCheckBox('Plot')
+        self.plot_cb.setToolTip(
+            "Plot (show) this object."
+        )
+        self.plot_cb.setFixedWidth(55)
+        grid0.addWidget(self.plot_cb, 0, 3)
+
         ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
@@ -158,30 +169,43 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(hlay_plot)
 
         #### Gerber Apertures ####
-        self.apertures_table_label = QtWidgets.QLabel('<b>Apertures Table</b>')
+        self.apertures_table_label = QtWidgets.QLabel('<b>Apertures:</b>')
         self.apertures_table_label.setToolTip(
-            "Apertures in this Gerber object."
+            "Apertures Table for the Gerber Object."
         )
+        self.apertures_table_label.setFixedWidth(90)
+
         hlay_plot.addWidget(self.apertures_table_label)
 
         # Aperture Table Visibility CB
         self.aperture_table_visibility_cb = FCCheckBox()
-        hlay_plot.addWidget(self.aperture_table_visibility_cb)
+        self.aperture_table_visibility_cb.setToolTip(
+            "Toggle the display of the Gerber Apertures Table.\n"
+            "When unchecked, it will delete all mark shapes\n"
+            "that are drawn on canvas."
 
-        # Plot CB
-        self.plot_cb = FCCheckBox('Plot Object')
-        self.plot_cb.setToolTip(
-            "Plot (show) this object."
         )
-        self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        # self.aperture_table_visibility_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        hlay_plot.addWidget(self.aperture_table_visibility_cb)
+
         hlay_plot.addStretch()
-        hlay_plot.addWidget(self.plot_cb)
+
+        # Aperture Mark all CB
+        self.mark_all_cb = FCCheckBox('Mark All')
+        self.mark_all_cb.setToolTip(
+            "When checked it will display all the apertures.\n"
+            "When unchecked, it will delete all mark shapes\n"
+            "that are drawn on canvas."
+
+        )
+        self.mark_all_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        hlay_plot.addWidget(self.mark_all_cb)
 
         self.apertures_table = FCTable()
         self.custom_box.addWidget(self.apertures_table)
 
         self.apertures_table.setColumnCount(6)
-        self.apertures_table.setHorizontalHeaderLabels(['#', 'Code', 'Type', 'Size', 'Dim', 'P'])
+        self.apertures_table.setHorizontalHeaderLabels(['#', 'Code', 'Type', 'Size', 'Dim', 'M'])
         self.apertures_table.setSortingEnabled(False)
 
         self.apertures_table.horizontalHeaderItem(0).setToolTip(
@@ -197,16 +221,82 @@ class GerberObjectUI(ObjectUI):
             " - (width, height) for R, O type.\n"
             " - (dia, nVertices) for P type")
         self.apertures_table.horizontalHeaderItem(5).setToolTip(
-            "Toggle display of the aperture instances.")
+            "Mark the aperture instances on canvas.")
+        # self.apertures_table.setColumnHidden(5, True)
+
+        #### Aperture Scale ####
+        self.transform_aperture_grid = QtWidgets.QGridLayout()
+        self.custom_box.addLayout(self.transform_aperture_grid)
+
+        # Scale Aperture Factor
+        self.scale_aperture_label = QtWidgets.QLabel('Scale Factor:')
+        self.scale_aperture_label.setToolTip(
+            "Change the size of the selected apertures.\n"
+            "Factor by which to multiply\n"
+            "geometric features of this object."
+        )
+        self.scale_aperture_label.setFixedWidth(90)
+        self.transform_aperture_grid.addWidget(self.scale_aperture_label, 0, 0)
+
+        self.scale_aperture_entry = FloatEntry2()
+        self.transform_aperture_grid.addWidget(self.scale_aperture_entry, 0, 1)
+
+        # Scale Button
+        self.scale_aperture_button = QtWidgets.QPushButton('Scale')
+        self.scale_aperture_button.setToolTip(
+            "Perform scaling operation on the selected apertures."
+        )
+        self.scale_aperture_button.setFixedWidth(50)
+        self.transform_aperture_grid.addWidget(self.scale_aperture_button, 0, 2)
+
+        # Buffer Aperture Factor
+        self.buffer_aperture_label = QtWidgets.QLabel('Buffer Factor:')
+        self.buffer_aperture_label.setToolTip(
+            "Change the size of the selected apertures.\n"
+            "Factor by which to expand/shrink\n"
+            "geometric features of this object."
+        )
+        self.buffer_aperture_label.setFixedWidth(90)
+        self.transform_aperture_grid.addWidget(self.buffer_aperture_label, 1, 0)
+
+        self.buffer_aperture_entry = FloatEntry2()
+        self.transform_aperture_grid.addWidget(self.buffer_aperture_entry, 1, 1)
+
+        # Buffer Button
+        self.buffer_aperture_button = QtWidgets.QPushButton('Buffer')
+        self.buffer_aperture_button.setToolTip(
+            "Perform buffer operation on the selected apertures."
+        )
+        self.buffer_aperture_button.setFixedWidth(50)
+        self.transform_aperture_grid.addWidget(self.buffer_aperture_button, 1, 2)
+
+        new_hlay = QtWidgets.QHBoxLayout()
+        self.custom_box.addLayout(new_hlay)
+
+        self.new_grb_label = QtWidgets.QLabel("<b>Generate new Gerber Object:</b>")
+        self.new_grb_label.setToolTip(
+            "Will generate a new Gerber object from the changed apertures."
+        )
+        new_hlay.addWidget(self.new_grb_label)
+
+        new_hlay.addStretch()
+
+        self.new_grb_button = FCButton('Go')
+        self.new_grb_button.setToolTip(
+            "Will generate a new Gerber object from the changed apertures.\n"
+            "This new object can then be isolated etc.")
+        self.new_grb_button.setFixedWidth(50)
+        new_hlay.addWidget(self.new_grb_button)
 
         # start with apertures table hidden
         self.apertures_table.setVisible(False)
+        self.scale_aperture_label.setVisible(False)
+        self.scale_aperture_entry.setVisible(False)
+        self.scale_aperture_button.setVisible(False)
 
-        # hide the plot column. for now I can't plot individually the apertures without making the plot really ugly
-        self.apertures_table.setColumnHidden(5, True)
-        #
-        # self.empty_label = QtWidgets.QLabel('')
-        # self.custom_box.addWidget(self.empty_label)
+        self.buffer_aperture_label.setVisible(False)
+        self.buffer_aperture_entry.setVisible(False)
+        self.buffer_aperture_button.setVisible(False)
 
         # Isolation Routing
         self.isolation_routing_label = QtWidgets.QLabel("<b>Isolation Routing:</b>")
@@ -226,6 +316,7 @@ class GerberObjectUI(ObjectUI):
             "feature, use a negative value for\n"
             "this parameter."
         )
+        tdlabel.setFixedWidth(90)
         grid1.addWidget(tdlabel, 0, 0)
         self.iso_tool_dia_entry = LengthEntry()
         grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
@@ -235,6 +326,7 @@ class GerberObjectUI(ObjectUI):
             "Width of the isolation gap in\n"
             "number (integer) of tool widths."
         )
+        passlabel.setFixedWidth(90)
         grid1.addWidget(passlabel, 1, 0)
         self.iso_width_entry = IntEntry()
         grid1.addWidget(self.iso_width_entry, 1, 1)
@@ -245,6 +337,7 @@ class GerberObjectUI(ObjectUI):
             "Example:\n"
             "A value here of 0.25 means an overlap of 25% from the tool diameter found above."
         )
+        overlabel.setFixedWidth(90)
         grid1.addWidget(overlabel, 2, 0)
         self.iso_overlap_entry = FloatEntry()
         grid1.addWidget(self.iso_overlap_entry, 2, 1)
@@ -295,7 +388,16 @@ class GerberObjectUI(ObjectUI):
         hlay_1 = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(hlay_1)
 
-        hlay_1.addStretch()
+        self.generate_iso_button = QtWidgets.QPushButton('FULL Geo')
+        self.generate_iso_button.setToolTip(
+            "Create the Geometry Object\n"
+            "for isolation routing. It contains both\n"
+            "the interiors and exteriors geometry."
+        )
+        self.generate_iso_button.setFixedWidth(90)
+        hlay_1.addWidget(self.generate_iso_button)
+
+        # hlay_1.addStretch()
 
         self.generate_ext_iso_button = QtWidgets.QPushButton('Ext Geo')
         self.generate_ext_iso_button.setToolTip(
@@ -303,7 +405,7 @@ class GerberObjectUI(ObjectUI):
             "for isolation routing containing\n"
             "only the exteriors geometry."
         )
-        self.generate_ext_iso_button.setFixedWidth(60)
+        # self.generate_ext_iso_button.setFixedWidth(100)
         hlay_1.addWidget(self.generate_ext_iso_button)
 
         self.generate_int_iso_button = QtWidgets.QPushButton('Int Geo')
@@ -312,18 +414,9 @@ class GerberObjectUI(ObjectUI):
             "for isolation routing containing\n"
             "only the interiors geometry."
         )
-        self.generate_int_iso_button.setFixedWidth(60)
+        # self.generate_ext_iso_button.setFixedWidth(90)
         hlay_1.addWidget(self.generate_int_iso_button)
 
-        self.generate_iso_button = QtWidgets.QPushButton('FULL Geo')
-        self.generate_iso_button.setToolTip(
-            "Create the Geometry Object\n"
-            "for isolation routing. It contains both\n"
-            "the interiors and exteriors geometry."
-        )
-        self.generate_iso_button.setFixedWidth(80)
-        hlay_1.addWidget(self.generate_iso_button)
-
         # when the follow checkbox is checked then the exteriors and interiors isolation generation buttons
         # are disabled as is doesn't make sense to have them enabled due of the nature of "follow"
         self.ois_iso = OptionalInputSection(self.follow_cb,
@@ -333,11 +426,12 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(grid2)
 
         ## Clear non-copper regions
-        self.clearcopper_label = QtWidgets.QLabel("<b>Clear non-copper:</b>")
+        self.clearcopper_label = QtWidgets.QLabel("<b>Clear N-copper:</b>")
         self.clearcopper_label.setToolTip(
             "Create a Geometry object with\n"
             "toolpaths to cut all non-copper regions."
         )
+        self.clearcopper_label.setFixedWidth(90)
         grid2.addWidget(self.clearcopper_label, 0, 0)
 
         self.generate_ncc_button = QtWidgets.QPushButton('NCC Tool')
@@ -385,15 +479,17 @@ class GerberObjectUI(ObjectUI):
             "objects with this minimum\n"
             "distance."
         )
+        bmlabel.setFixedWidth(90)
         grid4.addWidget(bmlabel, 0, 0)
         self.noncopper_margin_entry = LengthEntry()
         grid4.addWidget(self.noncopper_margin_entry, 0, 1)
 
         # Rounded corners
-        self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners")
+        self.noncopper_rounded_cb = FCCheckBox(label="Rounded Geo")
         self.noncopper_rounded_cb.setToolTip(
             "Resulting geometry will have rounded corners."
         )
+        self.noncopper_rounded_cb.setFixedWidth(90)
         grid4.addWidget(self.noncopper_rounded_cb, 1, 0)
 
         self.generate_noncopper_button = QtWidgets.QPushButton('Generate Geo')
@@ -415,17 +511,19 @@ class GerberObjectUI(ObjectUI):
             "Distance of the edges of the box\n"
             "to the nearest polygon."
         )
+        bbmargin.setFixedWidth(90)
         grid5.addWidget(bbmargin, 0, 0)
         self.bbmargin_entry = LengthEntry()
         grid5.addWidget(self.bbmargin_entry, 0, 1)
 
-        self.bbrounded_cb = FCCheckBox(label="Rounded corners")
+        self.bbrounded_cb = FCCheckBox(label="Rounded Geo")
         self.bbrounded_cb.setToolTip(
             "If the bounding box is \n"
             "to have rounded corners\n"
             "their radius is equal to\n"
             "the margin."
         )
+        self.bbrounded_cb.setFixedWidth(90)
         grid5.addWidget(self.bbrounded_cb, 1, 0)
 
         self.generate_bb_button = QtWidgets.QPushButton('Generate Geo')
@@ -1324,7 +1422,7 @@ class CNCObjectUI(ObjectUI):
         )
         self.custom_box.addWidget(self.export_gcode_label)
 
-        # Prepend text to Gerber
+        # Prepend text to GCode
         prependlabel = QtWidgets.QLabel('Prepend to CNC Code:')
         prependlabel.setToolTip(
             "Type here any G-Code commands you would\n"
@@ -1335,7 +1433,7 @@ class CNCObjectUI(ObjectUI):
         self.prepend_text = FCTextArea()
         self.custom_box.addWidget(self.prepend_text)
 
-        # Append text to Gerber
+        # Append text to GCode
         appendlabel = QtWidgets.QLabel('Append to CNC Code')
         appendlabel.setToolTip(
             "Type here any G-Code commands you would\n"
@@ -1347,6 +1445,84 @@ class CNCObjectUI(ObjectUI):
         self.append_text = FCTextArea()
         self.custom_box.addWidget(self.append_text)
 
+        self.cnc_frame = QtWidgets.QFrame()
+        self.cnc_frame.setContentsMargins(0, 0, 0, 0)
+        self.custom_box.addWidget(self.cnc_frame)
+        self.cnc_box = QtWidgets.QVBoxLayout()
+        self.cnc_box.setContentsMargins(0, 0, 0, 0)
+        self.cnc_frame.setLayout(self.cnc_box)
+
+        # Toolchange Custom G-Code
+        self.toolchangelabel = QtWidgets.QLabel('Toolchange G-Code:')
+        self.toolchangelabel.setToolTip(
+            "Type here any G-Code commands you would\n"
+            "like to be executed when Toolchange event is encountered.\n"
+            "This will constitute a Custom Toolchange GCode,\n"
+            "or a Toolchange Macro.\n"
+            "The FlatCAM variables are surrounded by '%' symbol.\n\n"
+            "WARNING: it can be used only with a postprocessor file\n"
+            "that has 'toolchange_custom' in it's name and this is built\n"
+            "having as template the 'Toolchange Custom' posprocessor file."
+        )
+        self.cnc_box.addWidget(self.toolchangelabel)
+
+        self.toolchange_text = FCTextArea()
+        self.cnc_box.addWidget(self.toolchange_text)
+
+        cnclay = QtWidgets.QHBoxLayout()
+        self.cnc_box.addLayout(cnclay)
+
+        # Toolchange Replacement Enable
+        self.toolchange_cb = FCCheckBox(label='Use Toolchange Macro')
+        self.toolchange_cb.setToolTip(
+            "Check this box if you want to use\n"
+            "a Custom Toolchange GCode (macro)."
+        )
+        cnclay.addWidget(self.toolchange_cb)
+
+        self.toolch_ois = OptionalInputSection(self.toolchange_cb, [self.toolchangelabel, self.toolchange_text])
+        cnclay.addStretch()
+
+        cnclay1 = QtWidgets.QHBoxLayout()
+        self.cnc_box.addLayout(cnclay1)
+
+        # Variable list
+        self.tc_variable_combo = FCComboBox()
+        self.tc_variable_combo.setToolTip(
+            "A list of the FlatCAM variables that can be used\n"
+            "in the Toolchange event.\n"
+            "They have to be surrounded by the '%' symbol"
+        )
+        cnclay1.addWidget(self.tc_variable_combo)
+
+        # Populate the Combo Box
+        variables = ['Parameters', 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange',
+                     'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime']
+        self.tc_variable_combo.addItems(variables)
+        self.tc_variable_combo.setItemData(0, "FlatCAM CNC parameters", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(1, "tool = tool number", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(2, "tooldia = tool diameter", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(3, "t_drills = for Excellon, total number of drills", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(4, "x_toolchange = X coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(5, "y_toolchange = Y coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(6, "z_toolchange = Z coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(7, "z_cut = Z coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(8, "z_move = Z coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(9, "z_depthpercut = the step value for multidepth cut", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(10, "spindlesspeed = the value for the spindle speed", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(11, "dwelltime = time to dwell to allow the spindle to reach it's set RPM",
+                                           Qt.ToolTipRole)
+
+        cnclay1.addStretch()
+
+        # Insert Variable into the Toolchange G-Code Text Box
+        # self.tc_insert_buton = FCButton("Insert")
+        # self.tc_insert_buton.setToolTip(
+        #     "Insert the variable in the GCode Box\n"
+        #     "surrounded by the '%' symbol."
+        # )
+        # cnclay1.addWidget(self.tc_insert_buton)
+
         h_lay = QtWidgets.QHBoxLayout()
         h_lay.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.custom_box.addLayout(h_lay)

+ 5 - 3
PlotCanvas.py

@@ -65,7 +65,7 @@ class PlotCanvas(QtCore.QObject):
         self.draw_workspace()
 
         # if self.app.defaults['global_workspace'] is True:
-        #     if self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper() == 'MM':
+        #     if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
         #         self.wkspace_t = Line(pos=)
 
         self.shape_collections = []
@@ -92,7 +92,7 @@ class PlotCanvas(QtCore.QObject):
         a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)])
         a3l_mm = np.array([(0, 0), (420, 0), (420, 297), (0, 297)])
 
-        if self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper() == 'MM':
+        if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
             if self.app.defaults['global_workspaceT'] == 'A4P':
                 a = a4p_mm
             elif self.app.defaults['global_workspaceT'] == 'A4L':
@@ -165,7 +165,9 @@ class PlotCanvas(QtCore.QObject):
         """
         self.vispy_canvas.view.camera.zoom(factor, center)
 
-    def new_shape_group(self):
+    def new_shape_group(self, shape_collection=None):
+        if shape_collection:
+            return ShapeGroup(shape_collection)
         return ShapeGroup(self.shape_collection)
 
     def new_shape_collection(self, **kwargs):

+ 78 - 0
README.md

@@ -9,6 +9,84 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+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_apertures() 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

+ 335 - 85
camlib.py

@@ -39,6 +39,8 @@ import numpy as np
 import rasterio
 from rasterio.features import shapes
 
+from copy import deepcopy
+
 # TODO: Commented for FlatCAM packaging with cx_freeze
 
 from xml.dom.minidom import parseString as parse_xml_string
@@ -1856,7 +1858,8 @@ class Gerber (Geometry):
     +-----------+-----------------------------------+
     | others    | Depend on ``type``                |
     +-----------+-----------------------------------+
-
+    | solid_geometry      | (list)                  |
+    +-----------+-----------------------------------+
     * ``aperture_macros`` (dictionary): Are predefined geometrical structures
       that can be instantiated with different parameters in an aperture
       definition. See ``apertures`` above. The key is the name of the macro,
@@ -1900,12 +1903,6 @@ class Gerber (Geometry):
         # Initialize parent
         Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
 
-        # will store the Gerber geometry's as solids
-        self.solid_geometry = Polygon()
-
-        # will store the Gerber geometry's as paths
-        self.follow_geometry = []
-
         # Number format
         self.int_digits = 3
         """Number of integer digits in Gerber numbers. Used during parsing."""
@@ -1918,14 +1915,31 @@ class Gerber (Geometry):
         """
 
         ## Gerber elements ##
-        # Apertures {'id':{'type':chr, 
-        #             ['size':float], ['width':float],
-        #             ['height':float]}, ...}
+        '''
+        apertures = {
+            'id':{
+                'type':chr, 
+                'size':float, 
+                'width':float,
+                'height':float,
+                'solid_geometry': [],
+                'follow_geometry': [],
+            }
+        }
+        '''
+
+        # aperture storage
         self.apertures = {}
 
         # Aperture Macros
         self.aperture_macros = {}
 
+        # will store the Gerber geometry's as solids
+        self.solid_geometry = Polygon()
+
+        # will store the Gerber geometry's as paths
+        self.follow_geometry = []
+
         self.source_file = ''
 
         # Attributes to be included in serialization
@@ -2139,9 +2153,6 @@ class Gerber (Geometry):
         :param glines: Gerber code as list of strings, each element being
             one line of the source file.
         :type glines: list
-        :param follow: If true, will not create polygons, just lines
-            following the gerber path.
-        :type follow: bool
         :return: None
         :rtype: None
         """
@@ -2221,8 +2232,10 @@ class Gerber (Geometry):
                 # buffer, then adds or subtracts accordingly.
                 match = self.lpol_re.search(gline)
                 if match:
-                    if len(path) > 1 and current_polarity != match.group(1):
+                    new_polarity = match.group(1)
+                    if len(path) > 1 and current_polarity != new_polarity:
 
+                        # finish the current path and add it to the storage
                         # --- Buffered ----
                         width = self.apertures[last_path_aperture]["size"]
 
@@ -2233,6 +2246,11 @@ class Gerber (Geometry):
                         geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                         if not geo.is_empty:
                             poly_buffer.append(geo)
+                            try:
+                                self.apertures[current_aperture]['solid_geometry'].append(geo)
+                            except KeyError:
+                                self.apertures[current_aperture]['solid_geometry'] = []
+                                self.apertures[current_aperture]['solid_geometry'].append(geo)
 
                         path = [path[-1]]
 
@@ -2241,17 +2259,17 @@ class Gerber (Geometry):
                     # TODO: Remove when bug fixed
                     if len(poly_buffer) > 0:
                         if current_polarity == 'D':
-                            self.follow_geometry = self.solid_geometry.union(cascaded_union(follow_buffer))
+                            # self.follow_geometry = self.follow_geometry.union(cascaded_union(follow_buffer))
                             self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
 
                         else:
-                            self.follow_geometry = self.solid_geometry.difference(cascaded_union(follow_buffer))
-                            self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
+                            # self.follow_geometry = self.follow_geometry.difference(cascaded_union(follow_buffer))
+                            self.solid_geometry = self.solid_geometry.difference(cascaded_union(poly_buffer))
 
-                        follow_buffer = []
+                        # follow_buffer = []
                         poly_buffer = []
 
-                    current_polarity = match.group(1)
+                    current_polarity = new_polarity
                     continue
 
                 ### Number format
@@ -2397,6 +2415,11 @@ class Gerber (Geometry):
                                 int(self.steps_per_circle))
                             if not flash.is_empty:
                                 poly_buffer.append(flash)
+                                try:
+                                    self.apertures[current_aperture]['solid_geometry'].append(flash)
+                                except KeyError:
+                                    self.apertures[current_aperture]['solid_geometry'] = []
+                                    self.apertures[current_aperture]['solid_geometry'].append(flash)
                         except IndexError:
                             log.warning("Line %d: %s -> Nothing there to flash!" % (line_num, gline))
 
@@ -2434,6 +2457,11 @@ class Gerber (Geometry):
                             geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                             if not geo.is_empty:
                                 poly_buffer.append(geo)
+                                try:
+                                    self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+                                except KeyError:
+                                    self.apertures[last_path_aperture]['solid_geometry'] = []
+                                    self.apertures[last_path_aperture]['solid_geometry'].append(geo)
 
                             path = [path[-1]]
 
@@ -2454,6 +2482,11 @@ class Gerber (Geometry):
                         geo = LineString(path).buffer(width/1.999, int(self.steps_per_circle / 4))
                         if not geo.is_empty:
                             poly_buffer.append(geo)
+                            try:
+                                self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+                            except KeyError:
+                                self.apertures[last_path_aperture]['solid_geometry'] = []
+                                self.apertures[last_path_aperture]['solid_geometry'].append(geo)
 
                         path = [path[-1]]
 
@@ -2471,6 +2504,11 @@ class Gerber (Geometry):
                             if not geo.is_empty:
                                 follow_buffer.append(geo)
                                 poly_buffer.append(geo)
+                                try:
+                                    self.apertures[current_aperture]['solid_geometry'].append(geo)
+                                except KeyError:
+                                    self.apertures[current_aperture]['solid_geometry'] = []
+                                    self.apertures[current_aperture]['solid_geometry'].append(geo)
                             continue
 
                     # Only one path defines region?
@@ -2497,9 +2535,29 @@ class Gerber (Geometry):
                     region = Polygon(path)
                     if not region.is_valid:
                         region = region.buffer(0, int(self.steps_per_circle / 4))
+
                     if not region.is_empty:
                         poly_buffer.append(region)
 
+                        # we do this for the case that a region is done without having defined any aperture
+                        # Allegro does that
+                        if current_aperture:
+                            used_aperture = current_aperture
+                        elif last_path_aperture:
+                            used_aperture = last_path_aperture
+                        else:
+                            if '0' not in self.apertures:
+                                self.apertures['0'] = {}
+                                self.apertures['0']['solid_geometry'] = []
+                                self.apertures['0']['type'] = 'REG'
+                            used_aperture = '0'
+
+                        try:
+                            self.apertures[used_aperture]['solid_geometry'].append(region)
+                        except KeyError:
+                            self.apertures[used_aperture]['solid_geometry'] = []
+                            self.apertures[used_aperture]['solid_geometry'].append(region)
+
                     path = [[current_x, current_y]]  # Start new path
                     continue
 
@@ -2566,7 +2624,14 @@ class Gerber (Geometry):
                                         miny = min(path[0][1], path[1][1]) - height / 2
                                         maxy = max(path[0][1], path[1][1]) + height / 2
                                         log.debug("Coords: %s - %s - %s - %s" % (minx, miny, maxx, maxy))
-                                        poly_buffer.append(shply_box(minx, miny, maxx, maxy))
+
+                                        geo = shply_box(minx, miny, maxx, maxy)
+                                        poly_buffer.append(geo)
+                                        try:
+                                            self.apertures[current_aperture]['solid_geometry'].append(geo)
+                                        except KeyError:
+                                            self.apertures[current_aperture]['solid_geometry'] = []
+                                            self.apertures[current_aperture]['solid_geometry'].append(geo)
                                 except:
                                     pass
                             last_path_aperture = current_aperture
@@ -2596,6 +2661,7 @@ class Gerber (Geometry):
                                 elem = [linear_x, linear_y]
                                 if elem != path[-1]:
                                     path.append([linear_x, linear_y])
+
                                 try:
                                     geo = Polygon(path)
                                 except ValueError:
@@ -2613,8 +2679,18 @@ class Gerber (Geometry):
                                 if self.apertures[last_path_aperture]["type"] != 'R':
                                     if not geo.is_empty:
                                         poly_buffer.append(geo)
+                                        try:
+                                            self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+                                        except KeyError:
+                                            self.apertures[last_path_aperture]['solid_geometry'] = []
+                                            self.apertures[last_path_aperture]['solid_geometry'].append(geo)
                             except:
                                 poly_buffer.append(geo)
+                                try:
+                                    self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+                                except KeyError:
+                                    self.apertures[last_path_aperture]['solid_geometry'] = []
+                                    self.apertures[last_path_aperture]['solid_geometry'].append(geo)
 
                         # if linear_x or linear_y are None, ignore those
                         if linear_x is not None and linear_y is not None:
@@ -2635,7 +2711,7 @@ class Gerber (Geometry):
                             geo = LineString(path)
                             if not geo.is_empty:
                                 try:
-                                    if self.apertures[current_aperture]["type"] != 'R':
+                                    if self.apertures[last_path_aperture]["type"] != 'R':
                                         follow_buffer.append(geo)
                                 except:
                                     follow_buffer.append(geo)
@@ -2645,10 +2721,20 @@ class Gerber (Geometry):
                             geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                             if not geo.is_empty:
                                 try:
-                                    if self.apertures[current_aperture]["type"] != 'R':
+                                    if self.apertures[last_path_aperture]["type"] != 'R':
                                         poly_buffer.append(geo)
+                                        try:
+                                            self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+                                        except KeyError:
+                                            self.apertures[last_path_aperture]['solid_geometry'] = []
+                                            self.apertures[last_path_aperture]['solid_geometry'].append(geo)
                                 except:
                                     poly_buffer.append(geo)
+                                    try:
+                                        self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+                                    except KeyError:
+                                        self.apertures[last_path_aperture]['solid_geometry'] = []
+                                        self.apertures[last_path_aperture]['solid_geometry'].append(geo)
 
                         # Reset path starting point
                         path = [[linear_x, linear_y]]
@@ -2666,6 +2752,11 @@ class Gerber (Geometry):
                         )
                         if not flash.is_empty:
                             poly_buffer.append(flash)
+                            try:
+                                self.apertures[current_aperture]['solid_geometry'].append(flash)
+                            except KeyError:
+                                self.apertures[current_aperture]['solid_geometry'] = []
+                                self.apertures[current_aperture]['solid_geometry'].append(flash)
 
                     # maybe those lines are not exactly needed but it is easier to read the program as those coordinates
                     # are used in case that circular interpolation is encountered within the Gerber file
@@ -2755,6 +2846,11 @@ class Gerber (Geometry):
                             buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
                             if not buffered.is_empty:
                                 poly_buffer.append(buffered)
+                                try:
+                                    self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
+                                except KeyError:
+                                    self.apertures[last_path_aperture]['solid_geometry'] = []
+                                    self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
 
                         current_x = circular_x
                         current_y = circular_y
@@ -2882,6 +2978,11 @@ class Gerber (Geometry):
                     geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                     if not geo.is_empty:
                         poly_buffer.append(geo)
+                        try:
+                            self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+                        except KeyError:
+                            self.apertures[last_path_aperture]['solid_geometry'] = []
+                            self.apertures[last_path_aperture]['solid_geometry'].append(geo)
 
             # --- Apply buffer ---
 
@@ -3092,6 +3193,7 @@ class Gerber (Geometry):
         :type factor: float
         :rtype : None
         """
+        log.debug("camlib.Gerber.scale()")
 
         try:
             xfactor = float(xfactor)
@@ -3125,8 +3227,18 @@ class Gerber (Geometry):
                                              yfactor, origin=(px, py))
 
         self.solid_geometry = scale_geom(self.solid_geometry)
+        self.follow_geometry = scale_geom(self.follow_geometry)
+
+        # we need to scale the geometry stored in the Gerber apertures, too
+        try:
+            for apid in self.apertures:
+                self.apertures[apid]['solid_geometry'] = scale_geom(self.apertures[apid]['solid_geometry'])
+        except Exception as e:
+            log.debug('FlatCAMGeometry.scale() --> %s' % str(e))
+
         self.app.inform.emit("[success]Gerber Scale done.")
 
+
         ## solid_geometry ???
         #  It's a cascaded union of objects.
         # self.solid_geometry = affinity.scale(self.solid_geometry, factor,
@@ -3171,8 +3283,16 @@ class Gerber (Geometry):
                 return affinity.translate(obj, xoff=dx, yoff=dy)
 
         ## Solid geometry
-        # self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
         self.solid_geometry = offset_geom(self.solid_geometry)
+        self.follow_geometry = offset_geom(self.follow_geometry)
+
+        # we need to offset the geometry stored in the Gerber apertures, too
+        try:
+            for apid in self.apertures:
+                self.apertures[apid]['solid_geometry'] = offset_geom(self.apertures[apid]['solid_geometry'])
+        except Exception as e:
+            log.debug('FlatCAMGeometry.offset() --> %s' % str(e))
+
         self.app.inform.emit("[success]Gerber Offset done.")
 
     def mirror(self, axis, point):
@@ -3210,6 +3330,14 @@ class Gerber (Geometry):
                 return affinity.scale(obj, xscale, yscale, origin=(px, py))
 
         self.solid_geometry = mirror_geom(self.solid_geometry)
+        self.follow_geometry = mirror_geom(self.follow_geometry)
+
+        # we need to mirror the geometry stored in the Gerber apertures, too
+        try:
+            for apid in self.apertures:
+                self.apertures[apid]['solid_geometry'] = mirror_geom(self.apertures[apid]['solid_geometry'])
+        except Exception as e:
+            log.debug('FlatCAMGeometry.mirror() --> %s' % str(e))
 
         #  It's a cascaded union of objects.
         # self.solid_geometry = affinity.scale(self.solid_geometry,
@@ -3243,7 +3371,14 @@ class Gerber (Geometry):
                 return affinity.skew(obj, angle_x, angle_y, origin=(px, py))
 
         self.solid_geometry = skew_geom(self.solid_geometry)
+        self.follow_geometry = skew_geom(self.follow_geometry)
 
+        # we need to skew the geometry stored in the Gerber apertures, too
+        try:
+            for apid in self.apertures:
+                self.apertures[apid]['solid_geometry'] = skew_geom(self.apertures[apid]['solid_geometry'])
+        except Exception as e:
+            log.debug('FlatCAMGeometry.skew() --> %s' % str(e))
         # self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y, origin=(px, py))
 
     def rotate(self, angle, point):
@@ -3266,7 +3401,14 @@ class Gerber (Geometry):
                 return affinity.rotate(obj, angle, origin=(px, py))
 
         self.solid_geometry = rotate_geom(self.solid_geometry)
+        self.follow_geometry = rotate_geom(self.follow_geometry)
 
+        # we need to rotate the geometry stored in the Gerber apertures, too
+        try:
+            for apid in self.apertures:
+                self.apertures[apid]['solid_geometry'] = rotate_geom(self.apertures[apid]['solid_geometry'])
+        except Exception as e:
+            log.debug('FlatCAMGeometry.rotate() --> %s' % str(e))
         # self.solid_geometry = affinity.rotate(self.solid_geometry, angle, origin=(px, py))
 
 
@@ -3579,7 +3721,6 @@ class Excellon(Geometry):
                             headerless = True
                             try:
                                 self.convert_units({"INCH": "IN", "METRIC": "MM"}[self.excellon_units])
-                                print("Units converted .............................. %s" % self.excellon_units)
                             except Exception as e:
                                 log.warning("Units could not be converted: %s" % str(e))
 
@@ -4285,10 +4426,24 @@ class Excellon(Geometry):
         else:
             px, py = point
 
+        def scale_geom(obj):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(scale_geom(g))
+                return new_obj
+            else:
+                return affinity.scale(obj, xfactor,
+                                             yfactor, origin=(px, py))
+
         # Drills
         for drill in self.drills:
             drill['point'] = affinity.scale(drill['point'], xfactor, yfactor, origin=(px, py))
 
+        # scale solid_geometry
+        for tool in self.tools:
+            self.tools[tool]['solid_geometry'] = scale_geom(self.tools[tool]['solid_geometry'])
+
         # Slots
         for slot in self.slots:
             slot['stop'] = affinity.scale(slot['stop'], xfactor, yfactor, origin=(px, py))
@@ -4307,10 +4462,23 @@ class Excellon(Geometry):
 
         dx, dy = vect
 
+        def offset_geom(obj):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(offset_geom(g))
+                return new_obj
+            else:
+                return affinity.translate(obj, xoff=dx, yoff=dy)
+
         # Drills
         for drill in self.drills:
             drill['point'] = affinity.translate(drill['point'], xoff=dx, yoff=dy)
 
+        # offset solid_geometry
+        for tool in self.tools:
+            self.tools[tool]['solid_geometry'] = offset_geom(self.tools[tool]['solid_geometry'])
+
         # Slots
         for slot in self.slots:
             slot['stop'] = affinity.translate(slot['stop'], xoff=dx, yoff=dy)
@@ -4331,11 +4499,24 @@ class Excellon(Geometry):
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
 
+        def mirror_geom(obj):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(mirror_geom(g))
+                return new_obj
+            else:
+                return affinity.scale(obj, xscale, yscale, origin=(px, py))
+
         # Modify data
         # Drills
         for drill in self.drills:
             drill['point'] = affinity.scale(drill['point'], xscale, yscale, origin=(px, py))
 
+        # mirror solid_geometry
+        for tool in self.tools:
+            self.tools[tool]['solid_geometry'] = mirror_geom(self.tools[tool]['solid_geometry'])
+
         # Slots
         for slot in self.slots:
             slot['stop'] = affinity.scale(slot['stop'], xscale, yscale, origin=(px, py))
@@ -4365,16 +4546,30 @@ class Excellon(Geometry):
         if angle_y is None:
             angle_y = 0.0
 
+        def skew_geom(obj):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(skew_geom(g))
+                return new_obj
+            else:
+                return affinity.skew(obj, angle_x, angle_y, origin=(px, py))
+
         if point is None:
+            px, py = 0, 0
+
             # Drills
             for drill in self.drills:
                 drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
-                                               origin=(0, 0))
+                                               origin=(px, py))
+            # skew solid_geometry
+            for tool in self.tools:
+                self.tools[tool]['solid_geometry'] = skew_geom(self.tools[tool]['solid_geometry'])
 
             # Slots
             for slot in self.slots:
-                slot['stop'] = affinity.skew(slot['stop'], angle_x, angle_y, origin=(0, 0))
-                slot['start'] = affinity.skew(slot['start'], angle_x, angle_y, origin=(0, 0))
+                slot['stop'] = affinity.skew(slot['stop'], angle_x, angle_y, origin=(px, py))
+                slot['start'] = affinity.skew(slot['start'], angle_x, angle_y, origin=(px, py))
         else:
             px, py = point
             # Drills
@@ -4382,6 +4577,10 @@ class Excellon(Geometry):
                 drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
                                                origin=(px, py))
 
+            # skew solid_geometry
+            for tool in self.tools:
+                self.tools[tool]['solid_geometry'] = skew_geom( self.tools[tool]['solid_geometry'])
+
             # Slots
             for slot in self.slots:
                 slot['stop'] = affinity.skew(slot['stop'], angle_x, angle_y, origin=(px, py))
@@ -4396,11 +4595,28 @@ class Excellon(Geometry):
         :param point: tuple of coordinates (x, y)
         :return:
         """
+
+        def rotate_geom(obj, origin=None):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(rotate_geom(g))
+                return new_obj
+            else:
+                if origin:
+                    return affinity.rotate(obj, angle, origin=origin)
+                else:
+                    return affinity.rotate(obj, angle, origin=(px, py))
+
         if point is None:
             # Drills
             for drill in self.drills:
                 drill['point'] = affinity.rotate(drill['point'], angle, origin='center')
 
+            # rotate solid_geometry
+            for tool in self.tools:
+                self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'], origin='center')
+
             # Slots
             for slot in self.slots:
                 slot['stop'] = affinity.rotate(slot['stop'], angle, origin='center')
@@ -4411,6 +4627,10 @@ class Excellon(Geometry):
             for drill in self.drills:
                 drill['point'] = affinity.rotate(drill['point'], angle, origin=(px, py))
 
+            # rotate solid_geometry
+            for tool in self.tools:
+                self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'])
+
             # Slots
             for slot in self.slots:
                 slot['stop'] = affinity.rotate(slot['stop'], angle, origin=(px, py))
@@ -4479,16 +4699,18 @@ class CNCjob(Geometry):
         self.z_move = z_move
 
         self.feedrate = feedrate
-        self.feedrate_z = feedrate_z
+        self.z_feedrate = feedrate_z
         self.feedrate_rapid = feedrate_rapid
 
         self.tooldia = tooldia
-        self.toolchangez = toolchangez
-        self.toolchange_xy = toolchange_xy
+        self.z_toolchange = toolchangez
+        self.xy_toolchange = toolchange_xy
         self.toolchange_xy_type = None
 
-        self.endz = endz
-        self.depthpercut = depthpercut
+        self.toolC = tooldia
+
+        self.z_end = endz
+        self.z_depthpercut = depthpercut
 
         self.unitcode = {"IN": "G20", "MM": "G21"}
 
@@ -4533,12 +4755,18 @@ class CNCjob(Geometry):
 
         self.tool = 0.0
 
+        # search for toolchange parameters in the Toolchange Custom Code
+        self.re_toolchange_custom = re.compile(r'(%[a-zA-Z0-9\-_]+%)')
+
+        # search for toolchange code: M6
+        self.re_toolchange = re.compile(r'^\s*(M6)$')
+
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # from Geometry.
-        self.ser_attrs += ['kind', 'z_cut', 'z_move', 'toolchangez', 'feedrate', 'feedrate_z', 'feedrate_rapid',
+        self.ser_attrs += ['kind', 'z_cut', 'z_move', 'z_toolchange', 'feedrate', 'z_feedrate', 'feedrate_rapid',
                            'tooldia', 'gcode', 'input_geometry_bounds', 'gcode_parsed', 'steps_per_circle',
-                           'depthpercut', 'spindlespeed', 'dwell', 'dwelltime']
+                           'z_depthpercut', 'spindlespeed', 'dwell', 'dwelltime']
 
     @property
     def postdata(self):
@@ -4551,12 +4779,12 @@ class CNCjob(Geometry):
         self.z_cut = float(self.z_cut) * factor
         self.z_move *= factor
         self.feedrate *= factor
-        self.feedrate_z *= factor
+        self.z_feedrate *= factor
         self.feedrate_rapid *= factor
         self.tooldia *= factor
-        self.toolchangez *= factor
-        self.endz *= factor
-        self.depthpercut = float(self.depthpercut) * factor
+        self.z_toolchange *= factor
+        self.z_end *= factor
+        self.z_depthpercut = float(self.z_depthpercut) * factor
 
         return factor
 
@@ -4574,6 +4802,22 @@ class CNCjob(Geometry):
             self.app.log.error('Exception occurred within a postprocessor: ' + traceback.format_exc())
             return ''
 
+    def parse_custom_toolchange_code(self, data):
+        text = data
+        match_list = self.re_toolchange_custom.findall(text)
+
+        if match_list:
+            for match in match_list:
+                command = match.strip('%')
+                try:
+                    value = getattr(self, command)
+                except AttributeError:
+                    self.app.inform.emit("[ERROR] There is no such parameter: %s" % str(match))
+                    log.debug("CNCJob.parse_custom_toolchange_code() --> AttributeError ")
+                    return 'fail'
+                text = text.replace(match, str(value))
+            return text
+
     def optimized_travelling_salesman(self, points, start=None):
         """
         As solving the problem in the brute force way is too slow,
@@ -4646,14 +4890,14 @@ class CNCjob(Geometry):
         else:
             self.z_cut = drillz
 
-        self.toolchangez = toolchangez
+        self.z_toolchange = toolchangez
 
         try:
             if toolchangexy == '':
-                self.toolchange_xy = None
+                self.xy_toolchange = None
             else:
-                self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
-                if len(self.toolchange_xy) < 2:
+                self.xy_toolchange = [float(eval(a)) for a in toolchangexy.split(",")]
+                if len(self.xy_toolchange) < 2:
                     self.app.inform.emit("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
                                          "in the format (x, y) \nbut now there is only one value, not two. ")
                     return 'fail'
@@ -4662,7 +4906,7 @@ class CNCjob(Geometry):
             pass
 
         self.startz = startz
-        self.endz = endz
+        self.z_end = endz
 
         self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
         p = self.pp_excellon
@@ -4712,9 +4956,9 @@ class CNCjob(Geometry):
         gcode += self.doformat(p.feedrate_code)
 
         if toolchange is False:
-            if self.toolchange_xy is not None:
-                gcode += self.doformat(p.lift_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
-                gcode += self.doformat(p.startz_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
+            if self.xy_toolchange is not None:
+                gcode += self.doformat(p.lift_code, x=self.xy_toolchange[0], y=self.xy_toolchange[1])
+                gcode += self.doformat(p.startz_code, x=self.xy_toolchange[0], y=self.xy_toolchange[1])
             else:
                 gcode += self.doformat(p.lift_code, x=0.0, y=0.0)
                 gcode += self.doformat(p.startz_code, x=0.0, y=0.0)
@@ -4751,9 +4995,9 @@ class CNCjob(Geometry):
                 locations.append((point.coords.xy[0][0], point.coords.xy[1][0]))
             return locations
 
-        if self.toolchange_xy is not None:
-            self.oldx = self.toolchange_xy[0]
-            self.oldy = self.toolchange_xy[1]
+        if self.xy_toolchange is not None:
+            self.oldx = self.xy_toolchange[0]
+            self.oldy = self.xy_toolchange[1]
         else:
             self.oldx = 0.0
             self.oldy = 0.0
@@ -4767,7 +5011,8 @@ class CNCjob(Geometry):
                 if exobj.drills:
                     for tool in tools:
                         self.tool=tool
-                        self.postdata['toolC']=exobj.tools[tool]["C"]
+                        self.postdata['toolC'] = exobj.tools[tool]["C"]
+                        self.tooldia = exobj.tools[tool]["C"]
 
                         ################################################
                         # Create the data.
@@ -4865,6 +5110,7 @@ class CNCjob(Geometry):
                     for tool in tools:
                         self.tool=tool
                         self.postdata['toolC']=exobj.tools[tool]["C"]
+                        self.tooldia = exobj.tools[tool]["C"]
 
                         ################################################
                         node_list = []
@@ -4957,6 +5203,7 @@ class CNCjob(Geometry):
                 if exobj.drills:
                     self.tool = tool
                     self.postdata['toolC'] = exobj.tools[tool]["C"]
+                    self.tooldia = exobj.tools[tool]["C"]
 
                     # Only if tool has points.
                     if tool in points:
@@ -5058,7 +5305,7 @@ class CNCjob(Geometry):
         self.z_move = float(z_move) if z_move else None
 
         self.feedrate = float(feedrate) if feedrate else None
-        self.feedrate_z = float(feedrate_z) if feedrate_z else None
+        self.z_feedrate = float(feedrate_z) if feedrate_z else None
         self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
 
         self.spindlespeed = int(spindlespeed) if spindlespeed else None
@@ -5066,19 +5313,22 @@ class CNCjob(Geometry):
         self.dwelltime = float(dwelltime) if dwelltime else None
 
         self.startz = float(startz) if startz else None
-        self.endz = float(endz) if endz else None
+        self.z_end = float(endz) if endz else None
 
-        self.depthpercut = float(depthpercut) if depthpercut else None
+        self.z_depthpercut = float(depthpercut) if depthpercut else None
         self.multidepth = multidepth
 
-        self.toolchangez = float(toolchangez) if toolchangez else None
+        self.z_toolchange = float(toolchangez) if toolchangez else None
+
+        # it servers in the postprocessor file
+        self.tool = tool_no
 
         try:
             if toolchangexy == '':
-                self.toolchange_xy = None
+                self.xy_toolchange = None
             else:
-                self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
-                if len(self.toolchange_xy) < 2:
+                self.xy_toolchange = [float(eval(a)) for a in toolchangexy.split(",")]
+                if len(self.xy_toolchange) < 2:
                     self.app.inform.emit("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
                                          "in the format (x, y) \nbut now there is only one value, not two. ")
                     return 'fail'
@@ -5141,7 +5391,7 @@ class CNCjob(Geometry):
 
         if toolchange:
             # if "line_xyz" in self.pp_geometry_name:
-            #     self.gcode += self.doformat(p.toolchange_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
+            #     self.gcode += self.doformat(p.toolchange_code, x=self.xy_toolchange[0], y=self.xy_toolchange[1])
             # else:
             #     self.gcode += self.doformat(p.toolchange_code)
             self.gcode += self.doformat(p.toolchange_code)
@@ -5302,7 +5552,7 @@ class CNCjob(Geometry):
 
         self.feedrate = float(feedrate) if feedrate else None
 
-        self.feedrate_z = float(feedrate_z) if feedrate_z else None
+        self.z_feedrate = float(feedrate_z) if feedrate_z else None
 
         self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
 
@@ -5314,20 +5564,20 @@ class CNCjob(Geometry):
 
         self.startz = float(startz) if startz else None
 
-        self.endz = float(endz) if endz else None
+        self.z_end = float(endz) if endz else None
 
-        self.depthpercut = float(depthpercut) if depthpercut else None
+        self.z_depthpercut = float(depthpercut) if depthpercut else None
 
         self.multidepth = multidepth
 
-        self.toolchangez = float(toolchangez) if toolchangez else None
+        self.z_toolchange = float(toolchangez) if toolchangez else None
 
         try:
             if toolchangexy == '':
-                self.toolchange_xy = None
+                self.xy_toolchange = None
             else:
-                self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
-                if len(self.toolchange_xy) < 2:
+                self.xy_toolchange = [float(eval(a)) for a in toolchangexy.split(",")]
+                if len(self.xy_toolchange) < 2:
                     self.app.inform.emit("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
                                          "in the format (x, y) \nbut now there is only one value, not two. ")
                     return 'fail'
@@ -5393,7 +5643,7 @@ class CNCjob(Geometry):
 
         if toolchange:
             # if "line_xyz" in self.pp_geometry_name:
-            #     self.gcode += self.doformat(p.toolchange_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
+            #     self.gcode += self.doformat(p.toolchange_code, x=self.xy_toolchange[0], y=self.xy_toolchange[1])
             # else:
             #     self.gcode += self.doformat(p.toolchange_code)
             self.gcode += self.doformat(p.toolchange_code)
@@ -5560,11 +5810,11 @@ class CNCjob(Geometry):
             gcode += self.doformat(p.rapid_code, x=path[0][0], y=path[0][1])  # Move to first point
 
             # Move down to cutting depth
-            gcode += self.doformat(p.feedrate_z_code)
+            gcode += self.doformat(p.z_feedrate_code)
             gcode += self.doformat(p.down_z_start_code)
             gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
             gcode += self.doformat(p.dwell_fwd_code)
-            gcode += self.doformat(p.feedrate_z_dispense_code)
+            gcode += self.doformat(p.z_feedrate_dispense_code)
             gcode += self.doformat(p.lift_z_dispense_code)
             gcode += self.doformat(p.feedrate_xy_code)
 
@@ -5578,12 +5828,12 @@ class CNCjob(Geometry):
             gcode += self.doformat(p.down_z_stop_code)
             gcode += self.doformat(p.spindle_off_code)
             gcode += self.doformat(p.dwell_rev_code)
-            gcode += self.doformat(p.feedrate_z_code)
+            gcode += self.doformat(p.z_feedrate_code)
             gcode += self.doformat(p.lift_code)
         elif type(geometry) == Point:
             gcode += self.doformat(p.linear_code, x=path[0][0], y=path[0][1])  # Move to first point
 
-            gcode += self.doformat(p.feedrate_z_dispense_code)
+            gcode += self.doformat(p.z_feedrate_dispense_code)
             gcode += self.doformat(p.down_z_start_code)
             gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
             gcode += self.doformat(p.dwell_fwd_code)
@@ -5594,7 +5844,7 @@ class CNCjob(Geometry):
             gcode += self.doformat(p.spindle_off_code)
             gcode += self.doformat(p.down_z_stop_code)
             gcode += self.doformat(p.dwell_rev_code)
-            gcode += self.doformat(p.feedrate_z_code)
+            gcode += self.doformat(p.z_feedrate_code)
             gcode += self.doformat(p.lift_code)
         return gcode
 
@@ -5627,17 +5877,17 @@ class CNCjob(Geometry):
         else:
             z_cut = Decimal(self.z_cut).quantize(Decimal('0.000000001'))
 
-        if self.depthpercut is None:
-            self.depthpercut = z_cut
-        elif not isinstance(self.depthpercut, Decimal):
-            self.depthpercut = Decimal(self.depthpercut).quantize(Decimal('0.000000001'))
+        if self.z_depthpercut is None:
+            self.z_depthpercut = z_cut
+        elif not isinstance(self.z_depthpercut, Decimal):
+            self.z_depthpercut = Decimal(self.z_depthpercut).quantize(Decimal('0.000000001'))
 
         depth = 0
         reverse = False
         while depth > z_cut:
 
             # Increase depth. Limit to z_cut.
-            depth -= self.depthpercut
+            depth -= self.z_depthpercut
             if depth < z_cut:
                 depth = z_cut
 
@@ -5887,7 +6137,7 @@ class CNCjob(Geometry):
     #             ax.add_patch(patch)
     #
     #     return fig
-        
+
     def plot2(self, tooldia=None, dpi=75, margin=0.1, gcode_parsed=None,
               color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]},
               alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, obj=None, visible=False, kind='all'):
@@ -6035,7 +6285,7 @@ class CNCjob(Geometry):
             feedrate = self.feedrate
 
         if feedrate_z is None:
-            feedrate_z = self.feedrate_z
+            feedrate_z = self.z_feedrate
 
         if feedrate_rapid is None:
             feedrate_rapid = self.feedrate_rapid
@@ -6060,7 +6310,7 @@ class CNCjob(Geometry):
         # Move down to cutting depth
         if down:
             # Different feedrate for vertical cut?
-            gcode += self.doformat(p.feedrate_z_code)
+            gcode += self.doformat(p.z_feedrate_code)
             # gcode += self.doformat(p.feedrate_code)
             gcode += self.doformat(p.down_code, x=path[0][0], y=path[0][1], z_cut=z_cut)
             gcode += self.doformat(p.feedrate_code, feedrate=feedrate)
@@ -6105,7 +6355,7 @@ class CNCjob(Geometry):
             feedrate = self.feedrate
 
         if feedrate_z is None:
-            feedrate_z = self.feedrate_z
+            feedrate_z = self.z_feedrate
 
         if feedrate_rapid is None:
             feedrate_rapid = self.feedrate_rapid
@@ -6128,8 +6378,8 @@ class CNCjob(Geometry):
         # Move down to cutting depth
         if down:
             # Different feedrate for vertical cut?
-            if self.feedrate_z is not None:
-                gcode += self.doformat(p.feedrate_z_code)
+            if self.z_feedrate is not None:
+                gcode += self.doformat(p.z_feedrate_code)
                 # gcode += self.doformat(p.feedrate_code)
                 gcode += self.doformat(p.down_code, x=path[0][0], y=path[0][1], z_cut=z_cut)
                 gcode += self.doformat(p.feedrate_code, feedrate=feedrate)
@@ -6157,8 +6407,8 @@ class CNCjob(Geometry):
         p = self.pp_geometry
         gcode += self.doformat(p.linear_code, x=path[0][0], y=path[0][1])  # Move to first point
 
-        if self.feedrate_z is not None:
-            gcode += self.doformat(p.feedrate_z_code)
+        if self.z_feedrate is not None:
+            gcode += self.doformat(p.z_feedrate_code)
             gcode += self.doformat(p.down_code, x=path[0][0], y=path[0][1], z_cut = self.z_cut)
             gcode += self.doformat(p.feedrate_code)
         else:
@@ -6315,7 +6565,7 @@ class CNCjob(Geometry):
             temp_gcode = ''
             header_start = False
             header_stop = False
-            units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+            units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
             lines = StringIO(g)
             for line in lines:

+ 11 - 5
flatcamTools/ToolCalculators.py

@@ -225,12 +225,18 @@ class ToolCalculator(FlatCAMTool):
     def run(self):
         self.app.report_usage("ToolCalculators()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "Calc. Tool")
 
@@ -238,7 +244,7 @@ class ToolCalculator(FlatCAMTool):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
 
     def set_tool_ui(self):
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         ## Initialize form
         self.mm_entry.set_value('0')

+ 433 - 112
flatcamTools/ToolCutOut.py

@@ -1,15 +1,20 @@
 from FlatCAMTool import FlatCAMTool
 from ObjectCollection import *
 from FlatCAMApp import *
+from shapely.geometry import box
 
 
 class CutOut(FlatCAMTool):
 
     toolName = "Cutout PCB"
+    gapFinished = pyqtSignal()
 
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)
 
+        self.app = app
+        self.canvas = app.plotcanvas
+
         ## Title
         title_label = QtWidgets.QLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
@@ -37,13 +42,14 @@ class CutOut(FlatCAMTool):
         # self.type_obj_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
 
-        self.type_obj_combo_label = QtWidgets.QLabel("Object Type:")
+        self.type_obj_combo_label = QtWidgets.QLabel("Obj 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.setFixedWidth(60)
         form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
 
         ## Object to be cutout
@@ -81,21 +87,13 @@ class CutOut(FlatCAMTool):
         self.gapsize = FCEntry()
         self.gapsize_label = QtWidgets.QLabel("Gap size:")
         self.gapsize_label.setToolTip(
-            "The size of the gaps in the cutout\n"
+            "The size of the bridge gaps in the cutout\n"
             "used to keep the board connected to\n"
             "the surrounding material (the one \n"
             "from which the PCB is cutout)."
         )
         form_layout.addRow(self.gapsize_label, self.gapsize)
 
-        ## Title2
-        title_ff_label = QtWidgets.QLabel("<font size=4><b>FreeForm Cutout</b></font>")
-        self.layout.addWidget(title_ff_label)
-
-        ## Form Layout
-        form_layout_2 = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout_2)
-
         # How gaps wil be rendered:
         # lr    - left + right
         # tb    - top + bottom
@@ -104,10 +102,21 @@ class CutOut(FlatCAMTool):
         # 2tb   - 2*top + 2*bottom
         # 8     - 2*left + 2*right +2*top + 2*bottom
 
+        ## Title2
+        title_param_label = QtWidgets.QLabel("<font size=4><b>A. Automatic Bridge Gaps</b></font>")
+        title_param_label.setToolTip(
+            "This section handle creation of automatic bridge gaps."
+        )
+        self.layout.addWidget(title_param_label)
+
+        ## Form Layout
+        form_layout_2 = QtWidgets.QFormLayout()
+        self.layout.addLayout(form_layout_2)
+
         # Gaps
-        gaps_ff_label = QtWidgets.QLabel('Gaps FF:      ')
-        gaps_ff_label.setToolTip(
-            "Number of gaps used for the FreeForm cutout.\n"
+        gaps_label = QtWidgets.QLabel('Gaps:')
+        gaps_label.setToolTip(
+            "Number of gaps used for the Automatic cutout.\n"
             "There can be maximum 8 bridges/gaps.\n"
             "The choices are:\n"
             "- lr    - left + right\n"
@@ -117,76 +126,137 @@ class CutOut(FlatCAMTool):
             "- 2tb  - 2*top + 2*bottom\n"
             "- 8     - 2*left + 2*right +2*top + 2*bottom"
         )
+        gaps_label.setFixedWidth(60)
 
         self.gaps = FCComboBox()
         gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
         for it in gaps_items:
             self.gaps.addItem(it)
             self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
-        form_layout_2.addRow(gaps_ff_label, self.gaps)
+        form_layout_2.addRow(gaps_label, self.gaps)
 
         ## Buttons
         hlay = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay)
 
+        title_ff_label = QtWidgets.QLabel("<b>FreeForm:</b>")
+        title_ff_label.setToolTip(
+            "The cutout shape can be of ny shape.\n"
+            "Useful when the PCB has a non-rectangular shape."
+        )
+        hlay.addWidget(title_ff_label)
+
         hlay.addStretch()
-        self.ff_cutout_object_btn = QtWidgets.QPushButton("  FreeForm Cutout Object ")
+
+        self.ff_cutout_object_btn = QtWidgets.QPushButton("Generate Geo")
         self.ff_cutout_object_btn.setToolTip(
             "Cutout the selected object.\n"
-            "The cutout shape can be any shape.\n"
-            "Useful when the PCB has a non-rectangular shape.\n"
-            "But if the object to be cutout is of Gerber Type,\n"
-            "it needs to be an outline of the actual board shape."
+            "The cutout shape can be of any shape.\n"
+            "Useful when the PCB has a non-rectangular shape."
         )
         hlay.addWidget(self.ff_cutout_object_btn)
 
-        ## Title3
-        title_rct_label = QtWidgets.QLabel("<font size=4><b>Rectangular Cutout</b></font>")
-        self.layout.addWidget(title_rct_label)
-
-        ## Form Layout
-        form_layout_3 = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout_3)
-
-        gapslabel_rect = QtWidgets.QLabel('Type of gaps:')
-        gapslabel_rect.setToolTip(
-            "Where to place the gaps:\n"
-            "- one gap Top / one gap Bottom\n"
-            "- one gap Left / one gap Right\n"
-            "- one gap on each of the 4 sides."
-        )
-        self.gaps_rect_radio = RadioSet([{'label': '2(T/B)', 'value': 'TB'},
-                                    {'label': '2(L/R)', 'value': 'LR'},
-                                    {'label': '4', 'value': '4'}])
-        form_layout_3.addRow(gapslabel_rect, self.gaps_rect_radio)
-
         hlay2 = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay2)
 
+        title_rct_label = QtWidgets.QLabel("<b>Rectangular:</b>")
+        title_rct_label.setToolTip(
+            "The resulting cutout shape is\n"
+            "always a rectangle shape and it will be\n"
+            "the bounding box of the Object."
+        )
+        hlay2.addWidget(title_rct_label)
+
         hlay2.addStretch()
-        self.rect_cutout_object_btn = QtWidgets.QPushButton("Rectangular Cutout Object")
+        self.rect_cutout_object_btn = QtWidgets.QPushButton("Generate Geo")
         self.rect_cutout_object_btn.setToolTip(
             "Cutout the selected object.\n"
             "The resulting cutout shape is\n"
-            "always of a rectangle form and it will be\n"
+            "always a rectangle shape and it will be\n"
             "the bounding box of the Object."
         )
         hlay2.addWidget(self.rect_cutout_object_btn)
 
-        self.layout.addStretch()
+        ## Title5
+        title_manual_label = QtWidgets.QLabel("<font size=4><b>B. Manual Bridge Gaps</b></font>")
+        title_manual_label.setToolTip(
+            "This section handle creation of manual bridge gaps.\n"
+            "This is done by mouse clicking on the perimeter of the\n"
+            "Geometry object that is used as a cutout object. "
+        )
+        self.layout.addWidget(title_manual_label)
 
-        ## Init GUI
-        # self.dia.set_value(1)
-        # self.margin.set_value(0)
-        # self.gapsize.set_value(1)
-        # self.gaps.set_value(4)
-        # self.gaps_rect_radio.set_value("4")
+        ## Form Layout
+        form_layout_3 = QtWidgets.QFormLayout()
+        self.layout.addLayout(form_layout_3)
 
-        ## Signals
-        self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
-        self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
+        ## Manual Geo Object
+        self.man_object_combo = QtWidgets.QComboBox()
+        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.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
+        self.man_object_label = QtWidgets.QLabel("Geo Obj:")
+        self.man_object_label.setToolTip(
+            "Geometry object used to create the manual cutout."
+        )
+        self.man_object_label.setFixedWidth(60)
+        # e_lab_0 = QtWidgets.QLabel('')
+
+        form_layout_3.addRow(self.man_object_label, self.man_object_combo)
+        # form_layout_3.addRow(e_lab_0)
+
+        hlay3 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay3)
+
+        self.man_geo_label = QtWidgets.QLabel("Manual Geo:")
+        self.man_geo_label.setToolTip(
+            "If the object to be cutout is a Gerber\n"
+            "first create a Geometry that surrounds it,\n"
+            "to be used as the cutout, if one doesn't exist yet.\n"
+            "Select the source Gerber file in the top object combobox."
+        )
+        hlay3.addWidget(self.man_geo_label)
+
+        hlay3.addStretch()
+        self.man_geo_creation_btn = QtWidgets.QPushButton("Generate Geo")
+        self.man_geo_creation_btn.setToolTip(
+            "If the object to be cutout is a Gerber\n"
+            "first create a Geometry that surrounds it,\n"
+            "to be used as the cutout, if one doesn't exist yet.\n"
+            "Select the source Gerber file in the top object combobox."
+        )
+        hlay3.addWidget(self.man_geo_creation_btn)
+
+        hlay4 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay4)
+
+        self.man_bridge_gaps_label = QtWidgets.QLabel("Manual Add Bridge Gaps:")
+        self.man_bridge_gaps_label.setToolTip(
+            "Use the left mouse button (LMB) click\n"
+            "to create a bridge gap to separate the PCB from\n"
+            "the surrounding material."
+        )
+        hlay4.addWidget(self.man_bridge_gaps_label)
+
+        hlay4.addStretch()
+        self.man_gaps_creation_btn = QtWidgets.QPushButton("Generate Gap")
+        self.man_gaps_creation_btn.setToolTip(
+            "Use the left mouse button (LMB) click\n"
+            "to create a bridge gap to separate the PCB from\n"
+            "the surrounding material.\n"
+            "The LMB click has to be done on the perimeter of\n"
+            "the Geometry object used as a cutout geometry."
+        )
+        hlay4.addWidget(self.man_gaps_creation_btn)
+
+        self.layout.addStretch()
+
+        self.cutting_gapsize = 0.0
+        self.cutting_dia = 0.0
+
+        # true if we want to repeat the gap without clicking again on the button
+        self.repeat_gap = False
 
     def on_type_obj_index_changed(self, index):
         obj_type = self.type_obj_combo.currentIndex()
@@ -196,12 +266,17 @@ class CutOut(FlatCAMTool):
     def run(self):
         self.app.report_usage("ToolCutOut()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "Cutout Tool")
 
@@ -215,7 +290,16 @@ class CutOut(FlatCAMTool):
         self.margin.set_value(float(self.app.defaults["tools_cutoutmargin"]))
         self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
         self.gaps.set_value(4)
-        self.gaps_rect_radio.set_value(str(self.app.defaults["tools_gaps_rect"]))
+
+        ## Signals
+        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.man_geo_creation_btn.clicked.connect(self.on_manual_geo)
+        self.man_gaps_creation_btn.clicked.connect(self.on_manual_gap_click)
+
+        self.gapFinished.connect(self.on_gap_finished)
 
     def on_freeform_cutout(self):
 
@@ -247,6 +331,11 @@ class CutOut(FlatCAMTool):
                                      "Add it and retry.")
                 return
 
+
+        if 0 in {dia}:
+            self.app.inform.emit("[WARNING_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
+            return "Tool Diameter is zero value. Change it to a positive integer."
+
         try:
             margin = float(self.margin.get_value())
         except ValueError:
@@ -275,10 +364,6 @@ class CutOut(FlatCAMTool):
             self.app.inform.emit("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry.")
             return
 
-        if 0 in {dia}:
-            self.app.inform.emit("[WARNING_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
-            return "Tool Diameter is zero value. Change it to a positive integer."
-
         if gaps not in ['LR', 'TB', '2LR', '2TB', '4', '8']:
             self.app.inform.emit("[WARNING_NOTCL] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
                                  "Fill in a correct value and retry. ")
@@ -303,21 +388,13 @@ class CutOut(FlatCAMTool):
             # rename the obj name so it can be identified as cutout
             cutout_obj.options["name"] += "_cutout"
         else:
-            cutout_obj.isolate(dia=dia, passes=1, overlap=1, combine=False, outname="_temp")
-            ext_obj = self.app.collection.get_by_name("_temp")
-
             def geo_init(geo_obj, app_obj):
-                geo_obj.solid_geometry = obj_exteriors
+                geo = cutout_obj.solid_geometry.convex_hull
+                geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
 
             outname = cutout_obj.options["name"] + "_cutout"
-
-            obj_exteriors = ext_obj.get_exteriors()
             self.app.new_object('geometry', outname, geo_init)
 
-            self.app.collection.set_all_inactive()
-            self.app.collection.set_active("_temp")
-            self.app.on_delete()
-
             cutout_obj = self.app.collection.get_by_name(outname)
 
         if gaps == '8' or gaps == '2LR':
@@ -364,6 +441,11 @@ class CutOut(FlatCAMTool):
         self.app.should_we_save = True
 
     def on_rectangular_cutout(self):
+
+        def subtract_rectangle(obj_, x0, y0, x1, y1):
+            pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
+            obj_.subtract_polygon(pts)
+
         name = self.obj_combo.currentText()
 
         # Get source object.
@@ -387,6 +469,10 @@ class CutOut(FlatCAMTool):
                                      "Add it and retry.")
                 return
 
+        if 0 in {dia}:
+            self.app.inform.emit("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
+            return "Tool Diameter is zero value. Change it to a positive integer."
+
         try:
             margin = float(self.margin.get_value())
         except ValueError:
@@ -410,14 +496,15 @@ class CutOut(FlatCAMTool):
                 return
 
         try:
-            gaps = self.gaps_rect_radio.get_value()
+            gaps = self.gaps.get_value()
         except TypeError:
             self.app.inform.emit("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry.")
             return
 
-        if 0 in {dia}:
-            self.app.inform.emit("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
-            return "Tool Diameter is zero value. Change it to a positive integer."
+        if gaps not in ['LR', 'TB', '2LR', '2TB', '4', '8']:
+            self.app.inform.emit("[WARNING_NOTCL] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
+                                 "Fill in a correct value and retry. ")
+            return
 
         if cutout_obj.multigeo is True:
             self.app.inform.emit("[ERROR]Cutout operation cannot be done on a multi-geo Geometry.\n"
@@ -425,45 +512,279 @@ class CutOut(FlatCAMTool):
                                  "and after that perform Cutout.")
             return
 
+        # Get min and max data for each object as we just cut rectangles across X or Y
+        xmin, ymin, xmax, ymax = cutout_obj.bounds()
+        geo = box(xmin, ymin, xmax, ymax)
+
+        px = 0.5 * (xmin + xmax) + margin
+        py = 0.5 * (ymin + ymax) + margin
+        lenghtx = (xmax - xmin) + (margin * 2)
+        lenghty = (ymax - ymin) + (margin * 2)
+
+        gapsize = gapsize / 2 + (dia / 2)
+
         def geo_init(geo_obj, app_obj):
-            real_margin = margin + (dia / 2)
-            real_gap_size = gapsize + dia
-
-            minx, miny, maxx, maxy = cutout_obj.bounds()
-            minx -= real_margin
-            maxx += real_margin
-            miny -= real_margin
-            maxy += real_margin
-            midx = 0.5 * (minx + maxx)
-            midy = 0.5 * (miny + maxy)
-            hgap = 0.5 * real_gap_size
-            pts = [[midx - hgap, maxy],
-                   [minx, maxy],
-                   [minx, midy + hgap],
-                   [minx, midy - hgap],
-                   [minx, miny],
-                   [midx - hgap, miny],
-                   [midx + hgap, miny],
-                   [maxx, miny],
-                   [maxx, midy - hgap],
-                   [maxx, midy + hgap],
-                   [maxx, maxy],
-                   [midx + hgap, maxy]]
-            cases = {"TB": [[pts[0], pts[1], pts[4], pts[5]],
-                            [pts[6], pts[7], pts[10], pts[11]]],
-                     "LR": [[pts[9], pts[10], pts[1], pts[2]],
-                            [pts[3], pts[4], pts[7], pts[8]]],
-                     "4": [[pts[0], pts[1], pts[2]],
-                           [pts[3], pts[4], pts[5]],
-                           [pts[6], pts[7], pts[8]],
-                           [pts[9], pts[10], pts[11]]]}
-            cuts = cases[gaps]
-            geo_obj.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
-
-        # TODO: Check for None
-        self.app.new_object("geometry", name + "_cutout", geo_init)
-        self.app.inform.emit("[success] Rectangular CutOut operation finished.")
+            geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+
+        outname = cutout_obj.options["name"] + "_cutout"
+        self.app.new_object('geometry', outname, geo_init)
+
+        cutout_obj = self.app.collection.get_by_name(outname)
+
+        if gaps == '8' or gaps == '2LR':
+            subtract_rectangle(cutout_obj,
+                               xmin - gapsize,  # botleft_x
+                               py - gapsize + lenghty / 4,  # botleft_y
+                               xmax + gapsize,  # topright_x
+                               py + gapsize + lenghty / 4)  # topright_y
+            subtract_rectangle(cutout_obj,
+                               xmin - gapsize,
+                               py - gapsize - lenghty / 4,
+                               xmax + gapsize,
+                               py + gapsize - lenghty / 4)
+
+        if gaps == '8' or gaps == '2TB':
+            subtract_rectangle(cutout_obj,
+                               px - gapsize + lenghtx / 4,
+                               ymin - gapsize,
+                               px + gapsize + lenghtx / 4,
+                               ymax + gapsize)
+            subtract_rectangle(cutout_obj,
+                               px - gapsize - lenghtx / 4,
+                               ymin - gapsize,
+                               px + gapsize - lenghtx / 4,
+                               ymax + gapsize)
+
+        if gaps == '4' or gaps == 'LR':
+            subtract_rectangle(cutout_obj,
+                               xmin - gapsize,
+                               py - gapsize,
+                               xmax + gapsize,
+                               py + gapsize)
+
+        if gaps == '4' or gaps == 'TB':
+            subtract_rectangle(cutout_obj,
+                               px - gapsize,
+                               ymin - gapsize,
+                               px + gapsize,
+                               ymax + gapsize)
+
+        cutout_obj.plot()
+        self.app.inform.emit("[success] 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):
+        self.app.inform.emit("Click on the selected geometry object perimeter to create a bridge gap ...")
+        self.app.geo_editor.tool_shape.enabled = True
+
+        try:
+            self.cutting_dia = float(self.dia.get_value())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                self.cutting_dia = float(self.dia.get_value().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
+                                     "Add it and retry.")
+                return
+
+        if 0 in {self.cutting_dia}:
+            self.app.inform.emit("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
+            return "Tool Diameter is zero value. Change it to a positive integer."
+
+        try:
+            self.cutting_gapsize = float(self.gapsize.get_value())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                self.cutting_gapsize = float(self.gapsize.get_value().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit("[WARNING_NOTCL] Gap size value is missing or wrong format. "
+                                     "Add it and retry.")
+                return
+
+        self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
+        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+        self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+        self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+        self.app.plotcanvas.vis_connect('key_press', self.on_key_press)
+        self.app.plotcanvas.vis_connect('mouse_move', self.on_mouse_move)
+        self.app.plotcanvas.vis_connect('mouse_release', self.doit)
+
+    # To be called after clicking on the plot.
+    def doit(self, event):
+        # do paint single only for left mouse clicks
+        if event.button == 1:
+            self.app.inform.emit("Making manual bridge gap...")
+            pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+            self.on_manual_cutout(click_pos=pos)
+
+            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
+            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
+            self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
+            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+
+            self.app.geo_editor.tool_shape.clear(update=True)
+            self.app.geo_editor.tool_shape.enabled = False
+            self.gapFinished.emit()
+
+    def on_manual_cutout(self, click_pos):
+        name = self.man_object_combo.currentText()
+
+        # Get source object.
+        try:
+            cutout_obj = self.app.collection.get_by_name(str(name))
+        except:
+            self.app.inform.emit("[ERROR_NOTCL]Could not retrieve Geoemtry object: %s" % name)
+            return "Could not retrieve object: %s" % name
+
+        if cutout_obj is None:
+            self.app.inform.emit("[ERROR_NOTCL]Geometry object for manual cutout not found: %s" % cutout_obj)
+            return
+
+        # use the snapped position as reference
+        snapped_pos = self.app.geo_editor.snap(click_pos[0], click_pos[1])
+
+        cut_poly = self.cutting_geo(pos=(snapped_pos[0], snapped_pos[1]))
+        cutout_obj.subtract_polygon(cut_poly)
+
+        cutout_obj.plot()
+        self.app.inform.emit("[success] Added manual Bridge Gap.")
+
+        self.app.should_we_save = True
+
+    def on_gap_finished(self):
+        # if CTRL key modifier is pressed then repeat the bridge gap cut
+        key_modifier = QtWidgets.QApplication.keyboardModifiers()
+        if key_modifier == Qt.ControlModifier:
+            self.on_manual_gap_click()
+
+    def on_manual_geo(self):
+        name = self.obj_combo.currentText()
+
+        # Get source object.
+        try:
+            cutout_obj = self.app.collection.get_by_name(str(name))
+        except:
+            self.app.inform.emit("[ERROR_NOTCL]Could not retrieve Gerber object: %s" % name)
+            return "Could not retrieve object: %s" % name
+
+        if cutout_obj is None:
+            self.app.inform.emit("[ERROR_NOTCL]There is no Gerber object selected for Cutout.\n"
+                                 "Select one and try again.")
+            return
+
+        if not isinstance(cutout_obj, FlatCAMGerber):
+            self.app.inform.emit("[ERROR_NOTCL]The selected object has to be of Gerber type.\n"
+                                 "Select a Gerber file and try again.")
+            return
+
+        try:
+            dia = float(self.dia.get_value())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                dia = float(self.dia.get_value().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
+                                     "Add it and retry.")
+                return
+
+        if 0 in {dia}:
+            self.app.inform.emit("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
+            return "Tool Diameter is zero value. Change it to a positive integer."
+
+        try:
+            margin = float(self.margin.get_value())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                margin = float(self.margin.get_value().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit("[WARNING_NOTCL] Margin value is missing or wrong format. "
+                                     "Add it and retry.")
+                return
+
+        def geo_init(geo_obj, app_obj):
+            geo = cutout_obj.solid_geometry.convex_hull
+            geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+
+        outname = cutout_obj.options["name"] + "_cutout"
+        self.app.new_object('geometry', outname, geo_init)
+
+    def cutting_geo(self, pos):
+        self.cutting_gapsize = self.cutting_gapsize / 2 + (self.cutting_dia / 2)
+        offset = self.cutting_gapsize / 2
+
+        # cutting area definition
+        orig_x = pos[0]
+        orig_y = pos[1]
+        xmin = orig_x - offset
+        ymin = orig_y - offset
+        xmax = orig_x + offset
+        ymax = orig_y + offset
+
+        cut_poly = box(xmin, ymin, xmax, ymax)
+        return cut_poly
+
+    def on_mouse_move(self, event):
+
+        self.app.on_mouse_move_over_plot(event=event)
+
+        pos = self.canvas.vispy_canvas.translate_coords(event.pos)
+        event.xdata, event.ydata = pos[0], pos[1]
+
+        try:
+            x = float(event.xdata)
+            y = float(event.ydata)
+        except TypeError:
+            return
+
+        snap_x, snap_y = self.app.geo_editor.snap(x, y)
+
+        geo = self.cutting_geo(pos=(snap_x, snap_y))
+
+        # Remove any previous utility shape
+        self.app.geo_editor.tool_shape.clear(update=True)
+        self.draw_utility_geometry(geo=geo)
+
+    def draw_utility_geometry(self, geo):
+        self.app.geo_editor.tool_shape.add(
+            shape=geo,
+            color=(self.app.defaults["global_draw_color"] + '80'),
+            update=False,
+            layer=0,
+            tolerance=None)
+        self.app.geo_editor.tool_shape.redraw()
+
+    def on_key_press(self, event):
+        # events out of the self.app.collection view (it's about Project Tab) are of type int
+        if type(event) is int:
+            key = event
+        # events from the GUI are of type QKeyEvent
+        elif type(event) == QtGui.QKeyEvent:
+            key = event.key()
+        # events from Vispy are of type KeyEvent
+        else:
+            key = event.key
+
+        # Escape = Deselect All
+        if key == QtCore.Qt.Key_Escape or key == 'Escape':
+            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
+            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
+            self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
+            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+
+            # Remove any previous utility shape
+            self.app.geo_editor.tool_shape.clear(update=True)
+            self.app.geo_editor.tool_shape.enabled = False
 
     def reset_fields(self):
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 10 - 4
flatcamTools/ToolDblSided.py

@@ -264,12 +264,18 @@ class DblSidedTool(FlatCAMTool):
     def run(self):
         self.app.report_usage("Tool2Sided()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "2-Sided Tool")
 

+ 10 - 4
flatcamTools/ToolFilm.py

@@ -166,12 +166,18 @@ class Film(FlatCAMTool):
     def run(self):
         self.app.report_usage("ToolFilm()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "Film Tool")
 

+ 10 - 4
flatcamTools/ToolImage.py

@@ -134,12 +134,18 @@ class ToolImage(FlatCAMTool):
     def run(self):
         self.app.report_usage("ToolImage()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "Image Tool")
 

+ 3 - 3
flatcamTools/ToolMeasurement.py

@@ -12,7 +12,7 @@ class Measurement(FlatCAMTool):
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)
 
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
 
         ## Title
         title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font><br>" % self.toolName)
@@ -173,7 +173,7 @@ class Measurement(FlatCAMTool):
 
         # Switch notebook to tool page
         self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
         self.show()
 
     def toggle(self):
@@ -210,7 +210,7 @@ class Measurement(FlatCAMTool):
         else:
             # ENABLE the Measuring TOOL
             self.active = True
-            self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower()
+            self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
 
             # we disconnect the mouse/key handlers from wherever the measurement tool was called
             if self.app.call_source == 'app':

+ 13 - 0
flatcamTools/ToolMove.py

@@ -122,8 +122,15 @@ class ToolMove(FlatCAMTool):
                             else:
                                 for sel_obj in obj_list:
 
+                                    # offset
                                     sel_obj.offset((dx, dy))
                                     sel_obj.plot()
+
+                                    try:
+                                        sel_obj.replotApertures.emit()
+                                    except:
+                                        pass
+
                                     # Update the object bounding box options
                                     a,b,c,d = sel_obj.bounds()
                                     sel_obj.options['xmin'] = a
@@ -233,6 +240,12 @@ class ToolMove(FlatCAMTool):
 
     def draw_shape(self, coords):
         self.sel_rect = Polygon(coords)
+        if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
+            self.sel_rect = self.sel_rect.buffer(-0.1)
+            self.sel_rect = self.sel_rect.buffer(0.2)
+        else:
+            self.sel_rect = self.sel_rect.buffer(-0.00393)
+            self.sel_rect = self.sel_rect.buffer(0.00787)
 
         blue_t = Color('blue')
         blue_t.alpha = 0.2

+ 12 - 6
flatcamTools/ToolNonCopperClear.py

@@ -246,12 +246,18 @@ class NonCopperClear(FlatCAMTool, Gerber):
     def run(self):
         self.app.report_usage("ToolNonCopperClear()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.build_ui()
         self.app.ui.notebook.setTabText(2, "NCC Tool")
@@ -339,13 +345,13 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.obj_name = ""
         self.ncc_obj = None
         self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
     def build_ui(self):
         self.ui_disconnect()
 
         # updated units
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         if self.units == "IN":
             self.addtool_entry.set_value(0.039)

+ 12 - 6
flatcamTools/ToolPaint.py

@@ -304,12 +304,18 @@ class ToolPaint(FlatCAMTool, Gerber):
     def run(self):
         self.app.report_usage("ToolPaint()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "Paint Tool")
 
@@ -347,7 +353,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.paintoverlap_entry.set_value(self.default_data["paintoverlap"])
 
         # updated units
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         if self.units == "IN":
             self.addtool_entry.set_value(0.039)
@@ -415,7 +421,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             pass
 
         # updated units
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         sorted_tools = []
         for k, v in self.paint_tools.items():

+ 10 - 4
flatcamTools/ToolPanelize.py

@@ -200,12 +200,18 @@ class Panelize(FlatCAMTool):
     def run(self):
         self.app.report_usage("ToolPanelize()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "Panel. Tool")
 

+ 12 - 6
flatcamTools/ToolProperties.py

@@ -55,9 +55,15 @@ class Properties(FlatCAMTool):
             return
         self.set_tool_ui()
 
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
 
         FlatCAMTool.run(self)
         self.properties()
@@ -120,10 +126,10 @@ class Properties(FlatCAMTool):
         width = abs(ymax - ymin)
 
         self.addChild(dims, ['Length:', '%.4f %s' % (
-            length, self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower())], True)
+            length, self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())], True)
         self.addChild(dims, ['Width:', '%.4f %s' % (
-            width, self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower())], True)
-        if self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower() == 'mm':
+            width, self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())], True)
+        if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() == 'mm':
             area = (length * width) / 100
             self.addChild(dims, ['Box Area:', '%.4f %s' % (area, 'cm2')], True)
         else:
@@ -136,7 +142,7 @@ class Properties(FlatCAMTool):
                            'in': 'Inch',
                            'mm': 'Metric'
                        }
-                       [str(self.app.ui.general_options_form.general_app_group.units_radio.get_value().lower())]], True)
+                       [str(self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())]], True)
 
         for option in obj.options:
             if option is 'name':
@@ -157,7 +163,7 @@ class Properties(FlatCAMTool):
                         printed_value = 'Present' if v else 'None'
                         self.addChild(geo_tool, [str(k), printed_value], True)
                     elif k == 'data':
-                        tool_data = self.addParent(geo_tool, str(k).capilalize(),
+                        tool_data = self.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)

+ 14 - 7
flatcamTools/ToolSolderPaste.py

@@ -414,13 +414,20 @@ class SolderPaste(FlatCAMTool):
     def run(self):
         self.app.report_usage("ToolSolderPaste()")
 
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.build_ui()
 
-        # if the splitter us hidden, display it
-        if self.app.ui.splitter.sizes()[0] == 0:
-            self.app.ui.splitter.setSizes([1, 1])
         self.app.ui.notebook.setTabText(2, "SolderPaste Tool")
 
     def install(self, icon=None, separator=None, **kwargs):
@@ -477,7 +484,7 @@ class SolderPaste(FlatCAMTool):
         self.name = ""
         self.obj = None
 
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         for name in list(self.app.postprocessors.keys()):
             # populate only with postprocessor files that start with 'Paste_'
@@ -495,7 +502,7 @@ class SolderPaste(FlatCAMTool):
         self.ui_disconnect()
 
         # updated units
-        self.units = self.app.ui.general_options_form.general_app_group.units_radio.get_value().upper()
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         sorted_tools = []
         for k, v in self.tooltable_tools.items():
@@ -997,7 +1004,7 @@ class SolderPaste(FlatCAMTool):
             geo_obj.special_group = 'solder_paste_tool'
 
             geo = LineString()
-            work_geo = []
+            work_geo = self.flat_geometry
             rest_geo = []
             tooluid = 1
 
@@ -1021,7 +1028,7 @@ class SolderPaste(FlatCAMTool):
                 # We get possible issues if we try to directly use the Polygons, due of possible the interiors,
                 # so we do a hack: get first the exterior in a form of LinearRings and then convert back to Polygon
                 # because intersection does not work on LinearRings
-                for g in self.flat_geometry:
+                for g in work_geo:
                     # for whatever reason intersection on LinearRings does not work so we convert back to Polygons
                     poly = Polygon(g)
                     x_min, y_min, x_max, y_max = poly.bounds

+ 10 - 4
flatcamTools/ToolTransform.py

@@ -365,12 +365,18 @@ class ToolTransform(FlatCAMTool):
     def run(self):
         self.app.report_usage("ToolTransform()")
 
-        FlatCAMTool.run(self)
-        self.set_tool_ui()
-
-        # if the splitter us hidden, display it
+        # 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:
+                    self.app.ui.splitter.setSizes([0, 1])
+            except AttributeError:
+                pass
+                
+        FlatCAMTool.run(self)
+        self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, "Transform Tool")
 

+ 11 - 11
postprocessors/Paste_1.py

@@ -59,13 +59,13 @@ class Paste_1(FlatCAMPostProc_Tools):
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_stop']))
 
     def toolchange_code(self, p):
-        toolchangez = float(p['z_toolchange'])
+        z_toolchange = float(p['z_toolchange'])
         toolchangexy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(float(p['toolC']), '.2f')
@@ -74,26 +74,26 @@ class Paste_1(FlatCAMPostProc_Tools):
 
         if toolchangexy is not None:
             gcode = """
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}
 T{tool}
 M6    
 (MSG, Change to Tool with Nozzle Dia = {toolC})
 M0
-""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".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(int(p.tool)),
            toolC=toolC_formatted)
 
         else:
             gcode = """
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
 M6    
 (MSG, Change to Tool with Nozzle Dia = {toolC})
 M0
-""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(int(p.tool)),
            toolC=toolC_formatted)
 
@@ -121,7 +121,7 @@ M0
     def feedrate_xy_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frxy'])))
 
-    def feedrate_z_code(self, p):
+    def z_feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz'])))
 
     def feedrate_z_dispense_code(self, p):

+ 29 - 29
postprocessors/Repetier.py

@@ -9,7 +9,7 @@ class Repetier(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -23,18 +23,18 @@ class Repetier(FlatCAMPostProc):
         gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += ';Feedrate_Z: ' + str(p['feedrate_z']) + 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':
             if p['multidepth'] is True:
-                gcode += ';DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + '\n'
+                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['toolchangez']) + units + '\n'
+        gcode += ';Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
 
         if coords_xy is not None:
             gcode += ';X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + '\n'
@@ -42,7 +42,7 @@ class Repetier(FlatCAMPostProc):
             gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
 
         gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
-        gcode += ';Z End: ' + str(p['endz']) + 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':
@@ -73,19 +73,19 @@ class Repetier(FlatCAMPostProc):
         return 'G1 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + " " + self.end_feedrate_code(p)
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -99,22 +99,22 @@ class Repetier(FlatCAMPostProc):
 
             if toolchangexy is not None:
                 gcode = """
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}                
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}                
 M84
 @pause Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
-""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".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)
             else:
                 gcode = """
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 M84
 @pause Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
-""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            t_drills=no_drills,
            toolC=toolC_formatted)
@@ -127,21 +127,21 @@ M84
         else:
             if toolchangexy is not None:
                 gcode = """
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}
 M84
 @pause Change to tool T{tool} with Tool Dia = {toolC}
-""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".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),
            toolC=toolC_formatted)
             else:
                 gcode = """
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 M84
 @pause Change to tool T{tool} with Tool Dia = {toolC}
-""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            toolC=toolC_formatted)
 
@@ -164,8 +164,8 @@ M84
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.end_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + " " + self.feedrate_rapid_code(p) + "\n")
+        coords_xy = p['xy_toolchange']
+        gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
         if coords_xy is not None:
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
@@ -178,8 +178,8 @@ M84
     def end_feedrate_code(self, p):
         return 'F' + self.feedrate_format %(p.fr_decimals, p.feedrate)
 
-    def feedrate_z_code(self, p):
-        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def feedrate_rapid_code(self, p):
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)

+ 6 - 6
postprocessors/Roland_MDX_20.py

@@ -72,9 +72,9 @@ class Roland_MDX_20(FlatCAMPostProc):
 
     def end_code(self, p):
         if p.units.upper() == 'IN':
-            z = p.endz / 25.4
+            z = p.z_end / 25.4
         else:
-            z = p.endz
+            z = p.z_end
         gcode = self.feedrate_rapid_code(p) + '\n'
         gcode += self.position_code(p).format(**p) + ',' + str(float(z * 40.0)) + ';'
         return gcode
@@ -89,13 +89,13 @@ class Roland_MDX_20(FlatCAMPostProc):
             fr_sec = 6
         return 'V' + str(self.feedrate_format % fr_sec) + ';'
 
-    def feedrate_z_code(self, p):
-        fr_sec = p.feedrate_z / 60
+    def z_feedrate_code(self, p):
+        fr_sec = p.z_feedrate / 60
 
         # valid feedrate for MDX20 is between 0.1mm/sec and 15mm/sec (6mm/min to 900mm/min)
-        if p.feedrate_z >= 900:
+        if p.z_feedrate >= 900:
             fr_sec = 15
-        if p.feedrate_z < 6:
+        if p.z_feedrate < 6:
             fr_sec = 6
         return 'V' + str(self.feedrate_format % fr_sec) + ';'
 

+ 21 - 50
postprocessors/Toolchange_Probe_general.py → postprocessors/Toolchange_Custom.py

@@ -1,14 +1,14 @@
 from FlatCAMPostProc import *
 
 
-class Toolchange_Probe_general(FlatCAMPostProc):
+class Toolchange_Custom(FlatCAMPostProc):
 
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -22,18 +22,18 @@ class Toolchange_Probe_general(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + 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':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
+                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['toolchangez']) + units + ')\n'
+        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
 
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
@@ -41,7 +41,7 @@ class Toolchange_Probe_general(FlatCAMPostProc):
             gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
-        gcode += '(Z End: ' + str(p['endz']) + 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':
@@ -71,19 +71,19 @@ class Toolchange_Probe_general(FlatCAMPostProc):
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -97,29 +97,13 @@ class Toolchange_Probe_general(FlatCAMPostProc):
 
             if toolchangexy is not None:
                 gcode = """
-M5
-G00 X{toolchangex} Y{toolchangey}                
-T{tool}
 M6
-(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0
-""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
+""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
+             y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
              tool=int(p.tool),
              t_drills=no_drills,
              toolC=toolC_formatted)
 
-            else:
-                gcode = """
-M5
-T{tool}
-M6
-(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0
-""".format(tool=int(p.tool),
-             t_drills=no_drills,
-             toolC=toolC_formatted)
-
             if f_plunge is True:
                 gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
             return gcode
@@ -127,24 +111,11 @@ M0
         else:
             if toolchangexy is not None:
                 gcode = """
-M5
-G00 X{toolchangex} Y{toolchangey}
-T{tool}
-M6    
-(MSG, Change to Tool Dia = {toolC})
-M0
-""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
+M6
+""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
+             y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
              tool=int(p.tool),
              toolC=toolC_formatted)
-            else:
-                gcode = """
-M5
-T{tool}
-M6    
-(MSG, Change to Tool Dia = {toolC})
-M0""".format(tool=int(p.tool),
-             toolC=toolC_formatted)
 
             if f_plunge is True:
                 gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
@@ -164,8 +135,8 @@ M0""".format(tool=int(p.tool),
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+        coords_xy = p['xy_toolchange']
+        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"
@@ -174,8 +145,8 @@ M0""".format(tool=int(p.tool),
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
-    def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def spindle_code(self, p):
         if p.spindlespeed:

+ 30 - 30
postprocessors/Toolchange_Probe_MACH3.py

@@ -8,7 +8,7 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -22,7 +22,7 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + 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'
@@ -30,11 +30,11 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
 
         if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
+                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['toolchangez']) + units + ')\n'
+        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
 
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
@@ -42,7 +42,7 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
             gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
-        gcode += '(Z End: ' + str(p['endz']) + 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'
 
@@ -61,7 +61,7 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
         gcode += 'G17\n'
         gcode += 'G94\n'
         gcode += '(MSG, WARNING: Make sure you do zero on all axis. ' \
-                 'For Z axis, since it will be probed, make a rough estimate and do a zero.)'
+                 'For Z axis, since it will be probed, make a rough estimate and do a zero.)\n'
         gcode += 'M0'
 
         return gcode
@@ -76,20 +76,20 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
 
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -106,8 +106,8 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
 M5             
 T{tool}
 M6
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE ||| Drills for this tool = {t_drills})
 M0
 F{feedrate_probe}
@@ -120,9 +120,9 @@ G92 Z0
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
-""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".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),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
@@ -136,7 +136,7 @@ M0
 M5
 T{tool}
 M6
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE ||| Drills for this tool = {t_drills})
 M0
 F{feedrate_probe}
@@ -149,7 +149,7 @@ G92 Z0
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
-""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
@@ -169,8 +169,8 @@ M0
 M5
 T{tool}
 M6
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE)
 M0
 F{feedrate_probe}
@@ -183,9 +183,9 @@ G92 Z0
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
-""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".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),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
@@ -198,7 +198,7 @@ M0
 M5
 T{tool}
 M6
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE)
 M0
 F{feedrate_probe}
@@ -211,7 +211,7 @@ G92 Z0
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
-""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
@@ -238,8 +238,8 @@ M0
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+        coords_xy = p['xy_toolchange']
+        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"
@@ -248,8 +248,8 @@ M0
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
-    def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def spindle_code(self, p):
         if p.spindlespeed:

+ 35 - 35
postprocessors/Toolchange_manual.py

@@ -8,7 +8,7 @@ class Toolchange_manual(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -22,24 +22,24 @@ class Toolchange_manual(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + 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':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
+                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['toolchangez']) + units + ')\n'
+        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], 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['endz']) + 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':
@@ -72,22 +72,22 @@ class Toolchange_manual(FlatCAMPostProc):
         return 'G01 Z' + self.coordinate_format % (p.coords_decimals, p.z_cut)
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
         # else:
-        #     toolchangex = p.oldx
-        #     toolchangey = p.oldy
+        #     x_toolchange = p.oldx
+        #     y_toolchange = p.oldy
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -102,20 +102,20 @@ class Toolchange_manual(FlatCAMPostProc):
             if toolchangexy is not None:
                 gcode = """
 M5
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool} 
-G00 X{toolchangex} Y{toolchangey} 
+G00 X{x_toolchange} Y{y_toolchange} 
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 M0
 G01 Z0
 (MSG, Adjust the tool T{tool} to touch the material and then tighten it slightly.)
 M0
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
-""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex),
-           toolchangey=self.coordinate_format%(p.coords_decimals, toolchangey),
-           toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+""".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)
@@ -123,18 +123,18 @@ M0
             else:
                 gcode = """
 M5 
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool} 
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 M0
 G01 Z0
 (MSG, Adjust the tool T{tool} to touch the material and then tighten it slightly.)
 M0
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
 """.format(
-           toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+           z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            t_drills=no_drills,
            toolC=toolC_formatted)
@@ -147,36 +147,36 @@ M0
             if toolchangexy is not None:
                 gcode = """
 M5
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
-G00 X{toolchangex}Y{toolchangey}    
+G00 X{x_toolchange}Y{y_toolchange}    
 (MSG, Change to Tool Dia = {toolC})
 M0
 G01 Z0
 (MSG, Adjust the tool T{tool} to touch the material and then tighten it slightly.)
 M0
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
-""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex),
-           toolchangey=self.coordinate_format%(p.coords_decimals, toolchangey),
-           toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+""".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),
            toolC=toolC_formatted)
             else:
                 gcode = """
 M5  
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
 (MSG, Change to Tool Dia = {toolC})
 M0
 G01 Z0
 (MSG, Adjust the tool T{tool} to touch the material and then tighten it slightly.)
 M0
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
-""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            toolC=toolC_formatted)
 
@@ -198,8 +198,8 @@ M0
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+        coords_xy = p['xy_toolchange']
+        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"
         else:
@@ -209,8 +209,8 @@ M0
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
-    def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def spindle_code(self,p):
         if p.spindlespeed:

+ 30 - 29
postprocessors/default.py

@@ -8,7 +8,7 @@ class default(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -22,18 +22,18 @@ class default(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + 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':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
+                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['toolchangez']) + units + ')\n'
+        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
 
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
@@ -41,7 +41,7 @@ class default(FlatCAMPostProc):
             gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
-        gcode += '(Z End: ' + str(p['endz']) + 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':
@@ -73,19 +73,19 @@ class default(FlatCAMPostProc):
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -100,25 +100,26 @@ class default(FlatCAMPostProc):
             if toolchangexy is not None:
                 gcode = """
 M5
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}                
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}                
 T{tool}
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0
+""".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)
             else:
                 gcode = """
 M5       
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              t_drills=no_drills,
              toolC=toolC_formatted)
@@ -130,24 +131,24 @@ M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchange
             if toolchangexy is not None:
                 gcode = """
 M5
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}
 T{tool}
 M6    
 (MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".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),
              toolC=toolC_formatted)
             else:
                 gcode = """
 M5
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
 M6    
 (MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              toolC=toolC_formatted)
 
@@ -169,8 +170,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+        coords_xy = p['xy_toolchange']
+        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"
@@ -179,8 +180,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
-    def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def spindle_code(self, p):
         if p.spindlespeed:

+ 29 - 29
postprocessors/grbl_11.py

@@ -8,7 +8,7 @@ class grbl_11(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -22,24 +22,24 @@ class grbl_11(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + 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':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
+                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['toolchangez']) + units + ')\n'
+        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], 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['endz']) + 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':
@@ -72,19 +72,19 @@ class grbl_11(FlatCAMPostProc):
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -99,25 +99,25 @@ class grbl_11(FlatCAMPostProc):
             if toolchangexy is not None:
                 gcode = """
 M5             
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}                
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}                
 T{tool}
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".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)
             else:
                 gcode = """
 M5             
-G00 Z{toolchangez}               
+G00 Z{z_toolchange}               
 T{tool}
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              t_drills=no_drills,
              toolC=toolC_formatted)
@@ -130,24 +130,24 @@ M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchange
             if toolchangexy is not None:
                 gcode = """
 M5             
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}                
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}                
 T{tool}
 M6
 (MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".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),
              toolC=toolC_formatted)
             else:
                 gcode = """
 M5             
-G00 Z{toolchangez}              
+G00 Z{z_toolchange}              
 T{tool}
 M6
 (MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              toolC=toolC_formatted)
 
@@ -170,8 +170,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
                ' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+        coords_xy = p['xy_toolchange']
+        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"
@@ -180,8 +180,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
-    def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def spindle_code(self,p):
         if p.spindlespeed:

+ 4 - 4
postprocessors/grbl_laser.py

@@ -68,8 +68,8 @@ class grbl_laser(FlatCAMPostProc):
                ' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+        coords_xy = p['xy_toolchange']
+        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"
@@ -78,8 +78,8 @@ class grbl_laser(FlatCAMPostProc):
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
-    def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def spindle_code(self, p):
         return ''

+ 1 - 1
postprocessors/hpgl.py

@@ -60,7 +60,7 @@ class hpgl(FlatCAMPostProc):
     def feedrate_code(self, p):
         return ''
 
-    def feedrate_z_code(self, p):
+    def z_feedrate_code(self, p):
         return ''
 
     def feedrate_rapid_code(self, p):

+ 34 - 34
postprocessors/line_xyz.py

@@ -8,7 +8,7 @@ class line_xyz(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -22,24 +22,24 @@ class line_xyz(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + 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':
             if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
+                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['toolchangez']) + units + ')\n'
+        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], 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['endz']) + 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':
@@ -80,26 +80,26 @@ class line_xyz(FlatCAMPostProc):
         return g
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        xy_toolchange = p.xy_toolchange
         f_plunge = p.f_plunge
         gcode = ''
 
-        if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+        if xy_toolchange is not None:
+            x_toolchange = xy_toolchange[0]
+            y_toolchange = xy_toolchange[1]
         else:
             if str(p['options']['type']) == 'Excellon':
-                toolchangex = p.oldx
-                toolchangey = p.oldy
+                x_toolchange = p.oldx
+                y_toolchange = p.oldy
             else:
-                toolchangex = p.x
-                toolchangey = p.y
+                x_toolchange = p.x
+                y_toolchange = p.y
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -112,40 +112,40 @@ class line_xyz(FlatCAMPostProc):
                     no_drills = i[2]
             gcode = """
 M5      
-G00 X{toolchangex} Y{toolchangey} Z{toolchangez}
+G00 X{x_toolchange} Y{x_toolchange} Z{z_toolchange}
 T{tool}
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".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)
 
             if f_plunge is True:
-                gcode += """\nG00 X{toolchangex} Y{toolchangey} Z{z_move}""".format(
-                    toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex),
-                    toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
+                gcode += """\nG00 X{x_toolchange} Y{x_toolchange} Z{z_move}""".format(
+                    x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchange),
+                    y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
                     z_move=self.coordinate_format % (p.coords_decimals, p.z_move))
             return gcode
         else:
             gcode = """
 M5
-G00 X{toolchangex} Y{toolchangey} Z{toolchangez}
+G00 X{x_toolchange} Y{x_toolchange} Z{z_toolchange}
 T{tool}
 M6    
 (MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".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),
              toolC=toolC_formatted)
 
             if f_plunge is True:
-                gcode += """\nG00 X{toolchangex} Y{toolchangey} Z{z_move}""".format(
-                    toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-                    toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
+                gcode += """\nG00 X{x_toolchange} Y{x_toolchange} Z{z_move}""".format(
+                    x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
+                    y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
                     z_move=self.coordinate_format % (p.coords_decimals, p.z_move))
             return gcode
 
@@ -170,19 +170,19 @@ M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex)
         return g
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         if coords_xy is not None:
             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)
-        g += ' Z' + self.coordinate_format % (p.coords_decimals, p.endz)
+        g += ' Z' + self.coordinate_format % (p.coords_decimals, p.z_end)
         return g
 
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
-    def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def spindle_code(self, p):
         if p.spindlespeed:

+ 29 - 29
postprocessors/marlin.py

@@ -9,7 +9,7 @@ class marlin(FlatCAMPostProc):
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -23,18 +23,18 @@ class marlin(FlatCAMPostProc):
         gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
 
         if str(p['options']['type']) == 'Geometry':
-            gcode += ';Feedrate_Z: ' + str(p['feedrate_z']) + 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':
             if p['multidepth'] is True:
-                gcode += ';DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + '\n'
+                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['toolchangez']) + units + '\n'
+        gcode += ';Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
 
         if coords_xy is not None:
             gcode += ';X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + '\n'
@@ -42,7 +42,7 @@ class marlin(FlatCAMPostProc):
             gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
 
         gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
-        gcode += ';Z End: ' + str(p['endz']) + 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':
@@ -73,19 +73,19 @@ class marlin(FlatCAMPostProc):
         return 'G1 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + " " + self.end_feedrate_code(p)
 
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
+        z_toolchange = p.z_toolchange
+        toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         gcode = ''
 
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
         no_drills = 1
 
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
@@ -100,25 +100,25 @@ class marlin(FlatCAMPostProc):
             if toolchangexy is not None:
                 gcode = """
 M5
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}                
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}                
 T{tool}
 M6
 ;MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".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)
             else:
                 gcode = """
 M5
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 T{tool}
 M6
 ;MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
-M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              t_drills=no_drills,
              toolC=toolC_formatted)
@@ -131,24 +131,24 @@ M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchange
             if toolchangexy is not None:
                 gcode = """
 M5
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}
 T{tool}
 M6    
 ;MSG, Change to Tool Dia = {toolC}
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+M0""".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),
              toolC=toolC_formatted)
             else:
                 gcode = """
 M5
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 T{tool}
 M6    
 ;MSG, Change to Tool Dia = {toolC}
-M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              toolC=toolC_formatted)
 
@@ -170,8 +170,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.end_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['toolchange_xy']
-        gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + " " + self.feedrate_rapid_code(p) + "\n")
+        coords_xy = p['xy_toolchange']
+        gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
         if coords_xy is not None:
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
@@ -184,8 +184,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
     def end_feedrate_code(self, p):
         return 'F' + self.feedrate_format %(p.fr_decimals, p.feedrate)
 
-    def feedrate_z_code(self, p):
-        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+    def z_feedrate_code(self, p):
+        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
     def feedrate_rapid_code(self, p):
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)

BIN
share/code_editor32.png


+ 146 - 0
tests/frameless_window.py

@@ -0,0 +1,146 @@
+import sys
+from PyQt5 import QtGui, QtWidgets
+from PyQt5 import QtCore
+from PyQt5.QtCore import Qt
+
+class TitleBar(QtWidgets.QDialog):
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent)
+        self.setWindowFlags(Qt.FramelessWindowHint);
+        css = """
+        QWidget{
+            Background: #AA00AA;
+            color:white;
+            font:12px bold;
+            font-weight:bold;
+            border-radius: 1px;
+            height: 11px;
+        }
+        QDialog{
+            Background-image:url('img/titlebar bg.png');
+            font-size:12px;
+            color: black;
+
+        }
+        QToolButton{
+            Background:#AA00AA;
+            font-size:11px;
+        }
+        QToolButton:hover{
+            Background: #FF00FF;
+            font-size:11px;
+        }
+        """
+        self.setAutoFillBackground(True)
+        self.setBackgroundRole(QtGui.QPalette.Highlight)
+        self.setStyleSheet(css)
+        self.minimize=QtWidgets.QToolButton(self)
+        self.minimize.setIcon(QtGui.QIcon('img/min.png'))
+        self.maximize=QtWidgets.QToolButton(self);
+        self.maximize.setIcon(QtGui.QIcon('img/max.png'))
+        close=QtWidgets.QToolButton(self)
+        close.setIcon(QtGui.QIcon('img/close.png'))
+        self.minimize.setMinimumHeight(10)
+        close.setMinimumHeight(10)
+        self.maximize.setMinimumHeight(10)
+        label=QtWidgets.QLabel(self)
+        label.setText("Abracadabra")
+        self.setWindowTitle("Alhambra")
+        hbox=QtWidgets.QHBoxLayout(self)
+        hbox.addWidget(label)
+        hbox.addWidget(self.minimize)
+        hbox.addWidget(self.maximize)
+        hbox.addWidget(close)
+        hbox.insertStretch(1,500)
+        hbox.setSpacing(0)
+        self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Fixed)
+        self.maxNormal=False
+        close.clicked.connect(self.close)
+        self.minimize.clicked.connect(self.showSmall)
+        self.maximize.clicked.connect(self.showMaxRestore)
+
+    def showSmall(self):
+        box.showMinimized();
+
+    def showMaxRestore(self):
+        if(self.maxNormal):
+            box.showNormal();
+            self.maxNormal= False;
+            self.maximize.setIcon(QtGui.QIcon('img/max.png'));
+            print(1)
+        else:
+            box.showMaximized();
+            self.maxNormal=  True;
+            print(2)
+            self.maximize.setIcon(QtGui.QIcon('img/max2.png'));
+
+    def close(self):
+        box.close()
+
+    def mousePressEvent(self,event):
+        if event.button() == Qt.LeftButton:
+            box.moving = True; box.offset = event.pos()
+
+    def mouseMoveEvent(self,event):
+        if box.moving: box.move(event.globalPos()-box.offset)
+
+
+class Frame(QtWidgets.QFrame):
+    def __init__(self, parent=None):
+        QtWidgets.QFrame.__init__(self, parent)
+        self.m_mouse_down= False;
+        self.setFrameShape(QtWidgets.QFrame.StyledPanel)
+        css = """
+        QFrame{
+            Background:  #D700D7;
+            color:white;
+            font:13px ;
+            font-weight:bold;
+            }
+        """
+        self.setStyleSheet(css)
+        self.setWindowFlags(Qt.FramelessWindowHint)
+        self.setMouseTracking(True)
+        self.m_titleBar= TitleBar(self)
+        self.m_content= QtWidgets.QWidget(self)
+        vbox=QtWidgets.QVBoxLayout(self)
+        vbox.addWidget(self.m_titleBar)
+        vbox.setContentsMargins(0, 0, 0, 0)
+        vbox.setSpacing(0)
+        layout=QtWidgets.QVBoxLayout(self)
+        layout.addWidget(self.m_content)
+        layout.setContentsMargins(5, 5, 5, 5)
+        layout.setSpacing(0)
+        vbox.addLayout(layout)
+        # Allows you to access the content area of the frame
+        # where widgets and layouts can be added
+
+    def contentWidget(self):
+        return self.m_content
+
+    def titleBar(self):
+        return self.m_titleBar
+
+    def mousePressEvent(self,event):
+        self.m_old_pos = event.pos();
+        self.m_mouse_down = event.button()== Qt.LeftButton;
+
+    def mouseMoveEvent(self,event):
+        x=event.x()
+        y=event.y()
+
+    def mouseReleaseEvent(self,event):
+        m_mouse_down=False;
+
+if __name__ == '__main__':
+    app = QtWidgets.QApplication(sys.argv);
+    box = Frame()
+    box.move(60,60)
+    l=QtWidgets.QVBoxLayout(box.contentWidget());
+    l.setContentsMargins(0, 0,0 ,0)
+    edit=QtWidgets.QLabel("""I would've did anything for you to show you how much I adored you
+But it's over now, it's too late to save our loveJust promise me you'll think of me
+Every time you look up in the sky and see a star 'cuz I'm  your star.""");
+    l.addWidget(edit)
+    box.show()
+    app.exec_()

Некоторые файлы не были показаны из-за большого количества измененных файлов