Browse Source

Merged in test_beta_8.911 (pull request #135)

Test beta 8.911
Marius Stanciu 6 years ago
parent
commit
3d25ba2838

File diff suppressed because it is too large
+ 360 - 134
FlatCAMApp.py


+ 6 - 7
FlatCAMEditor.py

@@ -346,7 +346,7 @@ class TextInputTool(FlatCAMTool):
                     font_name=self.font_name,
                     font_name=self.font_name,
                     font_size=font_to_geo_size,
                     font_size=font_to_geo_size,
                     font_type=font_to_geo_type,
                     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):
     def font_family(self, font):
         self.text_input_entry.selectAll()
         self.text_input_entry.selectAll()
@@ -1483,7 +1483,7 @@ class TransformEditorTool(FlatCAMTool):
                 "[WARNING_NOTCL] Geometry shape rotate cancelled...")
                 "[WARNING_NOTCL] Geometry shape rotate cancelled...")
 
 
     def on_offx_key(self):
     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 ...",
         val_box = FCInputDialog(title="Offset on X axis ...",
                                 text=('Enter a distance Value (%s):' % str(units)),
                                 text=('Enter a distance Value (%s):' % str(units)),
@@ -1502,7 +1502,7 @@ class TransformEditorTool(FlatCAMTool):
                 "[WARNING_NOTCL] Geometry shape offset X cancelled...")
                 "[WARNING_NOTCL] Geometry shape offset X cancelled...")
 
 
     def on_offy_key(self):
     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 ...",
         val_box = FCInputDialog(title="Offset on Y axis ...",
                                 text=('Enter a distance Value (%s):' % str(units)),
                                 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))
         geo = self.active_tool.utility_geometry(data=(x, y))
 
 
         if isinstance(geo, DrawToolShape) and geo.geo is not None:
         if isinstance(geo, DrawToolShape) and geo.geo is not None:
-
             # Remove any previous utility shape
             # Remove any previous utility shape
             self.tool_shape.clear(update=True)
             self.tool_shape.clear(update=True)
             self.draw_utility_geometry(geo=geo)
             self.draw_utility_geometry(geo=geo)
@@ -4985,7 +4984,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.move_timer.setSingleShot(True)
         self.move_timer.setSingleShot(True)
 
 
         ## Current application units in Upper Case
         ## 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.key = None  # Currently pressed key
         self.modifiers = None
         self.modifiers = None
@@ -5059,7 +5058,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
     def set_ui(self):
     def set_ui(self):
         # updated units
         # 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.olddia_newdia.clear()
         self.tool2tooldia.clear()
         self.tool2tooldia.clear()
@@ -5099,7 +5098,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             pass
             pass
 
 
         # updated units
         # 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)
         # make a new name for the new Excellon object (the one with edited content)
         self.edited_obj_name = self.exc_obj.options['name']
         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.menufileopenproject = QtWidgets.QAction(QtGui.QIcon('share/folder16.png'), 'Open &Project ...', self)
         self.menufile_open.addAction(self.menufileopenproject)
         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
         # Recent
         self.recent = self.menufile.addMenu(QtGui.QIcon('share/recent_files.png'), "Recent files")
         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_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_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_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.addSeparator()
         self.menuview_toggle_fscreen = self.menuview.addAction(
         self.menuview_toggle_fscreen = self.menuview.addAction(
             QtGui.QIcon('share/fscreen32.png'), "&Toggle FullScreen\tALT+F10")
             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 = QtWidgets.QToolBar('File Toolbar')
         self.toolbarfile.setObjectName('File_TB')
         self.toolbarfile.setObjectName('File_TB')
         self.addToolBar(self.toolbarfile)
         self.addToolBar(self.toolbarfile)
+
         self.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
         self.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
         self.toolbargeo.setObjectName('Edit_TB')
         self.toolbargeo.setObjectName('Edit_TB')
         self.addToolBar(self.toolbargeo)
         self.addToolBar(self.toolbargeo)
+
         self.toolbarview = QtWidgets.QToolBar('View Toolbar')
         self.toolbarview = QtWidgets.QToolBar('View Toolbar')
         self.toolbarview.setObjectName('View_TB')
         self.toolbarview.setObjectName('View_TB')
         self.addToolBar(self.toolbarview)
         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 = QtWidgets.QToolBar('Tools Toolbar')
         self.toolbartools.setObjectName('Tools_TB')
         self.toolbartools.setObjectName('Tools_TB')
         self.addToolBar(self.toolbartools)
         self.addToolBar(self.toolbartools)
+
         self.exc_edit_toolbar = QtWidgets.QToolBar('Excellon Editor Toolbar')
         self.exc_edit_toolbar = QtWidgets.QToolBar('Excellon Editor Toolbar')
         self.exc_edit_toolbar.setObjectName('ExcEditor_TB')
         self.exc_edit_toolbar.setObjectName('ExcEditor_TB')
         self.addToolBar(self.exc_edit_toolbar)
         self.addToolBar(self.exc_edit_toolbar)
+
         self.geo_edit_toolbar = QtWidgets.QToolBar('Geometry Editor Toolbar')
         self.geo_edit_toolbar = QtWidgets.QToolBar('Geometry Editor Toolbar')
         self.geo_edit_toolbar.setObjectName('GeoEditor_TB')
         self.geo_edit_toolbar.setObjectName('GeoEditor_TB')
         self.addToolBar(self.geo_edit_toolbar)
         self.addToolBar(self.geo_edit_toolbar)
@@ -516,8 +534,23 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         # self.toolbarview.setVisible(False)
         # self.toolbarview.setVisible(False)
 
 
+        ### Shell Toolbar ###
+        self.shell_btn = self.toolbarshell.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line")
+
         ### Tools Toolbar ###
         ### 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 ###
         ### Drill Editor Toolbar ###
         self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), "Select")
         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.geometry_tab_lay.addWidget(self.geometry_scroll_area)
 
 
         self.cncjob_tab = QtWidgets.QWidget()
         self.cncjob_tab = QtWidgets.QWidget()
+        self.cncjob_tab.setObjectName("cncjob_tab")
         self.pref_tab_area.addTab(self.cncjob_tab, "CNC-JOB")
         self.pref_tab_area.addTab(self.cncjob_tab, "CNC-JOB")
         self.cncjob_tab_lay = QtWidgets.QVBoxLayout()
         self.cncjob_tab_lay = QtWidgets.QVBoxLayout()
         self.cncjob_tab_lay.setContentsMargins(2, 2, 2, 2)
         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 height="20"><strong>SHIFT+C</strong></td>
 			<td>&nbsp;Copy Obj_Name</td>
 			<td>&nbsp;Copy Obj_Name</td>
 		</tr>
 		</tr>
+		<tr height="20">
+			<td height="20"><strong>SHIFT+E</strong></td>
+			<td>&nbsp;Toggle Code Editor</td>
+		</tr>
 		<tr height="20">
 		<tr height="20">
 			<td height="20"><strong>SHIFT+G</strong></td>
 			<td height="20"><strong>SHIFT+G</strong></td>
 			<td>&nbsp;Toggle the axis</td>
 			<td>&nbsp;Toggle the axis</td>
@@ -1540,8 +1578,23 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         # self.toolbarview.setVisible(False)
         # self.toolbarview.setVisible(False)
 
 
+        ### Shell Toolbar ###
+        self.shell_btn = self.toolbarshell.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line")
+
         ### Tools Toolbar ###
         ### 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 ###
         ### Drill Editor Toolbar ###
         self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), "Select")
         self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), "Select")
@@ -1692,11 +1745,14 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 return
                 return
             elif modifiers == QtCore.Qt.ShiftModifier:
             elif modifiers == QtCore.Qt.ShiftModifier:
 
 
-                # Copy Object Name
                 # Copy Object Name
                 # Copy Object Name
                 if key == QtCore.Qt.Key_C:
                 if key == QtCore.Qt.Key_C:
                     self.app.on_copy_name()
                     self.app.on_copy_name()
 
 
+                # Toggle Code Editor
+                if key == QtCore.Qt.Key_E:
+                    self.app.on_toggle_code_editor()
+
                 # Toggle axis
                 # Toggle axis
                 if key == QtCore.Qt.Key_G:
                 if key == QtCore.Qt.Key_G:
                     if self.app.toggle_axis is False:
                     if self.app.toggle_axis is False:
@@ -1847,6 +1903,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 # Escape = Deselect All
                 # Escape = Deselect All
                 if key == QtCore.Qt.Key_Escape or key == 'Escape':
                 if key == QtCore.Qt.Key_Escape or key == 'Escape':
                     self.app.on_deselect_all()
                     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("")
                     self.app.inform.emit("")
 
 
                 # Space = Toggle Active/Inactive
                 # Space = Toggle Active/Inactive
@@ -1891,10 +1952,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
                 # Change Units
                 # Change Units
                 if key == QtCore.Qt.Key_Q:
                 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:
                     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()
                     self.app.on_toggle_units()
 
 
                 # Rotate Object by 90 degree CW
                 # Rotate Object by 90 degree CW
@@ -2515,10 +2576,14 @@ class GerberPreferencesUI(QtWidgets.QWidget):
         self.gerber_gen_group = GerberGenPrefGroupUI()
         self.gerber_gen_group = GerberGenPrefGroupUI()
         self.gerber_gen_group.setFixedWidth(250)
         self.gerber_gen_group.setFixedWidth(250)
         self.gerber_opt_group = GerberOptPrefGroupUI()
         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_gen_group)
         self.layout.addWidget(self.gerber_opt_group)
         self.layout.addWidget(self.gerber_opt_group)
+        self.layout.addWidget(self.gerber_adv_opt_group)
+
         self.layout.addStretch()
         self.layout.addStretch()
 
 
 
 
@@ -2639,9 +2704,13 @@ class CNCJobPreferencesUI(QtWidgets.QWidget):
         self.cncjob_gen_group.setFixedWidth(270)
         self.cncjob_gen_group.setFixedWidth(270)
         self.cncjob_opt_group = CNCJobOptPrefGroupUI()
         self.cncjob_opt_group = CNCJobOptPrefGroupUI()
         self.cncjob_opt_group.setFixedWidth(260)
         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_gen_group)
         self.layout.addWidget(self.cncjob_opt_group)
         self.layout.addWidget(self.cncjob_opt_group)
+        self.layout.addWidget(self.cncjob_adv_opt_group)
+
         self.layout.addStretch()
         self.layout.addStretch()
 
 
 
 
@@ -3027,6 +3096,7 @@ class GeneralGUISetGroupUI(OptionsGroupUI):
             del settings
             del settings
             self.app.inform.emit("[success] GUI settings deleted ...")
             self.app.inform.emit("[success] GUI settings deleted ...")
 
 
+
 class GeneralAppPrefGroupUI(OptionsGroupUI):
 class GeneralAppPrefGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
     def __init__(self, parent=None):
         super(GeneralAppPrefGroupUI, self).__init__(self)
         super(GeneralAppPrefGroupUI, self).__init__(self)
@@ -3227,26 +3297,27 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
 
 
         grid0 = QtWidgets.QGridLayout()
         grid0 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0)
         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
         # Solid CB
         self.solid_cb = FCCheckBox(label='Solid')
         self.solid_cb = FCCheckBox(label='Solid')
         self.solid_cb.setToolTip(
         self.solid_cb.setToolTip(
             "Solid color polygons."
             "Solid color polygons."
         )
         )
-        grid0.addWidget(self.solid_cb, 0, 1)
+        grid0.addWidget(self.solid_cb, 0, 0)
 
 
         # Multicolored CB
         # Multicolored CB
         self.multicolored_cb = FCCheckBox(label='M-Color')
         self.multicolored_cb = FCCheckBox(label='M-Color')
         self.multicolored_cb.setToolTip(
         self.multicolored_cb.setToolTip(
             "Draw polygons in different colors."
             "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
         # Number of circle steps for circular aperture linear approximation
         self.circle_steps_label = QtWidgets.QLabel("Circle Steps:")
         self.circle_steps_label = QtWidgets.QLabel("Circle Steps:")
@@ -3386,6 +3457,73 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
         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):
 class ExcellonGenPrefGroupUI(OptionsGroupUI):
 
 
     def __init__(self, parent=None):
     def __init__(self, parent=None):
@@ -4485,6 +4623,89 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
         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):
 class ToolsNCCPrefGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
     def __init__(self, parent=None):
         # OptionsGroupUI.__init__(self, "NCC Tool Options", parent=parent)
         # OptionsGroupUI.__init__(self, "NCC Tool Options", parent=parent)
@@ -4632,22 +4853,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         self.cutout_gap_entry = LengthEntry()
         self.cutout_gap_entry = LengthEntry()
         grid0.addWidget(self.cutout_gap_entry, 2, 1)
         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"
             "There can be maximum 8 bridges/gaps.\n"
             "The choices are:\n"
             "The choices are:\n"
             "- lr    - left + right\n"
             "- lr    - left + right\n"
@@ -4657,9 +4865,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
             "- 2tb  - 2*top + 2*bottom\n"
             "- 2tb  - 2*top + 2*bottom\n"
             "- 8     - 2*left + 2*right +2*top + 2*bottom"
             "- 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()
         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']
         gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
         for it in gaps_items:
         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 = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene)
         self.shapes = self.app.plotcanvas.new_shape_group()
         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.item = None  # Link with project view item
 
 
         self.muted_ui = False
         self.muted_ui = False
@@ -77,6 +79,9 @@ class FlatCAMObj(QtCore.QObject):
 
 
         self._drawing_tolerance = 0.01
         self._drawing_tolerance = 0.01
 
 
+        self.isHovering = False
+        self.notHovering = True
+
         # assert isinstance(self.ui, ObjectUI)
         # assert isinstance(self.ui, ObjectUI)
         # self.ui.name_entry.returnPressed.connect(self.on_name_activate)
         # self.ui.name_entry.returnPressed.connect(self.on_name_activate)
         # self.ui.offset_button.clicked.connect(self.on_offset_button_click)
         # 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)
             key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs)
         return key
         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
     @property
     def visible(self):
     def visible(self):
         return self.shapes.visible
         return self.shapes.visible
@@ -356,6 +368,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
     Represents Gerber code.
     Represents Gerber code.
     """
     """
     optionChanged = QtCore.pyqtSignal(str)
     optionChanged = QtCore.pyqtSignal(str)
+    replotApertures = QtCore.pyqtSignal()
+
     ui_type = GerberObjectUI
     ui_type = GerberObjectUI
 
 
     @staticmethod
     @staticmethod
@@ -373,26 +387,39 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             grb_final.solid_geometry = []
             grb_final.solid_geometry = []
             grb_final.follow_geometry = []
             grb_final.follow_geometry = []
 
 
+        if not grb_final.apertures:
+            grb_final.apertures = {}
+
         if type(grb_final.solid_geometry) is not list:
         if type(grb_final.solid_geometry) is not list:
             grb_final.solid_geometry = [grb_final.solid_geometry]
             grb_final.solid_geometry = [grb_final.solid_geometry]
             grb_final.follow_geometry = [grb_final.follow_geometry]
             grb_final.follow_geometry = [grb_final.follow_geometry]
 
 
         for grb in grb_list:
         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
             # Expand lists
             if type(grb) is list:
             if type(grb) is list:
                 FlatCAMGerber.merge(grb, grb_final)
                 FlatCAMGerber.merge(grb, grb_final)
             else:   # If not list, just append
             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:
                 for geos in grb.solid_geometry:
                     grb_final.solid_geometry.append(geos)
                     grb_final.solid_geometry.append(geos)
                     grb_final.follow_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.solid_geometry = MultiPolygon(grb_final.solid_geometry)
         grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
         grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
 
 
@@ -416,7 +443,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             "noncoppermargin": 0.0,
             "noncoppermargin": 0.0,
             "noncopperrounded": False,
             "noncopperrounded": False,
             "bboxmargin": 0.0,
             "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)
         # 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
         # store the source file here
         self.source_file = ""
         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
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # Always append to it because it carries contents
@@ -455,9 +480,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return: None
         :return: None
         """
         """
         FlatCAMObj.set_ui(self, ui)
         FlatCAMObj.set_ui(self, ui)
-
         FlatCAMApp.App.log.debug("FlatCAMGerber.set_ui()")
         FlatCAMApp.App.log.debug("FlatCAMGerber.set_ui()")
 
 
+        self.replotApertures.connect(self.on_mark_cb_click_table)
+
         self.form_fields.update({
         self.form_fields.update({
             "plot": self.ui.plot_cb,
             "plot": self.ui.plot_cb,
             "multicolored": self.ui.multicolored_cb,
             "multicolored": self.ui.multicolored_cb,
@@ -470,7 +496,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             "noncoppermargin": self.ui.noncopper_margin_entry,
             "noncoppermargin": self.ui.noncopper_margin_entry,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "bboxmargin": self.ui.bbmargin_entry,
             "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
         # 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.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.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.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
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
         if self.app.defaults["global_app_level"] == 'b':
@@ -499,10 +532,14 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.ui.milling_type_radio.hide()
             self.ui.milling_type_radio.hide()
             self.ui.generate_ext_iso_button.hide()
             self.ui.generate_ext_iso_button.hide()
             self.ui.generate_int_iso_button.hide()
             self.ui.generate_int_iso_button.hide()
+            self.ui.follow_cb.hide()
 
 
         else:
         else:
             self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
             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()
         self.build_ui()
 
 
     def build_ui(self):
     def build_ui(self):
@@ -514,9 +551,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         except:
         except:
             pass
             pass
 
 
-        n = len(self.apertures) + len(self.aperture_macros)
-        self.ui.apertures_table.setRowCount(n)
-
         self.apertures_row = 0
         self.apertures_row = 0
         aper_no = self.apertures_row + 1
         aper_no = self.apertures_row + 1
         sort = []
         sort = []
@@ -529,6 +563,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             sort.append(k)
             sort.append(k)
         sorted_macros = sorted(sort)
         sorted_macros = sorted(sort)
 
 
+        n = len(sorted_apertures) + len(sorted_macros)
+        self.ui.apertures_table.setRowCount(n)
+
         for ap_code in sorted_apertures:
         for ap_code in sorted_apertures:
             ap_code = str(ap_code)
             ap_code = str(ap_code)
 
 
@@ -570,17 +607,17 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                 ap_size_item = QtWidgets.QTableWidgetItem('')
                 ap_size_item = QtWidgets.QTableWidgetItem('')
             ap_size_item.setFlags(QtCore.Qt.ItemIsEnabled)
             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, 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, 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, 3, ap_size_item)   # Aperture Dimensions
             self.ui.apertures_table.setItem(self.apertures_row, 4, ap_dim_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
             self.apertures_row += 1
 
 
@@ -596,19 +633,18 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             ap_type_item = QtWidgets.QTableWidgetItem('AM')
             ap_type_item = QtWidgets.QTableWidgetItem('AM')
             ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
             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, 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, 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.apertures_row += 1
 
 
         self.ui.apertures_table.selectColumn(0)
         self.ui.apertures_table.selectColumn(0)
-        #
         self.ui.apertures_table.resizeColumnsToContents()
         self.ui.apertures_table.resizeColumnsToContents()
         self.ui.apertures_table.resizeRowsToContents()
         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.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
         self.ui.apertures_table.setSortingEnabled(False)
         self.ui.apertures_table.setSortingEnabled(False)
         self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight())
         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):
     def on_generatenoncopper_button_click(self, *args):
         self.app.report_usage("gerber_on_generatenoncopper_button")
         self.app.report_usage("gerber_on_generatenoncopper_button")
@@ -895,6 +955,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         if self.muted_ui:
         if self.muted_ui:
             return
             return
         self.read_form_item('plot')
         self.read_form_item('plot')
+        self.plot()
 
 
     def on_solid_cb_click(self, *args):
     def on_solid_cb_click(self, *args):
         if self.muted_ui:
         if self.muted_ui:
@@ -916,8 +977,156 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
     def on_aperture_table_visibility_change(self):
     def on_aperture_table_visibility_change(self):
         if self.ui.aperture_table_visibility_cb.isChecked():
         if self.ui.aperture_table_visibility_cb.isChecked():
             self.ui.apertures_table.setVisible(True)
             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:
         else:
             self.ui.apertures_table.setVisible(False)
             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):
     def convert_units(self, units):
         """
         """
@@ -1005,70 +1214,149 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.shapes.clear(update=True)
             self.shapes.clear(update=True)
 
 
     # experimental plot() when the solid_geometry is stored in the self.apertures
     # 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):
     def serialize(self):
         return {
         return {
@@ -2288,7 +2576,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             if self.options["solid"]:
             if self.options["solid"]:
                 for tool in self.tools:
                 for tool in self.tools:
                     for geo in self.tools[tool]['solid_geometry']:
                     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)
                                        layer=2)
             else:
             else:
                 for tool in self.tools:
                 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))
             self.ui.geo_tools_table.setCurrentItem(self.ui.geo_tools_table.item(row, 0))
 
 
     def export_dxf(self):
     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
         dwg = None
         try:
         try:
             dwg = ezdxf.new('R2010')
             dwg = ezdxf.new('R2010')
@@ -4382,6 +4671,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry'])
                 self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry'])
         else:
         else:
             self.solid_geometry=scale_recursion(self.solid_geometry)
             self.solid_geometry=scale_recursion(self.solid_geometry)
+
         self.app.inform.emit("[success]Geometry Scale done.")
         self.app.inform.emit("[success]Geometry Scale done.")
 
 
     def offset(self, vect):
     def offset(self, vect):
@@ -4651,7 +4941,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             "prepend": "",
             "prepend": "",
             "dwell": False,
             "dwell": False,
             "dwelltime": 1,
             "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), \
         assert isinstance(self.ui, CNCObjectUI), \
             "Expected a CNCObjectUI, got %s" % type(self.ui)
             "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({
         self.form_fields.update({
             "plot": self.ui.plot_cb,
             "plot": self.ui.plot_cb,
             # "tooldia": self.ui.tooldia_entry,
             # "tooldia": self.ui.tooldia_entry,
             "append": self.ui.append_text,
             "append": self.ui.append_text,
             "prepend": self.ui.prepend_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
         # Fill form fields only on object create
@@ -4877,21 +5175,30 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         if self.app.defaults["global_app_level"] == 'b':
         if self.app.defaults["global_app_level"] == 'b':
             self.ui.level.setText('<span style="color:green;"><b>Basic</b></span>')
             self.ui.level.setText('<span style="color:green;"><b>Basic</b></span>')
 
 
+            self.ui.cnc_frame.hide()
         else:
         else:
             self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
             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.updateplot_button.clicked.connect(self.on_updateplot_button_click)
         self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_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.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)
         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):
     def ui_connect(self):
         for row in range(self.ui.cnc_tools_table.rowCount()):
         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.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)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
 
 
-
     def ui_disconnect(self):
     def ui_disconnect(self):
         for row in range(self.ui.cnc_tools_table.rowCount()):
         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)
             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
             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(self.gcode)
         lines = StringIO(g)
         lines = StringIO(g)
 
 
@@ -5168,6 +5488,29 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         else:
         else:
             return lines
             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=''):
     def get_gcode(self, preamble='', postamble=''):
         #we need this to be able get_gcode separatelly for shell command export_gcode
         #we need this to be able get_gcode separatelly for shell command export_gcode
         return preamble + '\n' + self.gcode + "\n" + postamble
         return preamble + '\n' + self.gcode + "\n" + postamble

+ 1 - 1
FlatCAMPostProc.py

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

+ 6 - 3
GUIElements.py

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

+ 0 - 233
ObjectCollection.py

@@ -263,239 +263,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
     def has_promises(self):
     def has_promises(self):
         return len(self.promises) > 0
         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):
     def on_mouse_down(self, event):
         FlatCAMApp.App.log.debug("Mouse button pressed on list")
         FlatCAMApp.App.log.debug("Mouse button pressed on list")
 
 

+ 211 - 35
ObjectUI.py

@@ -1,7 +1,8 @@
 import sys
 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, \
 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
 from camlib import Excellon
 
 
 
 
@@ -127,6 +128,8 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(grid0)
         self.custom_box.addLayout(grid0)
 
 
         self.plot_options_label = QtWidgets.QLabel("<b>Plot Options:</b>")
         self.plot_options_label = QtWidgets.QLabel("<b>Plot Options:</b>")
+        self.plot_options_label.setFixedWidth(90)
+
         grid0.addWidget(self.plot_options_label, 0, 0)
         grid0.addWidget(self.plot_options_label, 0, 0)
 
 
         # Solid CB
         # Solid CB
@@ -145,6 +148,14 @@ class GerberObjectUI(ObjectUI):
         self.multicolored_cb.setFixedWidth(55)
         self.multicolored_cb.setFixedWidth(55)
         grid0.addWidget(self.multicolored_cb, 0, 2)
         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
         ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
         self.custom_box.addLayout(self.name_hlay)
@@ -158,30 +169,43 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(hlay_plot)
         self.custom_box.addLayout(hlay_plot)
 
 
         #### Gerber Apertures ####
         #### 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(
         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)
         hlay_plot.addWidget(self.apertures_table_label)
 
 
         # Aperture Table Visibility CB
         # Aperture Table Visibility CB
         self.aperture_table_visibility_cb = FCCheckBox()
         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.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.apertures_table = FCTable()
         self.custom_box.addWidget(self.apertures_table)
         self.custom_box.addWidget(self.apertures_table)
 
 
         self.apertures_table.setColumnCount(6)
         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.setSortingEnabled(False)
 
 
         self.apertures_table.horizontalHeaderItem(0).setToolTip(
         self.apertures_table.horizontalHeaderItem(0).setToolTip(
@@ -197,16 +221,82 @@ class GerberObjectUI(ObjectUI):
             " - (width, height) for R, O type.\n"
             " - (width, height) for R, O type.\n"
             " - (dia, nVertices) for P type")
             " - (dia, nVertices) for P type")
         self.apertures_table.horizontalHeaderItem(5).setToolTip(
         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
         # start with apertures table hidden
         self.apertures_table.setVisible(False)
         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
         # Isolation Routing
         self.isolation_routing_label = QtWidgets.QLabel("<b>Isolation Routing:</b>")
         self.isolation_routing_label = QtWidgets.QLabel("<b>Isolation Routing:</b>")
@@ -226,6 +316,7 @@ class GerberObjectUI(ObjectUI):
             "feature, use a negative value for\n"
             "feature, use a negative value for\n"
             "this parameter."
             "this parameter."
         )
         )
+        tdlabel.setFixedWidth(90)
         grid1.addWidget(tdlabel, 0, 0)
         grid1.addWidget(tdlabel, 0, 0)
         self.iso_tool_dia_entry = LengthEntry()
         self.iso_tool_dia_entry = LengthEntry()
         grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
         grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
@@ -235,6 +326,7 @@ class GerberObjectUI(ObjectUI):
             "Width of the isolation gap in\n"
             "Width of the isolation gap in\n"
             "number (integer) of tool widths."
             "number (integer) of tool widths."
         )
         )
+        passlabel.setFixedWidth(90)
         grid1.addWidget(passlabel, 1, 0)
         grid1.addWidget(passlabel, 1, 0)
         self.iso_width_entry = IntEntry()
         self.iso_width_entry = IntEntry()
         grid1.addWidget(self.iso_width_entry, 1, 1)
         grid1.addWidget(self.iso_width_entry, 1, 1)
@@ -245,6 +337,7 @@ class GerberObjectUI(ObjectUI):
             "Example:\n"
             "Example:\n"
             "A value here of 0.25 means an overlap of 25% from the tool diameter found above."
             "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)
         grid1.addWidget(overlabel, 2, 0)
         self.iso_overlap_entry = FloatEntry()
         self.iso_overlap_entry = FloatEntry()
         grid1.addWidget(self.iso_overlap_entry, 2, 1)
         grid1.addWidget(self.iso_overlap_entry, 2, 1)
@@ -295,7 +388,16 @@ class GerberObjectUI(ObjectUI):
         hlay_1 = QtWidgets.QHBoxLayout()
         hlay_1 = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(hlay_1)
         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 = QtWidgets.QPushButton('Ext Geo')
         self.generate_ext_iso_button.setToolTip(
         self.generate_ext_iso_button.setToolTip(
@@ -303,7 +405,7 @@ class GerberObjectUI(ObjectUI):
             "for isolation routing containing\n"
             "for isolation routing containing\n"
             "only the exteriors geometry."
             "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)
         hlay_1.addWidget(self.generate_ext_iso_button)
 
 
         self.generate_int_iso_button = QtWidgets.QPushButton('Int Geo')
         self.generate_int_iso_button = QtWidgets.QPushButton('Int Geo')
@@ -312,18 +414,9 @@ class GerberObjectUI(ObjectUI):
             "for isolation routing containing\n"
             "for isolation routing containing\n"
             "only the interiors geometry."
             "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)
         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
         # 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"
         # 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,
         self.ois_iso = OptionalInputSection(self.follow_cb,
@@ -333,11 +426,12 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(grid2)
         self.custom_box.addLayout(grid2)
 
 
         ## Clear non-copper regions
         ## 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(
         self.clearcopper_label.setToolTip(
             "Create a Geometry object with\n"
             "Create a Geometry object with\n"
             "toolpaths to cut all non-copper regions."
             "toolpaths to cut all non-copper regions."
         )
         )
+        self.clearcopper_label.setFixedWidth(90)
         grid2.addWidget(self.clearcopper_label, 0, 0)
         grid2.addWidget(self.clearcopper_label, 0, 0)
 
 
         self.generate_ncc_button = QtWidgets.QPushButton('NCC Tool')
         self.generate_ncc_button = QtWidgets.QPushButton('NCC Tool')
@@ -385,15 +479,17 @@ class GerberObjectUI(ObjectUI):
             "objects with this minimum\n"
             "objects with this minimum\n"
             "distance."
             "distance."
         )
         )
+        bmlabel.setFixedWidth(90)
         grid4.addWidget(bmlabel, 0, 0)
         grid4.addWidget(bmlabel, 0, 0)
         self.noncopper_margin_entry = LengthEntry()
         self.noncopper_margin_entry = LengthEntry()
         grid4.addWidget(self.noncopper_margin_entry, 0, 1)
         grid4.addWidget(self.noncopper_margin_entry, 0, 1)
 
 
         # Rounded corners
         # Rounded corners
-        self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners")
+        self.noncopper_rounded_cb = FCCheckBox(label="Rounded Geo")
         self.noncopper_rounded_cb.setToolTip(
         self.noncopper_rounded_cb.setToolTip(
             "Resulting geometry will have rounded corners."
             "Resulting geometry will have rounded corners."
         )
         )
+        self.noncopper_rounded_cb.setFixedWidth(90)
         grid4.addWidget(self.noncopper_rounded_cb, 1, 0)
         grid4.addWidget(self.noncopper_rounded_cb, 1, 0)
 
 
         self.generate_noncopper_button = QtWidgets.QPushButton('Generate Geo')
         self.generate_noncopper_button = QtWidgets.QPushButton('Generate Geo')
@@ -415,17 +511,19 @@ class GerberObjectUI(ObjectUI):
             "Distance of the edges of the box\n"
             "Distance of the edges of the box\n"
             "to the nearest polygon."
             "to the nearest polygon."
         )
         )
+        bbmargin.setFixedWidth(90)
         grid5.addWidget(bbmargin, 0, 0)
         grid5.addWidget(bbmargin, 0, 0)
         self.bbmargin_entry = LengthEntry()
         self.bbmargin_entry = LengthEntry()
         grid5.addWidget(self.bbmargin_entry, 0, 1)
         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(
         self.bbrounded_cb.setToolTip(
             "If the bounding box is \n"
             "If the bounding box is \n"
             "to have rounded corners\n"
             "to have rounded corners\n"
             "their radius is equal to\n"
             "their radius is equal to\n"
             "the margin."
             "the margin."
         )
         )
+        self.bbrounded_cb.setFixedWidth(90)
         grid5.addWidget(self.bbrounded_cb, 1, 0)
         grid5.addWidget(self.bbrounded_cb, 1, 0)
 
 
         self.generate_bb_button = QtWidgets.QPushButton('Generate Geo')
         self.generate_bb_button = QtWidgets.QPushButton('Generate Geo')
@@ -1324,7 +1422,7 @@ class CNCObjectUI(ObjectUI):
         )
         )
         self.custom_box.addWidget(self.export_gcode_label)
         self.custom_box.addWidget(self.export_gcode_label)
 
 
-        # Prepend text to Gerber
+        # Prepend text to GCode
         prependlabel = QtWidgets.QLabel('Prepend to CNC Code:')
         prependlabel = QtWidgets.QLabel('Prepend to CNC Code:')
         prependlabel.setToolTip(
         prependlabel.setToolTip(
             "Type here any G-Code commands you would\n"
             "Type here any G-Code commands you would\n"
@@ -1335,7 +1433,7 @@ class CNCObjectUI(ObjectUI):
         self.prepend_text = FCTextArea()
         self.prepend_text = FCTextArea()
         self.custom_box.addWidget(self.prepend_text)
         self.custom_box.addWidget(self.prepend_text)
 
 
-        # Append text to Gerber
+        # Append text to GCode
         appendlabel = QtWidgets.QLabel('Append to CNC Code')
         appendlabel = QtWidgets.QLabel('Append to CNC Code')
         appendlabel.setToolTip(
         appendlabel.setToolTip(
             "Type here any G-Code commands you would\n"
             "Type here any G-Code commands you would\n"
@@ -1347,6 +1445,84 @@ class CNCObjectUI(ObjectUI):
         self.append_text = FCTextArea()
         self.append_text = FCTextArea()
         self.custom_box.addWidget(self.append_text)
         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 = QtWidgets.QHBoxLayout()
         h_lay.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         h_lay.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.custom_box.addLayout(h_lay)
         self.custom_box.addLayout(h_lay)

+ 5 - 3
PlotCanvas.py

@@ -65,7 +65,7 @@ class PlotCanvas(QtCore.QObject):
         self.draw_workspace()
         self.draw_workspace()
 
 
         # if self.app.defaults['global_workspace'] is True:
         # 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.wkspace_t = Line(pos=)
 
 
         self.shape_collections = []
         self.shape_collections = []
@@ -92,7 +92,7 @@ class PlotCanvas(QtCore.QObject):
         a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)])
         a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)])
         a3l_mm = np.array([(0, 0), (420, 0), (420, 297), (0, 297)])
         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':
             if self.app.defaults['global_workspaceT'] == 'A4P':
                 a = a4p_mm
                 a = a4p_mm
             elif self.app.defaults['global_workspaceT'] == 'A4L':
             elif self.app.defaults['global_workspaceT'] == 'A4L':
@@ -165,7 +165,9 @@ class PlotCanvas(QtCore.QObject):
         """
         """
         self.vispy_canvas.view.camera.zoom(factor, center)
         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)
         return ShapeGroup(self.shape_collection)
 
 
     def new_shape_collection(self, **kwargs):
     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
 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
 - 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
 import rasterio
 from rasterio.features import shapes
 from rasterio.features import shapes
 
 
+from copy import deepcopy
+
 # TODO: Commented for FlatCAM packaging with cx_freeze
 # TODO: Commented for FlatCAM packaging with cx_freeze
 
 
 from xml.dom.minidom import parseString as parse_xml_string
 from xml.dom.minidom import parseString as parse_xml_string
@@ -1856,7 +1858,8 @@ class Gerber (Geometry):
     +-----------+-----------------------------------+
     +-----------+-----------------------------------+
     | others    | Depend on ``type``                |
     | others    | Depend on ``type``                |
     +-----------+-----------------------------------+
     +-----------+-----------------------------------+
-
+    | solid_geometry      | (list)                  |
+    +-----------+-----------------------------------+
     * ``aperture_macros`` (dictionary): Are predefined geometrical structures
     * ``aperture_macros`` (dictionary): Are predefined geometrical structures
       that can be instantiated with different parameters in an aperture
       that can be instantiated with different parameters in an aperture
       definition. See ``apertures`` above. The key is the name of the macro,
       definition. See ``apertures`` above. The key is the name of the macro,
@@ -1900,12 +1903,6 @@ class Gerber (Geometry):
         # Initialize parent
         # Initialize parent
         Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
         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
         # Number format
         self.int_digits = 3
         self.int_digits = 3
         """Number of integer digits in Gerber numbers. Used during parsing."""
         """Number of integer digits in Gerber numbers. Used during parsing."""
@@ -1918,14 +1915,31 @@ class Gerber (Geometry):
         """
         """
 
 
         ## Gerber elements ##
         ## 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 = {}
         self.apertures = {}
 
 
         # Aperture Macros
         # Aperture Macros
         self.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 = ''
         self.source_file = ''
 
 
         # Attributes to be included in serialization
         # Attributes to be included in serialization
@@ -2139,9 +2153,6 @@ class Gerber (Geometry):
         :param glines: Gerber code as list of strings, each element being
         :param glines: Gerber code as list of strings, each element being
             one line of the source file.
             one line of the source file.
         :type glines: list
         :type glines: list
-        :param follow: If true, will not create polygons, just lines
-            following the gerber path.
-        :type follow: bool
         :return: None
         :return: None
         :rtype: None
         :rtype: None
         """
         """
@@ -2221,8 +2232,10 @@ class Gerber (Geometry):
                 # buffer, then adds or subtracts accordingly.
                 # buffer, then adds or subtracts accordingly.
                 match = self.lpol_re.search(gline)
                 match = self.lpol_re.search(gline)
                 if match:
                 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 ----
                         # --- Buffered ----
                         width = self.apertures[last_path_aperture]["size"]
                         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))
                         geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                         if not geo.is_empty:
                         if not geo.is_empty:
                             poly_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)
 
 
                         path = [path[-1]]
                         path = [path[-1]]
 
 
@@ -2241,17 +2259,17 @@ class Gerber (Geometry):
                     # TODO: Remove when bug fixed
                     # TODO: Remove when bug fixed
                     if len(poly_buffer) > 0:
                     if len(poly_buffer) > 0:
                         if current_polarity == 'D':
                         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))
                             self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
 
 
                         else:
                         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 = []
                         poly_buffer = []
 
 
-                    current_polarity = match.group(1)
+                    current_polarity = new_polarity
                     continue
                     continue
 
 
                 ### Number format
                 ### Number format
@@ -2397,6 +2415,11 @@ class Gerber (Geometry):
                                 int(self.steps_per_circle))
                                 int(self.steps_per_circle))
                             if not flash.is_empty:
                             if not flash.is_empty:
                                 poly_buffer.append(flash)
                                 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:
                         except IndexError:
                             log.warning("Line %d: %s -> Nothing there to flash!" % (line_num, gline))
                             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))
                             geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                             if not geo.is_empty:
                             if not geo.is_empty:
                                 poly_buffer.append(geo)
                                 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]]
                             path = [path[-1]]
 
 
@@ -2454,6 +2482,11 @@ class Gerber (Geometry):
                         geo = LineString(path).buffer(width/1.999, int(self.steps_per_circle / 4))
                         geo = LineString(path).buffer(width/1.999, int(self.steps_per_circle / 4))
                         if not geo.is_empty:
                         if not geo.is_empty:
                             poly_buffer.append(geo)
                             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]]
                         path = [path[-1]]
 
 
@@ -2471,6 +2504,11 @@ class Gerber (Geometry):
                             if not geo.is_empty:
                             if not geo.is_empty:
                                 follow_buffer.append(geo)
                                 follow_buffer.append(geo)
                                 poly_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
                             continue
 
 
                     # Only one path defines region?
                     # Only one path defines region?
@@ -2497,9 +2535,29 @@ class Gerber (Geometry):
                     region = Polygon(path)
                     region = Polygon(path)
                     if not region.is_valid:
                     if not region.is_valid:
                         region = region.buffer(0, int(self.steps_per_circle / 4))
                         region = region.buffer(0, int(self.steps_per_circle / 4))
+
                     if not region.is_empty:
                     if not region.is_empty:
                         poly_buffer.append(region)
                         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
                     path = [[current_x, current_y]]  # Start new path
                     continue
                     continue
 
 
@@ -2566,7 +2624,14 @@ class Gerber (Geometry):
                                         miny = min(path[0][1], path[1][1]) - height / 2
                                         miny = min(path[0][1], path[1][1]) - height / 2
                                         maxy = max(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))
                                         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:
                                 except:
                                     pass
                                     pass
                             last_path_aperture = current_aperture
                             last_path_aperture = current_aperture
@@ -2596,6 +2661,7 @@ class Gerber (Geometry):
                                 elem = [linear_x, linear_y]
                                 elem = [linear_x, linear_y]
                                 if elem != path[-1]:
                                 if elem != path[-1]:
                                     path.append([linear_x, linear_y])
                                     path.append([linear_x, linear_y])
+
                                 try:
                                 try:
                                     geo = Polygon(path)
                                     geo = Polygon(path)
                                 except ValueError:
                                 except ValueError:
@@ -2613,8 +2679,18 @@ class Gerber (Geometry):
                                 if self.apertures[last_path_aperture]["type"] != 'R':
                                 if self.apertures[last_path_aperture]["type"] != 'R':
                                     if not geo.is_empty:
                                     if not geo.is_empty:
                                         poly_buffer.append(geo)
                                         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:
                             except:
                                 poly_buffer.append(geo)
                                 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 or linear_y are None, ignore those
                         if linear_x is not None and linear_y is not None:
                         if linear_x is not None and linear_y is not None:
@@ -2635,7 +2711,7 @@ class Gerber (Geometry):
                             geo = LineString(path)
                             geo = LineString(path)
                             if not geo.is_empty:
                             if not geo.is_empty:
                                 try:
                                 try:
-                                    if self.apertures[current_aperture]["type"] != 'R':
+                                    if self.apertures[last_path_aperture]["type"] != 'R':
                                         follow_buffer.append(geo)
                                         follow_buffer.append(geo)
                                 except:
                                 except:
                                     follow_buffer.append(geo)
                                     follow_buffer.append(geo)
@@ -2645,10 +2721,20 @@ class Gerber (Geometry):
                             geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                             geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                             if not geo.is_empty:
                             if not geo.is_empty:
                                 try:
                                 try:
-                                    if self.apertures[current_aperture]["type"] != 'R':
+                                    if self.apertures[last_path_aperture]["type"] != 'R':
                                         poly_buffer.append(geo)
                                         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:
                                 except:
                                     poly_buffer.append(geo)
                                     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
                         # Reset path starting point
                         path = [[linear_x, linear_y]]
                         path = [[linear_x, linear_y]]
@@ -2666,6 +2752,11 @@ class Gerber (Geometry):
                         )
                         )
                         if not flash.is_empty:
                         if not flash.is_empty:
                             poly_buffer.append(flash)
                             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
                     # 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
                     # 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))
                             buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
                             if not buffered.is_empty:
                             if not buffered.is_empty:
                                 poly_buffer.append(buffered)
                                 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_x = circular_x
                         current_y = circular_y
                         current_y = circular_y
@@ -2882,6 +2978,11 @@ class Gerber (Geometry):
                     geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                     geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
                     if not geo.is_empty:
                     if not geo.is_empty:
                         poly_buffer.append(geo)
                         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 ---
             # --- Apply buffer ---
 
 
@@ -3092,6 +3193,7 @@ class Gerber (Geometry):
         :type factor: float
         :type factor: float
         :rtype : None
         :rtype : None
         """
         """
+        log.debug("camlib.Gerber.scale()")
 
 
         try:
         try:
             xfactor = float(xfactor)
             xfactor = float(xfactor)
@@ -3125,8 +3227,18 @@ class Gerber (Geometry):
                                              yfactor, origin=(px, py))
                                              yfactor, origin=(px, py))
 
 
         self.solid_geometry = scale_geom(self.solid_geometry)
         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.")
         self.app.inform.emit("[success]Gerber Scale done.")
 
 
+
         ## solid_geometry ???
         ## solid_geometry ???
         #  It's a cascaded union of objects.
         #  It's a cascaded union of objects.
         # self.solid_geometry = affinity.scale(self.solid_geometry, factor,
         # self.solid_geometry = affinity.scale(self.solid_geometry, factor,
@@ -3171,8 +3283,16 @@ class Gerber (Geometry):
                 return affinity.translate(obj, xoff=dx, yoff=dy)
                 return affinity.translate(obj, xoff=dx, yoff=dy)
 
 
         ## Solid geometry
         ## Solid geometry
-        # self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
         self.solid_geometry = offset_geom(self.solid_geometry)
         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.")
         self.app.inform.emit("[success]Gerber Offset done.")
 
 
     def mirror(self, axis, point):
     def mirror(self, axis, point):
@@ -3210,6 +3330,14 @@ class Gerber (Geometry):
                 return affinity.scale(obj, xscale, yscale, origin=(px, py))
                 return affinity.scale(obj, xscale, yscale, origin=(px, py))
 
 
         self.solid_geometry = mirror_geom(self.solid_geometry)
         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.
         #  It's a cascaded union of objects.
         # self.solid_geometry = affinity.scale(self.solid_geometry,
         # 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))
                 return affinity.skew(obj, angle_x, angle_y, origin=(px, py))
 
 
         self.solid_geometry = skew_geom(self.solid_geometry)
         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))
         # self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y, origin=(px, py))
 
 
     def rotate(self, angle, point):
     def rotate(self, angle, point):
@@ -3266,7 +3401,14 @@ class Gerber (Geometry):
                 return affinity.rotate(obj, angle, origin=(px, py))
                 return affinity.rotate(obj, angle, origin=(px, py))
 
 
         self.solid_geometry = rotate_geom(self.solid_geometry)
         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))
         # self.solid_geometry = affinity.rotate(self.solid_geometry, angle, origin=(px, py))
 
 
 
 
@@ -3579,7 +3721,6 @@ class Excellon(Geometry):
                             headerless = True
                             headerless = True
                             try:
                             try:
                                 self.convert_units({"INCH": "IN", "METRIC": "MM"}[self.excellon_units])
                                 self.convert_units({"INCH": "IN", "METRIC": "MM"}[self.excellon_units])
-                                print("Units converted .............................. %s" % self.excellon_units)
                             except Exception as e:
                             except Exception as e:
                                 log.warning("Units could not be converted: %s" % str(e))
                                 log.warning("Units could not be converted: %s" % str(e))
 
 
@@ -4285,10 +4426,24 @@ class Excellon(Geometry):
         else:
         else:
             px, py = point
             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
         # Drills
         for drill in self.drills:
         for drill in self.drills:
             drill['point'] = affinity.scale(drill['point'], xfactor, yfactor, origin=(px, py))
             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
         # Slots
         for slot in self.slots:
         for slot in self.slots:
             slot['stop'] = affinity.scale(slot['stop'], xfactor, yfactor, origin=(px, py))
             slot['stop'] = affinity.scale(slot['stop'], xfactor, yfactor, origin=(px, py))
@@ -4307,10 +4462,23 @@ class Excellon(Geometry):
 
 
         dx, dy = vect
         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
         # Drills
         for drill in self.drills:
         for drill in self.drills:
             drill['point'] = affinity.translate(drill['point'], xoff=dx, yoff=dy)
             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
         # Slots
         for slot in self.slots:
         for slot in self.slots:
             slot['stop'] = affinity.translate(slot['stop'], xoff=dx, yoff=dy)
             slot['stop'] = affinity.translate(slot['stop'], xoff=dx, yoff=dy)
@@ -4331,11 +4499,24 @@ class Excellon(Geometry):
         px, py = point
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
         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
         # Modify data
         # Drills
         # Drills
         for drill in self.drills:
         for drill in self.drills:
             drill['point'] = affinity.scale(drill['point'], xscale, yscale, origin=(px, py))
             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
         # Slots
         for slot in self.slots:
         for slot in self.slots:
             slot['stop'] = affinity.scale(slot['stop'], xscale, yscale, origin=(px, py))
             slot['stop'] = affinity.scale(slot['stop'], xscale, yscale, origin=(px, py))
@@ -4365,16 +4546,30 @@ class Excellon(Geometry):
         if angle_y is None:
         if angle_y is None:
             angle_y = 0.0
             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:
         if point is None:
+            px, py = 0, 0
+
             # Drills
             # Drills
             for drill in self.drills:
             for drill in self.drills:
                 drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
                 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
             # Slots
             for slot in self.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:
         else:
             px, py = point
             px, py = point
             # Drills
             # Drills
@@ -4382,6 +4577,10 @@ class Excellon(Geometry):
                 drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
                 drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
                                                origin=(px, py))
                                                origin=(px, py))
 
 
+            # skew solid_geometry
+            for tool in self.tools:
+                self.tools[tool]['solid_geometry'] = skew_geom( self.tools[tool]['solid_geometry'])
+
             # Slots
             # Slots
             for slot in self.slots:
             for slot in self.slots:
                 slot['stop'] = affinity.skew(slot['stop'], angle_x, angle_y, origin=(px, py))
                 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)
         :param point: tuple of coordinates (x, y)
         :return:
         :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:
         if point is None:
             # Drills
             # Drills
             for drill in self.drills:
             for drill in self.drills:
                 drill['point'] = affinity.rotate(drill['point'], angle, origin='center')
                 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
             # Slots
             for slot in self.slots:
             for slot in self.slots:
                 slot['stop'] = affinity.rotate(slot['stop'], angle, origin='center')
                 slot['stop'] = affinity.rotate(slot['stop'], angle, origin='center')
@@ -4411,6 +4627,10 @@ class Excellon(Geometry):
             for drill in self.drills:
             for drill in self.drills:
                 drill['point'] = affinity.rotate(drill['point'], angle, origin=(px, py))
                 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
             # Slots
             for slot in self.slots:
             for slot in self.slots:
                 slot['stop'] = affinity.rotate(slot['stop'], angle, origin=(px, py))
                 slot['stop'] = affinity.rotate(slot['stop'], angle, origin=(px, py))
@@ -4479,16 +4699,18 @@ class CNCjob(Geometry):
         self.z_move = z_move
         self.z_move = z_move
 
 
         self.feedrate = feedrate
         self.feedrate = feedrate
-        self.feedrate_z = feedrate_z
+        self.z_feedrate = feedrate_z
         self.feedrate_rapid = feedrate_rapid
         self.feedrate_rapid = feedrate_rapid
 
 
         self.tooldia = tooldia
         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.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"}
         self.unitcode = {"IN": "G20", "MM": "G21"}
 
 
@@ -4533,12 +4755,18 @@ class CNCjob(Geometry):
 
 
         self.tool = 0.0
         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
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # Always append to it because it carries contents
         # from Geometry.
         # 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',
                            'tooldia', 'gcode', 'input_geometry_bounds', 'gcode_parsed', 'steps_per_circle',
-                           'depthpercut', 'spindlespeed', 'dwell', 'dwelltime']
+                           'z_depthpercut', 'spindlespeed', 'dwell', 'dwelltime']
 
 
     @property
     @property
     def postdata(self):
     def postdata(self):
@@ -4551,12 +4779,12 @@ class CNCjob(Geometry):
         self.z_cut = float(self.z_cut) * factor
         self.z_cut = float(self.z_cut) * factor
         self.z_move *= factor
         self.z_move *= factor
         self.feedrate *= factor
         self.feedrate *= factor
-        self.feedrate_z *= factor
+        self.z_feedrate *= factor
         self.feedrate_rapid *= factor
         self.feedrate_rapid *= factor
         self.tooldia *= 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
         return factor
 
 
@@ -4574,6 +4802,22 @@ class CNCjob(Geometry):
             self.app.log.error('Exception occurred within a postprocessor: ' + traceback.format_exc())
             self.app.log.error('Exception occurred within a postprocessor: ' + traceback.format_exc())
             return ''
             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):
     def optimized_travelling_salesman(self, points, start=None):
         """
         """
         As solving the problem in the brute force way is too slow,
         As solving the problem in the brute force way is too slow,
@@ -4646,14 +4890,14 @@ class CNCjob(Geometry):
         else:
         else:
             self.z_cut = drillz
             self.z_cut = drillz
 
 
-        self.toolchangez = toolchangez
+        self.z_toolchange = toolchangez
 
 
         try:
         try:
             if toolchangexy == '':
             if toolchangexy == '':
-                self.toolchange_xy = None
+                self.xy_toolchange = None
             else:
             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 "
                     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. ")
                                          "in the format (x, y) \nbut now there is only one value, not two. ")
                     return 'fail'
                     return 'fail'
@@ -4662,7 +4906,7 @@ class CNCjob(Geometry):
             pass
             pass
 
 
         self.startz = startz
         self.startz = startz
-        self.endz = endz
+        self.z_end = endz
 
 
         self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
         self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
         p = self.pp_excellon
         p = self.pp_excellon
@@ -4712,9 +4956,9 @@ class CNCjob(Geometry):
         gcode += self.doformat(p.feedrate_code)
         gcode += self.doformat(p.feedrate_code)
 
 
         if toolchange is False:
         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:
             else:
                 gcode += self.doformat(p.lift_code, x=0.0, y=0.0)
                 gcode += self.doformat(p.lift_code, x=0.0, y=0.0)
                 gcode += self.doformat(p.startz_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]))
                 locations.append((point.coords.xy[0][0], point.coords.xy[1][0]))
             return locations
             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:
         else:
             self.oldx = 0.0
             self.oldx = 0.0
             self.oldy = 0.0
             self.oldy = 0.0
@@ -4767,7 +5011,8 @@ class CNCjob(Geometry):
                 if exobj.drills:
                 if exobj.drills:
                     for tool in tools:
                     for tool in tools:
                         self.tool=tool
                         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.
                         # Create the data.
@@ -4865,6 +5110,7 @@ class CNCjob(Geometry):
                     for tool in tools:
                     for tool in tools:
                         self.tool=tool
                         self.tool=tool
                         self.postdata['toolC']=exobj.tools[tool]["C"]
                         self.postdata['toolC']=exobj.tools[tool]["C"]
+                        self.tooldia = exobj.tools[tool]["C"]
 
 
                         ################################################
                         ################################################
                         node_list = []
                         node_list = []
@@ -4957,6 +5203,7 @@ class CNCjob(Geometry):
                 if exobj.drills:
                 if exobj.drills:
                     self.tool = tool
                     self.tool = tool
                     self.postdata['toolC'] = exobj.tools[tool]["C"]
                     self.postdata['toolC'] = exobj.tools[tool]["C"]
+                    self.tooldia = exobj.tools[tool]["C"]
 
 
                     # Only if tool has points.
                     # Only if tool has points.
                     if tool in points:
                     if tool in points:
@@ -5058,7 +5305,7 @@ class CNCjob(Geometry):
         self.z_move = float(z_move) if z_move else None
         self.z_move = float(z_move) if z_move else None
 
 
         self.feedrate = float(feedrate) if feedrate 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.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
 
 
         self.spindlespeed = int(spindlespeed) if spindlespeed 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.dwelltime = float(dwelltime) if dwelltime else None
 
 
         self.startz = float(startz) if startz 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.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:
         try:
             if toolchangexy == '':
             if toolchangexy == '':
-                self.toolchange_xy = None
+                self.xy_toolchange = None
             else:
             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 "
                     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. ")
                                          "in the format (x, y) \nbut now there is only one value, not two. ")
                     return 'fail'
                     return 'fail'
@@ -5141,7 +5391,7 @@ class CNCjob(Geometry):
 
 
         if toolchange:
         if toolchange:
             # if "line_xyz" in self.pp_geometry_name:
             # 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:
             # else:
             #     self.gcode += self.doformat(p.toolchange_code)
             #     self.gcode += self.doformat(p.toolchange_code)
             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 = 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.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.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.multidepth = multidepth
 
 
-        self.toolchangez = float(toolchangez) if toolchangez else None
+        self.z_toolchange = float(toolchangez) if toolchangez else None
 
 
         try:
         try:
             if toolchangexy == '':
             if toolchangexy == '':
-                self.toolchange_xy = None
+                self.xy_toolchange = None
             else:
             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 "
                     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. ")
                                          "in the format (x, y) \nbut now there is only one value, not two. ")
                     return 'fail'
                     return 'fail'
@@ -5393,7 +5643,7 @@ class CNCjob(Geometry):
 
 
         if toolchange:
         if toolchange:
             # if "line_xyz" in self.pp_geometry_name:
             # 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:
             # else:
             #     self.gcode += self.doformat(p.toolchange_code)
             #     self.gcode += self.doformat(p.toolchange_code)
             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
             gcode += self.doformat(p.rapid_code, x=path[0][0], y=path[0][1])  # Move to first point
 
 
             # Move down to cutting depth
             # 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.down_z_start_code)
             gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
             gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
             gcode += self.doformat(p.dwell_fwd_code)
             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.lift_z_dispense_code)
             gcode += self.doformat(p.feedrate_xy_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.down_z_stop_code)
             gcode += self.doformat(p.spindle_off_code)
             gcode += self.doformat(p.spindle_off_code)
             gcode += self.doformat(p.dwell_rev_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)
             gcode += self.doformat(p.lift_code)
         elif type(geometry) == Point:
         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.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.down_z_start_code)
             gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
             gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
             gcode += self.doformat(p.dwell_fwd_code)
             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.spindle_off_code)
             gcode += self.doformat(p.down_z_stop_code)
             gcode += self.doformat(p.down_z_stop_code)
             gcode += self.doformat(p.dwell_rev_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)
             gcode += self.doformat(p.lift_code)
         return gcode
         return gcode
 
 
@@ -5627,17 +5877,17 @@ class CNCjob(Geometry):
         else:
         else:
             z_cut = Decimal(self.z_cut).quantize(Decimal('0.000000001'))
             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
         depth = 0
         reverse = False
         reverse = False
         while depth > z_cut:
         while depth > z_cut:
 
 
             # Increase depth. Limit to z_cut.
             # Increase depth. Limit to z_cut.
-            depth -= self.depthpercut
+            depth -= self.z_depthpercut
             if depth < z_cut:
             if depth < z_cut:
                 depth = z_cut
                 depth = z_cut
 
 
@@ -5887,7 +6137,7 @@ class CNCjob(Geometry):
     #             ax.add_patch(patch)
     #             ax.add_patch(patch)
     #
     #
     #     return fig
     #     return fig
-        
+
     def plot2(self, tooldia=None, dpi=75, margin=0.1, gcode_parsed=None,
     def plot2(self, tooldia=None, dpi=75, margin=0.1, gcode_parsed=None,
               color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]},
               color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]},
               alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, obj=None, visible=False, kind='all'):
               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
             feedrate = self.feedrate
 
 
         if feedrate_z is None:
         if feedrate_z is None:
-            feedrate_z = self.feedrate_z
+            feedrate_z = self.z_feedrate
 
 
         if feedrate_rapid is None:
         if feedrate_rapid is None:
             feedrate_rapid = self.feedrate_rapid
             feedrate_rapid = self.feedrate_rapid
@@ -6060,7 +6310,7 @@ class CNCjob(Geometry):
         # Move down to cutting depth
         # Move down to cutting depth
         if down:
         if down:
             # Different feedrate for vertical cut?
             # 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.feedrate_code)
             gcode += self.doformat(p.down_code, x=path[0][0], y=path[0][1], z_cut=z_cut)
             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)
             gcode += self.doformat(p.feedrate_code, feedrate=feedrate)
@@ -6105,7 +6355,7 @@ class CNCjob(Geometry):
             feedrate = self.feedrate
             feedrate = self.feedrate
 
 
         if feedrate_z is None:
         if feedrate_z is None:
-            feedrate_z = self.feedrate_z
+            feedrate_z = self.z_feedrate
 
 
         if feedrate_rapid is None:
         if feedrate_rapid is None:
             feedrate_rapid = self.feedrate_rapid
             feedrate_rapid = self.feedrate_rapid
@@ -6128,8 +6378,8 @@ class CNCjob(Geometry):
         # Move down to cutting depth
         # Move down to cutting depth
         if down:
         if down:
             # Different feedrate for vertical cut?
             # 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.feedrate_code)
                 gcode += self.doformat(p.down_code, x=path[0][0], y=path[0][1], z_cut=z_cut)
                 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)
                 gcode += self.doformat(p.feedrate_code, feedrate=feedrate)
@@ -6157,8 +6407,8 @@ class CNCjob(Geometry):
         p = self.pp_geometry
         p = self.pp_geometry
         gcode += self.doformat(p.linear_code, x=path[0][0], y=path[0][1])  # Move to first point
         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.down_code, x=path[0][0], y=path[0][1], z_cut = self.z_cut)
             gcode += self.doformat(p.feedrate_code)
             gcode += self.doformat(p.feedrate_code)
         else:
         else:
@@ -6315,7 +6565,7 @@ class CNCjob(Geometry):
             temp_gcode = ''
             temp_gcode = ''
             header_start = False
             header_start = False
             header_stop = 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)
             lines = StringIO(g)
             for line in lines:
             for line in lines:

+ 11 - 5
flatcamTools/ToolCalculators.py

@@ -225,12 +225,18 @@ class ToolCalculator(FlatCAMTool):
     def run(self):
     def run(self):
         self.app.report_usage("ToolCalculators()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         self.app.ui.notebook.setTabText(2, "Calc. Tool")
 
 
@@ -238,7 +244,7 @@ class ToolCalculator(FlatCAMTool):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
 
 
     def set_tool_ui(self):
     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
         ## Initialize form
         self.mm_entry.set_value('0')
         self.mm_entry.set_value('0')

+ 433 - 112
flatcamTools/ToolCutOut.py

@@ -1,15 +1,20 @@
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from ObjectCollection import *
 from ObjectCollection import *
 from FlatCAMApp import *
 from FlatCAMApp import *
+from shapely.geometry import box
 
 
 
 
 class CutOut(FlatCAMTool):
 class CutOut(FlatCAMTool):
 
 
     toolName = "Cutout PCB"
     toolName = "Cutout PCB"
+    gapFinished = pyqtSignal()
 
 
     def __init__(self, app):
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)
         FlatCAMTool.__init__(self, app)
 
 
+        self.app = app
+        self.canvas = app.plotcanvas
+
         ## Title
         ## Title
         title_label = QtWidgets.QLabel("%s" % self.toolName)
         title_label = QtWidgets.QLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
         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(1, QtGui.QIcon("share/drill16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.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(
         self.type_obj_combo_label.setToolTip(
             "Specify the type of object to be cutout.\n"
             "Specify the type of object to be cutout.\n"
             "It can be of type: Gerber or Geometry.\n"
             "It can be of type: Gerber or Geometry.\n"
             "What is selected here will dictate the kind\n"
             "What is selected here will dictate the kind\n"
             "of objects that will populate the 'Object' combobox."
             "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)
         form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
 
 
         ## Object to be cutout
         ## Object to be cutout
@@ -81,21 +87,13 @@ class CutOut(FlatCAMTool):
         self.gapsize = FCEntry()
         self.gapsize = FCEntry()
         self.gapsize_label = QtWidgets.QLabel("Gap size:")
         self.gapsize_label = QtWidgets.QLabel("Gap size:")
         self.gapsize_label.setToolTip(
         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"
             "used to keep the board connected to\n"
             "the surrounding material (the one \n"
             "the surrounding material (the one \n"
             "from which the PCB is cutout)."
             "from which the PCB is cutout)."
         )
         )
         form_layout.addRow(self.gapsize_label, self.gapsize)
         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:
         # How gaps wil be rendered:
         # lr    - left + right
         # lr    - left + right
         # tb    - top + bottom
         # tb    - top + bottom
@@ -104,10 +102,21 @@ class CutOut(FlatCAMTool):
         # 2tb   - 2*top + 2*bottom
         # 2tb   - 2*top + 2*bottom
         # 8     - 2*left + 2*right +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
-        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"
             "There can be maximum 8 bridges/gaps.\n"
             "The choices are:\n"
             "The choices are:\n"
             "- lr    - left + right\n"
             "- lr    - left + right\n"
@@ -117,76 +126,137 @@ class CutOut(FlatCAMTool):
             "- 2tb  - 2*top + 2*bottom\n"
             "- 2tb  - 2*top + 2*bottom\n"
             "- 8     - 2*left + 2*right +2*top + 2*bottom"
             "- 8     - 2*left + 2*right +2*top + 2*bottom"
         )
         )
+        gaps_label.setFixedWidth(60)
 
 
         self.gaps = FCComboBox()
         self.gaps = FCComboBox()
         gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
         gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
         for it in gaps_items:
         for it in gaps_items:
             self.gaps.addItem(it)
             self.gaps.addItem(it)
             self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
             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
         ## Buttons
         hlay = QtWidgets.QHBoxLayout()
         hlay = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay)
         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()
         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(
         self.ff_cutout_object_btn.setToolTip(
             "Cutout the selected object.\n"
             "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)
         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()
         hlay2 = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay2)
         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()
         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(
         self.rect_cutout_object_btn.setToolTip(
             "Cutout the selected object.\n"
             "Cutout the selected object.\n"
             "The resulting cutout shape is\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."
             "the bounding box of the Object."
         )
         )
         hlay2.addWidget(self.rect_cutout_object_btn)
         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):
     def on_type_obj_index_changed(self, index):
         obj_type = self.type_obj_combo.currentIndex()
         obj_type = self.type_obj_combo.currentIndex()
@@ -196,12 +266,17 @@ class CutOut(FlatCAMTool):
     def run(self):
     def run(self):
         self.app.report_usage("ToolCutOut()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         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.margin.set_value(float(self.app.defaults["tools_cutoutmargin"]))
         self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
         self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
         self.gaps.set_value(4)
         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):
     def on_freeform_cutout(self):
 
 
@@ -247,6 +331,11 @@ class CutOut(FlatCAMTool):
                                      "Add it and retry.")
                                      "Add it and retry.")
                 return
                 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:
         try:
             margin = float(self.margin.get_value())
             margin = float(self.margin.get_value())
         except ValueError:
         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.")
             self.app.inform.emit("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry.")
             return
             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']:
         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. "
             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. ")
                                  "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
             # rename the obj name so it can be identified as cutout
             cutout_obj.options["name"] += "_cutout"
             cutout_obj.options["name"] += "_cutout"
         else:
         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):
             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"
             outname = cutout_obj.options["name"] + "_cutout"
-
-            obj_exteriors = ext_obj.get_exteriors()
             self.app.new_object('geometry', outname, geo_init)
             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)
             cutout_obj = self.app.collection.get_by_name(outname)
 
 
         if gaps == '8' or gaps == '2LR':
         if gaps == '8' or gaps == '2LR':
@@ -364,6 +441,11 @@ class CutOut(FlatCAMTool):
         self.app.should_we_save = True
         self.app.should_we_save = True
 
 
     def on_rectangular_cutout(self):
     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()
         name = self.obj_combo.currentText()
 
 
         # Get source object.
         # Get source object.
@@ -387,6 +469,10 @@ class CutOut(FlatCAMTool):
                                      "Add it and retry.")
                                      "Add it and retry.")
                 return
                 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:
         try:
             margin = float(self.margin.get_value())
             margin = float(self.margin.get_value())
         except ValueError:
         except ValueError:
@@ -410,14 +496,15 @@ class CutOut(FlatCAMTool):
                 return
                 return
 
 
         try:
         try:
-            gaps = self.gaps_rect_radio.get_value()
+            gaps = self.gaps.get_value()
         except TypeError:
         except TypeError:
             self.app.inform.emit("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry.")
             self.app.inform.emit("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry.")
             return
             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:
         if cutout_obj.multigeo is True:
             self.app.inform.emit("[ERROR]Cutout operation cannot be done on a multi-geo Geometry.\n"
             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.")
                                  "and after that perform Cutout.")
             return
             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):
         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.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):
     def reset_fields(self):
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         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):
     def run(self):
         self.app.report_usage("Tool2Sided()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         self.app.ui.notebook.setTabText(2, "2-Sided Tool")
 
 

+ 10 - 4
flatcamTools/ToolFilm.py

@@ -166,12 +166,18 @@ class Film(FlatCAMTool):
     def run(self):
     def run(self):
         self.app.report_usage("ToolFilm()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         self.app.ui.notebook.setTabText(2, "Film Tool")
 
 

+ 10 - 4
flatcamTools/ToolImage.py

@@ -134,12 +134,18 @@ class ToolImage(FlatCAMTool):
     def run(self):
     def run(self):
         self.app.report_usage("ToolImage()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         self.app.ui.notebook.setTabText(2, "Image Tool")
 
 

+ 3 - 3
flatcamTools/ToolMeasurement.py

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

+ 13 - 0
flatcamTools/ToolMove.py

@@ -122,8 +122,15 @@ class ToolMove(FlatCAMTool):
                             else:
                             else:
                                 for sel_obj in obj_list:
                                 for sel_obj in obj_list:
 
 
+                                    # offset
                                     sel_obj.offset((dx, dy))
                                     sel_obj.offset((dx, dy))
                                     sel_obj.plot()
                                     sel_obj.plot()
+
+                                    try:
+                                        sel_obj.replotApertures.emit()
+                                    except:
+                                        pass
+
                                     # Update the object bounding box options
                                     # Update the object bounding box options
                                     a,b,c,d = sel_obj.bounds()
                                     a,b,c,d = sel_obj.bounds()
                                     sel_obj.options['xmin'] = a
                                     sel_obj.options['xmin'] = a
@@ -233,6 +240,12 @@ class ToolMove(FlatCAMTool):
 
 
     def draw_shape(self, coords):
     def draw_shape(self, coords):
         self.sel_rect = Polygon(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 = Color('blue')
         blue_t.alpha = 0.2
         blue_t.alpha = 0.2

+ 12 - 6
flatcamTools/ToolNonCopperClear.py

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

+ 12 - 6
flatcamTools/ToolPaint.py

@@ -304,12 +304,18 @@ class ToolPaint(FlatCAMTool, Gerber):
     def run(self):
     def run(self):
         self.app.report_usage("ToolPaint()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         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"])
         self.paintoverlap_entry.set_value(self.default_data["paintoverlap"])
 
 
         # updated units
         # 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":
         if self.units == "IN":
             self.addtool_entry.set_value(0.039)
             self.addtool_entry.set_value(0.039)
@@ -415,7 +421,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             pass
             pass
 
 
         # updated units
         # 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 = []
         sorted_tools = []
         for k, v in self.paint_tools.items():
         for k, v in self.paint_tools.items():

+ 10 - 4
flatcamTools/ToolPanelize.py

@@ -200,12 +200,18 @@ class Panelize(FlatCAMTool):
     def run(self):
     def run(self):
         self.app.report_usage("ToolPanelize()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         self.app.ui.notebook.setTabText(2, "Panel. Tool")
 
 

+ 12 - 6
flatcamTools/ToolProperties.py

@@ -55,9 +55,15 @@ class Properties(FlatCAMTool):
             return
             return
         self.set_tool_ui()
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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)
         FlatCAMTool.run(self)
         self.properties()
         self.properties()
@@ -120,10 +126,10 @@ class Properties(FlatCAMTool):
         width = abs(ymax - ymin)
         width = abs(ymax - ymin)
 
 
         self.addChild(dims, ['Length:', '%.4f %s' % (
         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' % (
         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
             area = (length * width) / 100
             self.addChild(dims, ['Box Area:', '%.4f %s' % (area, 'cm2')], True)
             self.addChild(dims, ['Box Area:', '%.4f %s' % (area, 'cm2')], True)
         else:
         else:
@@ -136,7 +142,7 @@ class Properties(FlatCAMTool):
                            'in': 'Inch',
                            'in': 'Inch',
                            'mm': 'Metric'
                            '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:
         for option in obj.options:
             if option is 'name':
             if option is 'name':
@@ -157,7 +163,7 @@ class Properties(FlatCAMTool):
                         printed_value = 'Present' if v else 'None'
                         printed_value = 'Present' if v else 'None'
                         self.addChild(geo_tool, [str(k), printed_value], True)
                         self.addChild(geo_tool, [str(k), printed_value], True)
                     elif k == 'data':
                     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)
                                                    color=QtGui.QColor("#000000"), font=font)
                         for data_k, data_v in v.items():
                         for data_k, data_v in v.items():
                             self.addChild(tool_data, [str(data_k), str(data_v)], True)
                             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):
     def run(self):
         self.app.report_usage("ToolSolderPaste()")
         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)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
         self.build_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")
         self.app.ui.notebook.setTabText(2, "SolderPaste Tool")
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
@@ -477,7 +484,7 @@ class SolderPaste(FlatCAMTool):
         self.name = ""
         self.name = ""
         self.obj = None
         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()):
         for name in list(self.app.postprocessors.keys()):
             # populate only with postprocessor files that start with 'Paste_'
             # populate only with postprocessor files that start with 'Paste_'
@@ -495,7 +502,7 @@ class SolderPaste(FlatCAMTool):
         self.ui_disconnect()
         self.ui_disconnect()
 
 
         # updated units
         # 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 = []
         sorted_tools = []
         for k, v in self.tooltable_tools.items():
         for k, v in self.tooltable_tools.items():
@@ -997,7 +1004,7 @@ class SolderPaste(FlatCAMTool):
             geo_obj.special_group = 'solder_paste_tool'
             geo_obj.special_group = 'solder_paste_tool'
 
 
             geo = LineString()
             geo = LineString()
-            work_geo = []
+            work_geo = self.flat_geometry
             rest_geo = []
             rest_geo = []
             tooluid = 1
             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,
                 # 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
                 # 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
                 # 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
                     # for whatever reason intersection on LinearRings does not work so we convert back to Polygons
                     poly = Polygon(g)
                     poly = Polygon(g)
                     x_min, y_min, x_max, y_max = poly.bounds
                     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):
     def run(self):
         self.app.report_usage("ToolTransform()")
         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:
         if self.app.ui.splitter.sizes()[0] == 0:
             self.app.ui.splitter.setSizes([1, 1])
             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")
         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']))
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_stop']))
 
 
     def toolchange_code(self, p):
     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(",")]
         toolchangexy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
         gcode = ''
         gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
 
         if p.units.upper() == 'MM':
         if p.units.upper() == 'MM':
             toolC_formatted = format(float(p['toolC']), '.2f')
             toolC_formatted = format(float(p['toolC']), '.2f')
@@ -74,26 +74,26 @@ class Paste_1(FlatCAMPostProc_Tools):
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             gcode = """
             gcode = """
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}
 T{tool}
 T{tool}
 M6    
 M6    
 (MSG, Change to Tool with Nozzle Dia = {toolC})
 (MSG, Change to Tool with Nozzle Dia = {toolC})
 M0
 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)),
            tool=int(int(p.tool)),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
         else:
         else:
             gcode = """
             gcode = """
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
 T{tool}
 M6    
 M6    
 (MSG, Change to Tool with Nozzle Dia = {toolC})
 (MSG, Change to Tool with Nozzle Dia = {toolC})
 M0
 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)),
            tool=int(int(p.tool)),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
@@ -121,7 +121,7 @@ M0
     def feedrate_xy_code(self, p):
     def feedrate_xy_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frxy'])))
         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'])))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz'])))
 
 
     def feedrate_z_dispense_code(self, p):
     def feedrate_z_dispense_code(self, p):

+ 29 - 29
postprocessors/Repetier.py

@@ -9,7 +9,7 @@ class Repetier(FlatCAMPostProc):
 
 
     def start_code(self, p):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
         gcode = ''
 
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -23,18 +23,18 @@ class Repetier(FlatCAMPostProc):
         gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
         gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         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 rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
         gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
         gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
             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_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:
         if coords_xy is not None:
             gcode += ';X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + '\n'
             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 += ';X,Y Toolchange: ' + "None" + units + '\n'
 
 
         gcode += ';Z Start: ' + str(p['startz']) + 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'
         gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
 
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
         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)
         return 'G1 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + " " + self.end_feedrate_code(p)
 
 
     def toolchange_code(self, 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
         f_plunge = p.f_plunge
         gcode = ''
         gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
 
         no_drills = 1
         no_drills = 1
 
 
         if int(p.tool) == 1 and p.startz is not None:
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
 
         if p.units.upper() == 'MM':
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
             toolC_formatted = format(p.toolC, '.2f')
@@ -99,22 +99,22 @@ class Repetier(FlatCAMPostProc):
 
 
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 gcode = """
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}                
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}                
 M84
 M84
 @pause Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
 @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),
            tool=int(p.tool),
            t_drills=no_drills,
            t_drills=no_drills,
            toolC=toolC_formatted)
            toolC=toolC_formatted)
             else:
             else:
                 gcode = """
                 gcode = """
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 M84
 M84
 @pause Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
 @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),
            tool=int(p.tool),
            t_drills=no_drills,
            t_drills=no_drills,
            toolC=toolC_formatted)
            toolC=toolC_formatted)
@@ -127,21 +127,21 @@ M84
         else:
         else:
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 gcode = """
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}
 M84
 M84
 @pause Change to tool T{tool} with Tool Dia = {toolC}
 @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),
            tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
             else:
             else:
                 gcode = """
                 gcode = """
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 M84
 M84
 @pause Change to tool T{tool} with Tool Dia = {toolC}
 @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),
            tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
@@ -164,8 +164,8 @@ M84
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.end_feedrate_code(p)
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.end_feedrate_code(p)
 
 
     def end_code(self, 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:
         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"
             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):
     def end_feedrate_code(self, p):
         return 'F' + self.feedrate_format %(p.fr_decimals, p.feedrate)
         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):
     def feedrate_rapid_code(self, p):
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
         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):
     def end_code(self, p):
         if p.units.upper() == 'IN':
         if p.units.upper() == 'IN':
-            z = p.endz / 25.4
+            z = p.z_end / 25.4
         else:
         else:
-            z = p.endz
+            z = p.z_end
         gcode = self.feedrate_rapid_code(p) + '\n'
         gcode = self.feedrate_rapid_code(p) + '\n'
         gcode += self.position_code(p).format(**p) + ',' + str(float(z * 40.0)) + ';'
         gcode += self.position_code(p).format(**p) + ',' + str(float(z * 40.0)) + ';'
         return gcode
         return gcode
@@ -89,13 +89,13 @@ class Roland_MDX_20(FlatCAMPostProc):
             fr_sec = 6
             fr_sec = 6
         return 'V' + str(self.feedrate_format % fr_sec) + ';'
         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)
         # 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
             fr_sec = 15
-        if p.feedrate_z < 6:
+        if p.z_feedrate < 6:
             fr_sec = 6
             fr_sec = 6
         return 'V' + str(self.feedrate_format % fr_sec) + ';'
         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 *
 from FlatCAMPostProc import *
 
 
 
 
-class Toolchange_Probe_general(FlatCAMPostProc):
+class Toolchange_Custom(FlatCAMPostProc):
 
 
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
     def start_code(self, p):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
         gcode = ''
 
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         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'
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         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 rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
         gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
         gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
             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_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:
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
             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 += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
 
         gcode += '(Z Start: ' + str(p['startz']) + 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'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
         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)
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
 
 
     def toolchange_code(self, 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
         f_plunge = p.f_plunge
         gcode = ''
         gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
 
         no_drills = 1
         no_drills = 1
 
 
         if int(p.tool) == 1 and p.startz is not None:
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
 
         if p.units.upper() == 'MM':
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
             toolC_formatted = format(p.toolC, '.2f')
@@ -97,29 +97,13 @@ class Toolchange_Probe_general(FlatCAMPostProc):
 
 
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 gcode = """
-M5
-G00 X{toolchangex} Y{toolchangey}                
-T{tool}
 M6
 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),
              tool=int(p.tool),
              t_drills=no_drills,
              t_drills=no_drills,
              toolC=toolC_formatted)
              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:
             if f_plunge is True:
                 gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
                 gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
             return gcode
             return gcode
@@ -127,24 +111,11 @@ M0
         else:
         else:
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 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),
              tool=int(p.tool),
              toolC=toolC_formatted)
              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:
             if f_plunge is True:
                 gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
                 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)
         return ('G01 ' + self.position_code(p)).format(**p)
 
 
     def end_code(self, 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:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             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):
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
         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):
     def spindle_code(self, p):
         if p.spindlespeed:
         if p.spindlespeed:

+ 30 - 30
postprocessors/Toolchange_Probe_MACH3.py

@@ -8,7 +8,7 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
 
 
     def start_code(self, p):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
         gcode = ''
 
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         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'
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         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 rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
         gcode += '(Feedrate Probe ' + str(p['feedrate_probe']) + 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 str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
             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_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:
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
             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 += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
 
         gcode += '(Z Start: ' + str(p['startz']) + 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 += '(Z Probe Depth: ' + str(p['z_pdepth']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
 
@@ -61,7 +61,7 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
         gcode += 'G17\n'
         gcode += 'G17\n'
         gcode += 'G94\n'
         gcode += 'G94\n'
         gcode += '(MSG, WARNING: Make sure you do zero on all axis. ' \
         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'
         gcode += 'M0'
 
 
         return gcode
         return gcode
@@ -76,20 +76,20 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
 
 
     def toolchange_code(self, 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
         f_plunge = p.f_plunge
 
 
         gcode = ''
         gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
 
         no_drills = 1
         no_drills = 1
 
 
         if int(p.tool) == 1 and p.startz is not None:
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
 
         if p.units.upper() == 'MM':
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
             toolC_formatted = format(p.toolC, '.2f')
@@ -106,8 +106,8 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
 M5             
 M5             
 T{tool}
 T{tool}
 M6
 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})
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE ||| Drills for this tool = {t_drills})
 M0
 M0
 F{feedrate_probe}
 F{feedrate_probe}
@@ -120,9 +120,9 @@ G92 Z0
 G00 Z{z_move}
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
 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_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            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)),
            feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
@@ -136,7 +136,7 @@ M0
 M5
 M5
 T{tool}
 T{tool}
 M6
 M6
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE ||| Drills for this tool = {t_drills})
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE ||| Drills for this tool = {t_drills})
 M0
 M0
 F{feedrate_probe}
 F{feedrate_probe}
@@ -149,7 +149,7 @@ G92 Z0
 G00 Z{z_move}
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
 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_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            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)),
            feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
@@ -169,8 +169,8 @@ M0
 M5
 M5
 T{tool}
 T{tool}
 M6
 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)
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE)
 M0
 M0
 F{feedrate_probe}
 F{feedrate_probe}
@@ -183,9 +183,9 @@ G92 Z0
 G00 Z{z_move}
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
 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_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            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)),
            feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
@@ -198,7 +198,7 @@ M0
 M5
 M5
 T{tool}
 T{tool}
 M6
 M6
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE)
 (MSG, Change to Tool Dia = {toolC} ||| CONNECT THE PROBE)
 M0
 M0
 F{feedrate_probe}
 F{feedrate_probe}
@@ -211,7 +211,7 @@ G92 Z0
 G00 Z{z_move}
 G00 Z{z_move}
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 (MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
 M0
 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_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            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)),
            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)
         return ('G01 ' + self.position_code(p)).format(**p)
 
 
     def end_code(self, 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:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             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):
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
         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):
     def spindle_code(self, p):
         if p.spindlespeed:
         if p.spindlespeed:

+ 35 - 35
postprocessors/Toolchange_manual.py

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

+ 30 - 29
postprocessors/default.py

@@ -8,7 +8,7 @@ class default(FlatCAMPostProc):
 
 
     def start_code(self, p):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
         gcode = ''
 
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -22,18 +22,18 @@ class default(FlatCAMPostProc):
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
         gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         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 rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
         gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
         gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
             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_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:
         if coords_xy is not None:
             gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
             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 += '(X,Y Toolchange: ' + "None" + units + ')\n'
 
 
         gcode += '(Z Start: ' + str(p['startz']) + 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'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
         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)
         return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
 
 
     def toolchange_code(self, 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
         f_plunge = p.f_plunge
         gcode = ''
         gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
 
         no_drills = 1
         no_drills = 1
 
 
         if int(p.tool) == 1 and p.startz is not None:
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
 
         if p.units.upper() == 'MM':
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
             toolC_formatted = format(p.toolC, '.2f')
@@ -100,25 +100,26 @@ class default(FlatCAMPostProc):
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 gcode = """
 M5
 M5
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}                
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}                
 T{tool}
 T{tool}
 M6
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 (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),
              tool=int(p.tool),
              t_drills=no_drills,
              t_drills=no_drills,
              toolC=toolC_formatted)
              toolC=toolC_formatted)
             else:
             else:
                 gcode = """
                 gcode = """
 M5       
 M5       
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
 T{tool}
 M6
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 (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),
              tool=int(p.tool),
              t_drills=no_drills,
              t_drills=no_drills,
              toolC=toolC_formatted)
              toolC=toolC_formatted)
@@ -130,24 +131,24 @@ M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchange
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 gcode = """
 M5
 M5
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}
+G00 Z{z_toolchange}
+G00 X{x_toolchange} Y{y_toolchange}
 T{tool}
 T{tool}
 M6    
 M6    
 (MSG, Change to Tool Dia = {toolC})
 (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),
              tool=int(p.tool),
              toolC=toolC_formatted)
              toolC=toolC_formatted)
             else:
             else:
                 gcode = """
                 gcode = """
 M5
 M5
-G00 Z{toolchangez}
+G00 Z{z_toolchange}
 T{tool}
 T{tool}
 M6    
 M6    
 (MSG, Change to Tool Dia = {toolC})
 (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),
              tool=int(p.tool),
              toolC=toolC_formatted)
              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)
         return ('G01 ' + self.position_code(p)).format(**p)
 
 
     def end_code(self, 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:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             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):
     def feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
         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):
     def spindle_code(self, p):
         if p.spindlespeed:
         if p.spindlespeed:

+ 29 - 29
postprocessors/grbl_11.py

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

+ 1 - 1
postprocessors/hpgl.py

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

+ 29 - 29
postprocessors/marlin.py

@@ -9,7 +9,7 @@ class marlin(FlatCAMPostProc):
 
 
     def start_code(self, p):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = p['xy_toolchange']
         gcode = ''
         gcode = ''
 
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -23,18 +23,18 @@ class marlin(FlatCAMPostProc):
         gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
         gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         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 rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
         gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
         gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
 
 
         if str(p['options']['type']) == 'Geometry':
         if str(p['options']['type']) == 'Geometry':
             if p['multidepth'] is True:
             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_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:
         if coords_xy is not None:
             gcode += ';X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + '\n'
             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 += ';X,Y Toolchange: ' + "None" + units + '\n'
 
 
         gcode += ';Z Start: ' + str(p['startz']) + 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'
         gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
 
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
         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)
         return 'G1 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + " " + self.end_feedrate_code(p)
 
 
     def toolchange_code(self, 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
         f_plunge = p.f_plunge
         gcode = ''
         gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
-            toolchangex = toolchangexy[0]
-            toolchangey = toolchangexy[1]
+            x_toolchange = toolchangexy[0]
+            y_toolchange = toolchangexy[1]
 
 
         no_drills = 1
         no_drills = 1
 
 
         if int(p.tool) == 1 and p.startz is not None:
         if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
+            z_toolchange = p.startz
 
 
         if p.units.upper() == 'MM':
         if p.units.upper() == 'MM':
             toolC_formatted = format(p.toolC, '.2f')
             toolC_formatted = format(p.toolC, '.2f')
@@ -100,25 +100,25 @@ class marlin(FlatCAMPostProc):
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 gcode = """
 M5
 M5
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}                
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}                
 T{tool}
 T{tool}
 M6
 M6
 ;MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
 ;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),
              tool=int(p.tool),
              t_drills=no_drills,
              t_drills=no_drills,
              toolC=toolC_formatted)
              toolC=toolC_formatted)
             else:
             else:
                 gcode = """
                 gcode = """
 M5
 M5
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 T{tool}
 T{tool}
 M6
 M6
 ;MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
 ;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),
              tool=int(p.tool),
              t_drills=no_drills,
              t_drills=no_drills,
              toolC=toolC_formatted)
              toolC=toolC_formatted)
@@ -131,24 +131,24 @@ M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchange
             if toolchangexy is not None:
             if toolchangexy is not None:
                 gcode = """
                 gcode = """
 M5
 M5
-G0 Z{toolchangez}
-G0 X{toolchangex} Y{toolchangey}
+G0 Z{z_toolchange}
+G0 X{x_toolchange} Y{y_toolchange}
 T{tool}
 T{tool}
 M6    
 M6    
 ;MSG, Change to Tool Dia = {toolC}
 ;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),
              tool=int(p.tool),
              toolC=toolC_formatted)
              toolC=toolC_formatted)
             else:
             else:
                 gcode = """
                 gcode = """
 M5
 M5
-G0 Z{toolchangez}
+G0 Z{z_toolchange}
 T{tool}
 T{tool}
 M6    
 M6    
 ;MSG, Change to Tool Dia = {toolC}
 ;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),
              tool=int(p.tool),
              toolC=toolC_formatted)
              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)
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.end_feedrate_code(p)
 
 
     def end_code(self, 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:
         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"
             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):
     def end_feedrate_code(self, p):
         return 'F' + self.feedrate_format %(p.fr_decimals, p.feedrate)
         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):
     def feedrate_rapid_code(self, p):
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
         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_()

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