Przeglądaj źródła

- 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

Marius Stanciu 7 lat temu
rodzic
commit
386c3408ca
6 zmienionych plików z 498 dodań i 33 usunięć
  1. 29 1
      FlatCAMApp.py
  2. 160 9
      FlatCAMGUI.py
  3. 176 9
      FlatCAMObj.py
  4. 127 13
      ObjectUI.py
  5. 4 0
      README.md
  6. 2 1
      postprocessors/default.py

+ 29 - 1
FlatCAMApp.py

@@ -330,11 +330,13 @@ class App(QtCore.QObject):
             "global_workspace": self.ui.general_defaults_form.general_gui_group.workspace_cb,
             "global_workspace": self.ui.general_defaults_form.general_gui_group.workspace_cb,
             "global_workspaceT": self.ui.general_defaults_form.general_gui_group.wk_cb,
             "global_workspaceT": self.ui.general_defaults_form.general_gui_group.wk_cb,
 
 
+            # Gerber General
             "gerber_plot": self.ui.gerber_defaults_form.gerber_gen_group.plot_cb,
             "gerber_plot": self.ui.gerber_defaults_form.gerber_gen_group.plot_cb,
             "gerber_solid": self.ui.gerber_defaults_form.gerber_gen_group.solid_cb,
             "gerber_solid": self.ui.gerber_defaults_form.gerber_gen_group.solid_cb,
             "gerber_multicolored": self.ui.gerber_defaults_form.gerber_gen_group.multicolored_cb,
             "gerber_multicolored": self.ui.gerber_defaults_form.gerber_gen_group.multicolored_cb,
             "gerber_circle_steps": self.ui.gerber_defaults_form.gerber_gen_group.circle_steps_entry,
             "gerber_circle_steps": self.ui.gerber_defaults_form.gerber_gen_group.circle_steps_entry,
 
 
+            # Gerber Options
             "gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry,
             "gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry,
             "gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry,
             "gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry,
             "gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry,
             "gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry,
@@ -345,6 +347,12 @@ class App(QtCore.QObject):
             "gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry,
             "gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry,
             "gerber_bboxrounded": self.ui.gerber_defaults_form.gerber_opt_group.bbrounded_cb,
             "gerber_bboxrounded": self.ui.gerber_defaults_form.gerber_opt_group.bbrounded_cb,
 
 
+            # Gerber Advanced Options
+            "gerber_aperture_display": self.ui.gerber_defaults_form.gerber_adv_opt_group.aperture_table_visibility_cb,
+            "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
+            "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
+            "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
+
             # Excellon General
             # Excellon General
             "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
             "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
             "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
             "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
@@ -428,9 +436,14 @@ class App(QtCore.QObject):
             "cncjob_fr_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry,
             "cncjob_fr_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry,
             "cncjob_steps_per_circle": self.ui.cncjob_defaults_form.cncjob_gen_group.steps_per_circle_entry,
             "cncjob_steps_per_circle": self.ui.cncjob_defaults_form.cncjob_gen_group.steps_per_circle_entry,
 
 
+            # CNC Job Options
             "cncjob_prepend": self.ui.cncjob_defaults_form.cncjob_opt_group.prepend_text,
             "cncjob_prepend": self.ui.cncjob_defaults_form.cncjob_opt_group.prepend_text,
             "cncjob_append": self.ui.cncjob_defaults_form.cncjob_opt_group.append_text,
             "cncjob_append": self.ui.cncjob_defaults_form.cncjob_opt_group.append_text,
 
 
+            # CNC Job Advanced Options
+            "cncjob_toolchange_macro": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_text,
+            "cncjob_toolchange_macro_enable": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_cb,
+
             # NCC Tool
             # NCC Tool
             "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
             "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
             "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry,
             "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry,
@@ -605,6 +618,7 @@ class App(QtCore.QObject):
             "global_point_clipboard_format": "(%.4f, %.4f)",
             "global_point_clipboard_format": "(%.4f, %.4f)",
             "global_zdownrate": None,
             "global_zdownrate": None,
 
 
+            # Gerber General
             "gerber_plot": True,
             "gerber_plot": True,
             "gerber_solid": True,
             "gerber_solid": True,
             "gerber_multicolored": False,
             "gerber_multicolored": False,
@@ -612,6 +626,7 @@ class App(QtCore.QObject):
             "gerber_isopasses": 1,
             "gerber_isopasses": 1,
             "gerber_isooverlap": 0.15,
             "gerber_isooverlap": 0.15,
 
 
+            # Gerber Options
             "gerber_combine_passes": False,
             "gerber_combine_passes": False,
             "gerber_milling_type": "cl",
             "gerber_milling_type": "cl",
             "gerber_noncoppermargin": 0.1,
             "gerber_noncoppermargin": 0.1,
@@ -621,6 +636,12 @@ class App(QtCore.QObject):
             "gerber_circle_steps": 64,
             "gerber_circle_steps": 64,
             "gerber_use_buffer_for_union": True,
             "gerber_use_buffer_for_union": True,
 
 
+            # Gerber Advanced Options
+            "gerber_aperture_display": False,
+            "gerber_aperture_scale_factor": 1.0,
+            "gerber_aperture_buffer_factor": 0.0,
+            "gerber_follow": False,
+
             # Excellon General
             # Excellon General
             "excellon_plot": True,
             "excellon_plot": True,
             "excellon_solid": True,
             "excellon_solid": True,
@@ -696,14 +717,21 @@ class App(QtCore.QObject):
             "geometry_segx": 0.0,
             "geometry_segx": 0.0,
             "geometry_segy": 0.0,
             "geometry_segy": 0.0,
 
 
+            # CNC Job General
             "cncjob_plot": True,
             "cncjob_plot": True,
             "cncjob_plot_kind": 'all',
             "cncjob_plot_kind": 'all',
             "cncjob_tooldia": 0.0393701,
             "cncjob_tooldia": 0.0393701,
             "cncjob_coords_decimals": 4,
             "cncjob_coords_decimals": 4,
             "cncjob_fr_decimals": 2,
             "cncjob_fr_decimals": 2,
+            "cncjob_steps_per_circle": 64,
+
+            # CNC Job Options
             "cncjob_prepend": "",
             "cncjob_prepend": "",
             "cncjob_append": "",
             "cncjob_append": "",
-            "cncjob_steps_per_circle": 64,
+
+            # CNC Job Advanced Options
+            "cncjob_toolchange_macro": "",
+            "cncjob_toolchange_macro_enable": False,
 
 
             "tools_ncctools": "1.0, 0.5",
             "tools_ncctools": "1.0, 0.5",
             "tools_nccoverlap": 0.4,
             "tools_nccoverlap": 0.4,

+ 160 - 9
FlatCAMGUI.py

@@ -2576,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(200)
+        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()
 
 
 
 
@@ -2700,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()
 
 
 
 
@@ -3288,26 +3296,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:")
@@ -3447,6 +3456,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):
@@ -4546,6 +4622,81 @@ 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 = ['tool', 'toolC', 't_drills', 'toolchangex', 'toolchangey', 'toolchangez']
+        self.tc_variable_combo.addItems(variables)
+        self.tc_variable_combo.setItemData(0, "tool = tool number", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(1, "toolC = tool diameter", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(2, "t_drills = for Excellon, total number of drills", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(3, "toolchangex = X coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(4, "toolchangey = Y coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(5, "toolchangez = Z coord for Toolchange", 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)

+ 176 - 9
FlatCAMObj.py

@@ -382,26 +382,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)
 
 
@@ -425,7 +438,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)
@@ -479,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
@@ -498,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':
@@ -513,6 +537,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         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):
@@ -963,17 +990,151 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.ui.scale_aperture_label.setVisible(True)
             self.ui.scale_aperture_label.setVisible(True)
             self.ui.scale_aperture_entry.setVisible(True)
             self.ui.scale_aperture_entry.setVisible(True)
             self.ui.scale_aperture_button.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)
+
         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_label.setVisible(False)
             self.ui.scale_aperture_entry.setVisible(False)
             self.ui.scale_aperture_entry.setVisible(False)
             self.ui.scale_aperture_button.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)
+
             # on hide disable all mark plots
             # on hide disable all mark plots
             for row in range(self.ui.apertures_table.rowCount()):
             for row in range(self.ui.apertures_table.rowCount()):
                 self.ui.apertures_table.cellWidget(row, 5).set_value(False)
                 self.ui.apertures_table.cellWidget(row, 5).set_value(False)
             self.mark_shapes.clear(update=True)
             self.mark_shapes.clear(update=True)
 
 
+    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 = ''
+            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)
+
+            # 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):
         """
         """
         Converts the units of the object by scaling dimensions in all geometry
         Converts the units of the object by scaling dimensions in all geometry
@@ -4698,7 +4859,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
         })
         })
 
 
         '''
         '''
@@ -4900,6 +5063,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             # "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
@@ -4924,8 +5089,10 @@ 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)

+ 127 - 13
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
 
 
 
 
@@ -180,6 +181,12 @@ class GerberObjectUI(ObjectUI):
 
 
         # Aperture Table Visibility CB
         # Aperture Table Visibility CB
         self.aperture_table_visibility_cb = FCCheckBox('Show/Hide')
         self.aperture_table_visibility_cb = FCCheckBox('Show/Hide')
+        self.aperture_table_visibility_cb.setToolTip(
+            "Toogle the display of the Gerber Apertures Table.\n"
+            "Also, on hide, it will delete all mark shapes\n"
+            "that are drawn on canvas."
+
+        )
         self.aperture_table_visibility_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         self.aperture_table_visibility_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         hlay_plot.addWidget(self.aperture_table_visibility_cb)
         hlay_plot.addWidget(self.aperture_table_visibility_cb)
 
 
@@ -207,30 +214,69 @@ class GerberObjectUI(ObjectUI):
         # self.apertures_table.setColumnHidden(5, True)
         # self.apertures_table.setColumnHidden(5, True)
 
 
         #### Aperture Scale ####
         #### Aperture Scale ####
-        self.scale_aperture_grid = QtWidgets.QGridLayout()
-        self.custom_box.addLayout(self.scale_aperture_grid)
+        self.transform_aperture_grid = QtWidgets.QGridLayout()
+        self.custom_box.addLayout(self.transform_aperture_grid)
 
 
-        # Factor
-        self.scale_aperture_label = QtWidgets.QLabel('<b>Factor:</b>')
+        # Scale Aperture Factor
+        self.scale_aperture_label = QtWidgets.QLabel('Scale Factor:')
         self.scale_aperture_label.setToolTip(
         self.scale_aperture_label.setToolTip(
             "Change the size of the selected apertures.\n"
             "Change the size of the selected apertures.\n"
             "Factor by which to multiply\n"
             "Factor by which to multiply\n"
             "geometric features of this object."
             "geometric features of this object."
         )
         )
         self.scale_aperture_label.setFixedWidth(90)
         self.scale_aperture_label.setFixedWidth(90)
-        self.scale_aperture_grid.addWidget(self.scale_aperture_label, 0, 0)
+        self.transform_aperture_grid.addWidget(self.scale_aperture_label, 0, 0)
 
 
         self.scale_aperture_entry = FloatEntry2()
         self.scale_aperture_entry = FloatEntry2()
-        self.scale_aperture_entry.set_value(1.0)
-        self.scale_aperture_grid.addWidget(self.scale_aperture_entry, 0, 1)
+        self.transform_aperture_grid.addWidget(self.scale_aperture_entry, 0, 1)
 
 
         # Scale Button
         # Scale Button
         self.scale_aperture_button = QtWidgets.QPushButton('Scale')
         self.scale_aperture_button = QtWidgets.QPushButton('Scale')
         self.scale_aperture_button.setToolTip(
         self.scale_aperture_button.setToolTip(
             "Perform scaling operation."
             "Perform scaling operation."
         )
         )
-        self.scale_aperture_button.setFixedWidth(40)
-        self.scale_aperture_grid.addWidget(self.scale_aperture_button, 0, 2)
+        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 scaling operation."
+        )
+        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.\n"
+            "This new object can then be isolated etc."
+        )
+        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)
@@ -238,6 +284,10 @@ class GerberObjectUI(ObjectUI):
         self.scale_aperture_entry.setVisible(False)
         self.scale_aperture_entry.setVisible(False)
         self.scale_aperture_button.setVisible(False)
         self.scale_aperture_button.setVisible(False)
 
 
+        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>")
         self.isolation_routing_label.setToolTip(
         self.isolation_routing_label.setToolTip(
@@ -1362,7 +1412,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"
@@ -1373,7 +1423,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"
@@ -1385,6 +1435,70 @@ 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)
+
+        # 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.cnc_box.addWidget(toolchangelabel)
+
+        self.toolchange_text = FCTextArea()
+        self.cnc_box.addWidget(self.toolchange_text)
+
+        cnclay = QtWidgets.QHBoxLayout()
+        self.cnc_box.addLayout(cnclay)
+
+        # 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)."
+        )
+        cnclay.addWidget(self.toolchange_cb)
+        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 = ['tool', 'toolC', 't_drills', 'toolchangex', 'toolchangey', 'toolchangez']
+        self.tc_variable_combo.addItems(variables)
+        self.tc_variable_combo.setItemData(0, "tool = tool number", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(1, "toolC = tool diameter", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(2, "t_drills = for Excellon, total number of drills", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(3, "toolchangex = X coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(4, "toolchangey = Y coord for Toolchange", Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(5, "toolchangez = Z coord for Toolchange", 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)

+ 4 - 0
README.md

@@ -14,6 +14,10 @@ CAD program, and create G-Code for Isolation routing.
 - fixed the Gerber object UI layout
 - fixed the Gerber object UI layout
 - added ability to mark individual apertures in Gerber file using the Gerber Aperture Table
 - 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
 - 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
 24.02.2019
 
 

+ 2 - 1
postprocessors/default.py

@@ -105,7 +105,8 @@ G00 X{toolchangex} Y{toolchangey}
 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),
+M0
+""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
              toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
              toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
              toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
              toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
              tool=int(p.tool),
              tool=int(p.tool),