فهرست منبع

Merged in marius_stanciu/flatcam_beta/beta_8.917 (pull request #147)

Beta 8.917
Marius Stanciu 6 سال پیش
والد
کامیت
900631b92e

+ 88 - 46
FlatCAMApp.py

@@ -94,8 +94,8 @@ class App(QtCore.QObject):
     log.addHandler(handler)
     log.addHandler(handler)
 
 
     # Version
     # Version
-    version = 8.916
-    version_date = "2019/05/10"
+    version = 8.917
+    version_date = "2019/05/22"
     beta = True
     beta = True
 
 
     # current date now
     # current date now
@@ -324,6 +324,8 @@ class App(QtCore.QObject):
             "global_worker_number": self.ui.general_defaults_form.general_app_group.worker_number_sb,
             "global_worker_number": self.ui.general_defaults_form.general_app_group.worker_number_sb,
             "global_tolerance": self.ui.general_defaults_form.general_app_group.tol_entry,
             "global_tolerance": self.ui.general_defaults_form.general_app_group.tol_entry,
 
 
+            "global_open_style": self.ui.general_defaults_form.general_app_group.open_style_cb,
+
             "global_compression_level": self.ui.general_defaults_form.general_app_group.compress_combo,
             "global_compression_level": self.ui.general_defaults_form.general_app_group.compress_combo,
             "global_save_compressed": self.ui.general_defaults_form.general_app_group.save_type_cb,
             "global_save_compressed": self.ui.general_defaults_form.general_app_group.save_type_cb,
 
 
@@ -369,8 +371,8 @@ class App(QtCore.QObject):
 
 
             # Gerber Advanced Options
             # Gerber Advanced Options
             "gerber_aperture_display": self.ui.gerber_defaults_form.gerber_adv_opt_group.aperture_table_visibility_cb,
             "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_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,
             "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
 
 
             # Gerber Export
             # Gerber Export
@@ -379,6 +381,9 @@ class App(QtCore.QObject):
             "gerber_exp_decimals": self.ui.gerber_defaults_form.gerber_exp_group.format_dec_entry,
             "gerber_exp_decimals": self.ui.gerber_defaults_form.gerber_exp_group.format_dec_entry,
             "gerber_exp_zeros": self.ui.gerber_defaults_form.gerber_exp_group.zeros_radio,
             "gerber_exp_zeros": self.ui.gerber_defaults_form.gerber_exp_group.zeros_radio,
 
 
+            # Gerber Editor
+            "gerber_editor_sel_limit": self.ui.gerber_defaults_form.gerber_editor_group.sel_limit_entry,
+
             # 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,
@@ -396,6 +401,7 @@ class App(QtCore.QObject):
             "excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry,
             "excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry,
             "excellon_feedrate": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_entry,
             "excellon_feedrate": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_entry,
             "excellon_spindlespeed": self.ui.excellon_defaults_form.excellon_opt_group.spindlespeed_entry,
             "excellon_spindlespeed": self.ui.excellon_defaults_form.excellon_opt_group.spindlespeed_entry,
+            "excellon_spindledir": self.ui.excellon_defaults_form.excellon_opt_group.spindledir_radio,
             "excellon_dwell": self.ui.excellon_defaults_form.excellon_opt_group.dwell_cb,
             "excellon_dwell": self.ui.excellon_defaults_form.excellon_opt_group.dwell_cb,
             "excellon_dwelltime": self.ui.excellon_defaults_form.excellon_opt_group.dwelltime_entry,
             "excellon_dwelltime": self.ui.excellon_defaults_form.excellon_opt_group.dwelltime_entry,
             "excellon_toolchange": self.ui.excellon_defaults_form.excellon_opt_group.toolchange_cb,
             "excellon_toolchange": self.ui.excellon_defaults_form.excellon_opt_group.toolchange_cb,
@@ -434,6 +440,7 @@ class App(QtCore.QObject):
             "geometry_feedrate": self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry,
             "geometry_feedrate": self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry,
             "geometry_feedrate_z": self.ui.geometry_defaults_form.geometry_opt_group.cncplunge_entry,
             "geometry_feedrate_z": self.ui.geometry_defaults_form.geometry_opt_group.cncplunge_entry,
             "geometry_spindlespeed": self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry,
             "geometry_spindlespeed": self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry,
+            "geometry_spindledir": self.ui.geometry_defaults_form.geometry_opt_group.spindledir_radio,
             "geometry_dwell": self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb,
             "geometry_dwell": self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb,
             "geometry_dwelltime": self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry,
             "geometry_dwelltime": self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry,
             "geometry_ppname_g": self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb,
             "geometry_ppname_g": self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb,
@@ -454,6 +461,9 @@ class App(QtCore.QObject):
             "geometry_segx": self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry,
             "geometry_segx": self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry,
             "geometry_segy": self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry,
             "geometry_segy": self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry,
 
 
+            # Geometry Editor
+            "geometry_editor_sel_limit": self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry,
+
             # CNCJob General
             # CNCJob General
             "cncjob_plot": self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb,
             "cncjob_plot": self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb,
             "cncjob_plot_kind": self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio,
             "cncjob_plot_kind": self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio,
@@ -602,6 +612,7 @@ class App(QtCore.QObject):
             "global_toggle_tooltips": True,
             "global_toggle_tooltips": True,
             "global_worker_number": 2,
             "global_worker_number": 2,
             "global_tolerance": 0.01,
             "global_tolerance": 0.01,
+            "global_open_style": True,
             "global_compression_level": 3,
             "global_compression_level": 3,
             "global_save_compressed": True,
             "global_save_compressed": True,
 
 
@@ -661,7 +672,7 @@ class App(QtCore.QObject):
             "global_zdownrate": None,
             "global_zdownrate": None,
 
 
             # General GUI Settings
             # General GUI Settings
-            "global_hover": True,
+            "global_hover": False,
             "global_selection_shape": True,
             "global_selection_shape": True,
             "global_layout": "compact",
             "global_layout": "compact",
             # Gerber General
             # Gerber General
@@ -679,7 +690,7 @@ class App(QtCore.QObject):
             "gerber_noncopperrounded": False,
             "gerber_noncopperrounded": False,
             "gerber_bboxmargin": 0.1,
             "gerber_bboxmargin": 0.1,
             "gerber_bboxrounded": False,
             "gerber_bboxrounded": False,
-            "gerber_circle_steps": 64,
+            "gerber_circle_steps": 128,
             "gerber_use_buffer_for_union": True,
             "gerber_use_buffer_for_union": True,
 
 
             # Gerber Advanced Options
             # Gerber Advanced Options
@@ -694,6 +705,9 @@ class App(QtCore.QObject):
             "gerber_exp_decimals": 4,
             "gerber_exp_decimals": 4,
             "gerber_exp_zeros": 'L',
             "gerber_exp_zeros": 'L',
 
 
+            # Gerber Editor
+            "gerber_editor_sel_limit": 30,
+
             # Excellon General
             # Excellon General
             "excellon_plot": True,
             "excellon_plot": True,
             "excellon_solid": True,
             "excellon_solid": True,
@@ -711,6 +725,7 @@ class App(QtCore.QObject):
             "excellon_travelz": 0.1,
             "excellon_travelz": 0.1,
             "excellon_feedrate": 3.0,
             "excellon_feedrate": 3.0,
             "excellon_spindlespeed": None,
             "excellon_spindlespeed": None,
+            "excellon_spindledir": 'CW',
             "excellon_dwell": False,
             "excellon_dwell": False,
             "excellon_dwelltime": 1,
             "excellon_dwelltime": 1,
             "excellon_toolchange": False,
             "excellon_toolchange": False,
@@ -740,7 +755,7 @@ class App(QtCore.QObject):
 
 
             # Geometry General
             # Geometry General
             "geometry_plot": True,
             "geometry_plot": True,
-            "geometry_circle_steps": 64,
+            "geometry_circle_steps": 128,
             "geometry_cnctooldia": 0.016,
             "geometry_cnctooldia": 0.016,
 
 
             # Geometry Options
             # Geometry Options
@@ -753,6 +768,7 @@ class App(QtCore.QObject):
             "geometry_feedrate": 3.0,
             "geometry_feedrate": 3.0,
             "geometry_feedrate_z": 3.0,
             "geometry_feedrate_z": 3.0,
             "geometry_spindlespeed": None,
             "geometry_spindlespeed": None,
+            "geometry_spindledir": 'CW',
             "geometry_dwell": False,
             "geometry_dwell": False,
             "geometry_dwelltime": 1,
             "geometry_dwelltime": 1,
             "geometry_ppname_g": 'default',
             "geometry_ppname_g": 'default',
@@ -769,6 +785,9 @@ class App(QtCore.QObject):
             "geometry_segx": 0.0,
             "geometry_segx": 0.0,
             "geometry_segy": 0.0,
             "geometry_segy": 0.0,
 
 
+            # Geometry Editor
+            "geometry_editor_sel_limit": 30,
+
             # CNC Job General
             # CNC Job General
             "cncjob_plot": True,
             "cncjob_plot": True,
             "cncjob_plot_kind": 'all',
             "cncjob_plot_kind": 'all',
@@ -942,6 +961,7 @@ class App(QtCore.QObject):
             "excellon_travelz": self.ui.excellon_options_form.excellon_opt_group.travelz_entry,
             "excellon_travelz": self.ui.excellon_options_form.excellon_opt_group.travelz_entry,
             "excellon_feedrate": self.ui.excellon_options_form.excellon_opt_group.feedrate_entry,
             "excellon_feedrate": self.ui.excellon_options_form.excellon_opt_group.feedrate_entry,
             "excellon_spindlespeed": self.ui.excellon_options_form.excellon_opt_group.spindlespeed_entry,
             "excellon_spindlespeed": self.ui.excellon_options_form.excellon_opt_group.spindlespeed_entry,
+            "excellon_spindledir": self.ui.excellon_options_form.excellon_opt_group.spindledir_radio,
             "excellon_dwell": self.ui.excellon_options_form.excellon_opt_group.dwell_cb,
             "excellon_dwell": self.ui.excellon_options_form.excellon_opt_group.dwell_cb,
             "excellon_dwelltime": self.ui.excellon_options_form.excellon_opt_group.dwelltime_entry,
             "excellon_dwelltime": self.ui.excellon_options_form.excellon_opt_group.dwelltime_entry,
             "excellon_toolchange": self.ui.excellon_options_form.excellon_opt_group.toolchange_cb,
             "excellon_toolchange": self.ui.excellon_options_form.excellon_opt_group.toolchange_cb,
@@ -963,6 +983,7 @@ class App(QtCore.QObject):
             "geometry_feedrate": self.ui.geometry_options_form.geometry_opt_group.cncfeedrate_entry,
             "geometry_feedrate": self.ui.geometry_options_form.geometry_opt_group.cncfeedrate_entry,
             "geometry_feedrate_z": self.ui.geometry_options_form.geometry_opt_group.cncplunge_entry,
             "geometry_feedrate_z": self.ui.geometry_options_form.geometry_opt_group.cncplunge_entry,
             "geometry_spindlespeed": self.ui.geometry_options_form.geometry_opt_group.cncspindlespeed_entry,
             "geometry_spindlespeed": self.ui.geometry_options_form.geometry_opt_group.cncspindlespeed_entry,
+            "geometry_spindledir": self.ui.geometry_options_form.geometry_opt_group.spindledir_radio,
             "geometry_dwell": self.ui.geometry_options_form.geometry_opt_group.dwell_cb,
             "geometry_dwell": self.ui.geometry_options_form.geometry_opt_group.dwell_cb,
             "geometry_dwelltime": self.ui.geometry_options_form.geometry_opt_group.dwelltime_entry,
             "geometry_dwelltime": self.ui.geometry_options_form.geometry_opt_group.dwelltime_entry,
             "geometry_ppname_g": self.ui.geometry_options_form.geometry_opt_group.pp_geometry_name_cb,
             "geometry_ppname_g": self.ui.geometry_options_form.geometry_opt_group.pp_geometry_name_cb,
@@ -1065,6 +1086,7 @@ class App(QtCore.QObject):
             "excellon_feedrate": 3.0,
             "excellon_feedrate": 3.0,
             "excellon_feedrate_rapid": 3.0,
             "excellon_feedrate_rapid": 3.0,
             "excellon_spindlespeed": None,
             "excellon_spindlespeed": None,
+            "excellon_spindledir": 'CW',
             "excellon_dwell": True,
             "excellon_dwell": True,
             "excellon_dwelltime": 1000,
             "excellon_dwelltime": 1000,
             "excellon_toolchange": False,
             "excellon_toolchange": False,
@@ -1085,6 +1107,7 @@ class App(QtCore.QObject):
             "geometry_feedrate_z": 3.0,
             "geometry_feedrate_z": 3.0,
             "geometry_feedrate_rapid": 3.0,
             "geometry_feedrate_rapid": 3.0,
             "geometry_spindlespeed": None,
             "geometry_spindlespeed": None,
+            "geometry_spindledir": 'CW',
             "geometry_dwell": True,
             "geometry_dwell": True,
             "geometry_dwelltime": 1000,
             "geometry_dwelltime": 1000,
             "geometry_cnctooldia": 0.016,
             "geometry_cnctooldia": 0.016,
@@ -1882,7 +1905,7 @@ class App(QtCore.QObject):
                          'dim', 'mil', 'grb', 'top', 'bot', 'smt', 'smb', 'sst', 'ssb', 'spt', 'spb', 'pho', 'gdo',
                          'dim', 'mil', 'grb', 'top', 'bot', 'smt', 'smb', 'sst', 'ssb', 'spt', 'spb', 'pho', 'gdo',
                          'art', 'gbd', 'gb0', 'gb1', 'gb2', 'gb3', 'g4', 'gb5', 'gb6', 'gb7', 'gb8', 'gb9'
                          'art', 'gbd', 'gb0', 'gb1', 'gb2', 'gb3', 'g4', 'gb5', 'gb6', 'gb7', 'gb8', 'gb9'
                          ]
                          ]
-        self.exc_list = ['drl', 'txt', 'xln', 'drd', 'tap', 'exc']
+        self.exc_list = ['drl', 'txt', 'xln', 'drd', 'tap', 'exc', 'ncd']
         self.gcode_list = ['nc', 'ncc', 'tap', 'gcode', 'cnc', 'ecs', 'fnc', 'dnc', 'ncg', 'gc', 'fan', 'fgc', 'din',
         self.gcode_list = ['nc', 'ncc', 'tap', 'gcode', 'cnc', 'ecs', 'fnc', 'dnc', 'ncg', 'gc', 'fan', 'fgc', 'din',
                       'xpi', 'hnc', 'h', 'i', 'ncp', 'min', 'gcd', 'rol', 'mpr', 'ply', 'out', 'eia', 'plt', 'sbp',
                       'xpi', 'hnc', 'h', 'i', 'ncp', 'min', 'gcd', 'rol', 'mpr', 'ply', 'out', 'eia', 'plt', 'sbp',
                       'mpf']
                       'mpf']
@@ -2264,30 +2287,18 @@ class App(QtCore.QObject):
                             self.inform.emit(_("[WARNING] Object empty after edit."))
                             self.inform.emit(_("[WARNING] Object empty after edit."))
                             log.debug("App.editor2object() --> Geometry --> %s" % str(e))
                             log.debug("App.editor2object() --> Geometry --> %s" % str(e))
                     elif isinstance(edited_obj, FlatCAMGerber):
                     elif isinstance(edited_obj, FlatCAMGerber):
-                        new_obj = self.collection.get_active()
                         obj_type = "Gerber"
                         obj_type = "Gerber"
                         if cleanup is None:
                         if cleanup is None:
-                            self.grb_editor.update_fcgerber(edited_obj)
-                            self.grb_editor.update_options(new_obj)
+                            self.grb_editor.update_fcgerber()
+                            self.grb_editor.update_options(edited_obj)
                         self.grb_editor.deactivate_grb_editor()
                         self.grb_editor.deactivate_grb_editor()
 
 
                         # delete the old object (the source object) if it was an empty one
                         # delete the old object (the source object) if it was an empty one
-                        if not edited_obj.solid_geometry:
+                        if len(edited_obj.solid_geometry) == 0:
                             old_name = edited_obj.options['name']
                             old_name = edited_obj.options['name']
                             self.collection.set_active(old_name)
                             self.collection.set_active(old_name)
                             self.collection.delete_active()
                             self.collection.delete_active()
-                        else:
-                            # update the geo object options so it is including the bounding box values
-                            # but don't do this for objects that are made out of empty source objects, it will fail
-                            try:
-                                xmin, ymin, xmax, ymax = new_obj.bounds()
-                                new_obj.options['xmin'] = xmin
-                                new_obj.options['ymin'] = ymin
-                                new_obj.options['xmax'] = xmax
-                                new_obj.options['ymax'] = ymax
-                            except Exception as e:
-                                self.inform.emit(_("[WARNING] Object empty after edit."))
-                                log.debug("App.editor2object() --> Gerber --> %s" % str(e))
+
                     elif isinstance(edited_obj, FlatCAMExcellon):
                     elif isinstance(edited_obj, FlatCAMExcellon):
                         obj_type = "Excellon"
                         obj_type = "Excellon"
                         if cleanup is None:
                         if cleanup is None:
@@ -2780,7 +2791,8 @@ class App(QtCore.QObject):
             except:
             except:
                 self.inform.emit(_("[ERROR_NOTCL] Failed to write defaults to file."))
                 self.inform.emit(_("[ERROR_NOTCL] Failed to write defaults to file."))
                 return
                 return
-
+        if self.defaults["global_open_style"] is False:
+            self.file_opened.emit("preferences", filename)
         self.file_saved.emit("preferences", filename)
         self.file_saved.emit("preferences", filename)
         self.inform.emit("[success] Exported Defaults to %s" % filename)
         self.inform.emit("[success] Exported Defaults to %s" % filename)
 
 
@@ -2984,6 +2996,7 @@ class App(QtCore.QObject):
             grb_obj.multigeo = False
             grb_obj.multigeo = False
             grb_obj.follow = False
             grb_obj.follow = False
             grb_obj.apertures = {}
             grb_obj.apertures = {}
+            grb_obj.solid_geometry = []
 
 
             try:
             try:
                 grb_obj.options['xmin'] = 0
                 grb_obj.options['xmin'] = 0
@@ -4401,8 +4414,8 @@ class App(QtCore.QObject):
                 return
                 return
 
 
         # Just for adding it to the recent files list.
         # Just for adding it to the recent files list.
-        self.file_opened.emit("cncjob", filename)
-
+        if self.defaults["global_open_style"] is False:
+            self.file_opened.emit("cncjob", filename)
         self.file_saved.emit("cncjob", filename)
         self.file_saved.emit("cncjob", filename)
         self.inform.emit(_("Saved to: %s") % filename)
         self.inform.emit(_("Saved to: %s") % filename)
 
 
@@ -4797,13 +4810,17 @@ class App(QtCore.QObject):
     def convert_any2gerber(self):
     def convert_any2gerber(self):
         self.report_usage("convert_any2gerber()")
         self.report_usage("convert_any2gerber()")
 
 
-        def initialize(obj_init, app):
+        def initialize_geometry(obj_init, app):
             apertures = {}
             apertures = {}
             apid = 0
             apid = 0
 
 
             apertures[str(apid)] = {}
             apertures[str(apid)] = {}
-            apertures[str(apid)]['solid_geometry'] = []
-            apertures[str(apid)]['solid_geometry'] = deepcopy(obj.solid_geometry)
+            apertures[str(apid)]['geometry'] = []
+            for obj_orig in obj.solid_geometry:
+                new_elem = dict()
+                new_elem['solid'] = obj_orig
+                new_elem['follow'] = obj_orig.exterior
+                apertures[str(apid)]['geometry'].append(deepcopy(new_elem))
             apertures[str(apid)]['size'] = 0.0
             apertures[str(apid)]['size'] = 0.0
             apertures[str(apid)]['type'] = 'C'
             apertures[str(apid)]['type'] = 'C'
 
 
@@ -4816,9 +4833,12 @@ class App(QtCore.QObject):
             apid = 10
             apid = 10
             for tool in obj.tools:
             for tool in obj.tools:
                 apertures[str(apid)] = {}
                 apertures[str(apid)] = {}
-                apertures[str(apid)]['solid_geometry'] = []
+                apertures[str(apid)]['geometry'] = []
                 for geo in obj.tools[tool]['solid_geometry']:
                 for geo in obj.tools[tool]['solid_geometry']:
-                    apertures[str(apid)]['solid_geometry'].append(geo)
+                    new_el = dict()
+                    new_el['solid'] = geo
+                    new_el['follow'] = geo.exterior
+                    apertures[str(apid)]['geometry'].append(deepcopy(new_el))
 
 
                 apertures[str(apid)]['size'] = float(obj.tools[tool]['C'])
                 apertures[str(apid)]['size'] = float(obj.tools[tool]['C'])
                 apertures[str(apid)]['type'] = 'C'
                 apertures[str(apid)]['type'] = 'C'
@@ -4827,8 +4847,8 @@ class App(QtCore.QObject):
             # create solid_geometry
             # create solid_geometry
             solid_geometry = []
             solid_geometry = []
             for apid in apertures:
             for apid in apertures:
-                for geo in apertures[apid]['solid_geometry']:
-                    solid_geometry.append(geo)
+                for geo_el in apertures[apid]['geometry']:
+                    solid_geometry.append(geo_el['solid'])
 
 
             solid_geometry = MultiPolygon(solid_geometry)
             solid_geometry = MultiPolygon(solid_geometry)
             solid_geometry = solid_geometry.buffer(0.0000001)
             solid_geometry = solid_geometry.buffer(0.0000001)
@@ -4850,8 +4870,10 @@ class App(QtCore.QObject):
             try:
             try:
                 if isinstance(obj, FlatCAMExcellon):
                 if isinstance(obj, FlatCAMExcellon):
                     self.new_object("gerber", str(obj_name) + "_conv", initialize_excellon)
                     self.new_object("gerber", str(obj_name) + "_conv", initialize_excellon)
+                elif isinstance(obj, FlatCAMGeometry):
+                    self.new_object("gerber", str(obj_name) + "_conv", initialize_geometry)
                 else:
                 else:
-                    self.new_object("gerber", str(obj_name) + "_conv", initialize)
+                    log.warning("App.convert_any2gerber --> This is no vaild object for conversion.")
 
 
             except Exception as e:
             except Exception as e:
                 return "Operation failed: %s" % str(e)
                 return "Operation failed: %s" % str(e)
@@ -5923,7 +5945,7 @@ class App(QtCore.QObject):
         self.report_usage("on_fileopenexcellon")
         self.report_usage("on_fileopenexcellon")
         App.log.debug("on_fileopenexcellon()")
         App.log.debug("on_fileopenexcellon()")
 
 
-        _filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc);;" \
+        _filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc *.ncd);;" \
                    "All Files (*.*)"
                    "All Files (*.*)"
 
 
         try:
         try:
@@ -6073,6 +6095,8 @@ class App(QtCore.QObject):
             return
             return
         else:
         else:
             self.export_svg(name, filename)
             self.export_svg(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
 
 
     def on_file_exportpng(self):
     def on_file_exportpng(self):
@@ -6102,6 +6126,8 @@ class App(QtCore.QObject):
             return
             return
         else:
         else:
             write_png(filename, data)
             write_png(filename, data)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("png", filename)
             self.file_saved.emit("png", filename)
             self.file_saved.emit("png", filename)
 
 
     def on_file_savegerber(self):
     def on_file_savegerber(self):
@@ -6141,6 +6167,8 @@ class App(QtCore.QObject):
             return
             return
         else:
         else:
             self.save_source_file(name, filename)
             self.save_source_file(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("Gerber", filename)
             self.file_saved.emit("Gerber", filename)
             self.file_saved.emit("Gerber", filename)
 
 
     def on_file_saveexcellon(self):
     def on_file_saveexcellon(self):
@@ -6180,6 +6208,8 @@ class App(QtCore.QObject):
             return
             return
         else:
         else:
             self.save_source_file(name, filename)
             self.save_source_file(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("Excellon", filename)
             self.file_saved.emit("Excellon", filename)
             self.file_saved.emit("Excellon", filename)
 
 
     def on_file_exportexcellon(self):
     def on_file_exportexcellon(self):
@@ -6219,6 +6249,8 @@ class App(QtCore.QObject):
             return
             return
         else:
         else:
             self.export_excellon(name, filename)
             self.export_excellon(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("Excellon", filename)
             self.file_saved.emit("Excellon", filename)
             self.file_saved.emit("Excellon", filename)
 
 
     def on_file_exportgerber(self):
     def on_file_exportgerber(self):
@@ -6258,6 +6290,8 @@ class App(QtCore.QObject):
             return
             return
         else:
         else:
             self.export_gerber(name, filename)
             self.export_gerber(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("Gerber", filename)
             self.file_saved.emit("Gerber", filename)
             self.file_saved.emit("Gerber", filename)
 
 
     def on_file_exportdxf(self):
     def on_file_exportdxf(self):
@@ -6309,6 +6343,8 @@ class App(QtCore.QObject):
             return
             return
         else:
         else:
             self.export_dxf(name, filename)
             self.export_dxf(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("DXF", filename)
             self.file_saved.emit("DXF", filename)
             self.file_saved.emit("DXF", filename)
 
 
     def on_file_importsvg(self, type_of_obj):
     def on_file_importsvg(self, type_of_obj):
@@ -6563,8 +6599,8 @@ class App(QtCore.QObject):
         else:
         else:
             self.worker_task.emit({'fcn': self.save_project,
             self.worker_task.emit({'fcn': self.save_project,
                                    'params': [self.project_filename]})
                                    'params': [self.project_filename]})
-
-            self.file_opened.emit("project", self.project_filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("project", self.project_filename)
             self.file_saved.emit("project", self.project_filename)
             self.file_saved.emit("project", self.project_filename)
 
 
         self.should_we_save = False
         self.should_we_save = False
@@ -6609,8 +6645,8 @@ class App(QtCore.QObject):
             self.save_project(filename, quit)
             self.save_project(filename, quit)
 
 
         # self.save_project(filename)
         # self.save_project(filename)
-        self.file_opened.emit("project", filename)
-
+        if self.defaults["global_open_style"] is False:
+            self.file_opened.emit("project", filename)
         self.file_saved.emit("project", filename)
         self.file_saved.emit("project", filename)
         if not make_copy:
         if not make_copy:
             self.project_filename = filename
             self.project_filename = filename
@@ -6668,7 +6704,8 @@ class App(QtCore.QObject):
             svgcode = parse_xml_string(svg_elem)
             svgcode = parse_xml_string(svg_elem)
             with open(filename, 'w') as fp:
             with open(filename, 'w') as fp:
                 fp.write(svgcode.toprettyxml())
                 fp.write(svgcode.toprettyxml())
-
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
             self.inform.emit(_("[success] SVG file exported to %s") % filename)
             self.inform.emit(_("[success] SVG file exported to %s") % filename)
 
 
@@ -6773,7 +6810,8 @@ class App(QtCore.QObject):
                 fp.write(doc.toprettyxml())
                 fp.write(doc.toprettyxml())
 
 
             self.progress.emit(100)
             self.progress.emit(100)
-
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
             self.inform.emit(_("[success] SVG file exported to %s") % filename)
             self.inform.emit(_("[success] SVG file exported to %s") % filename)
 
 
@@ -6887,7 +6925,8 @@ class App(QtCore.QObject):
             with open(filename, 'w') as fp:
             with open(filename, 'w') as fp:
                 fp.write(doc.toprettyxml())
                 fp.write(doc.toprettyxml())
             self.progress.emit(100)
             self.progress.emit(100)
-
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
             self.file_saved.emit("SVG", filename)
             self.inform.emit(_("[success] SVG file exported to %s") % filename)
             self.inform.emit(_("[success] SVG file exported to %s") % filename)
 
 
@@ -7037,7 +7076,8 @@ class App(QtCore.QObject):
 
 
                 with open(filename, 'w') as fp:
                 with open(filename, 'w') as fp:
                     fp.write(exported_excellon)
                     fp.write(exported_excellon)
-
+                if self.defaults["global_open_style"] is False:
+                    self.file_opened.emit("Excellon", filename)
                 self.file_saved.emit("Excellon", filename)
                 self.file_saved.emit("Excellon", filename)
                 self.inform.emit(_("[success] Excellon file exported to %s") % filename)
                 self.inform.emit(_("[success] Excellon file exported to %s") % filename)
             except Exception as e:
             except Exception as e:
@@ -7153,7 +7193,8 @@ class App(QtCore.QObject):
 
 
                 with open(filename, 'w') as fp:
                 with open(filename, 'w') as fp:
                     fp.write(exported_gerber)
                     fp.write(exported_gerber)
-
+                if self.defaults["global_open_style"] is False:
+                    self.file_opened.emit("Gerber", filename)
                 self.file_saved.emit("Gerber", filename)
                 self.file_saved.emit("Gerber", filename)
                 self.inform.emit(_("[success] Gerber file exported to %s") % filename)
                 self.inform.emit(_("[success] Gerber file exported to %s") % filename)
             except Exception as e:
             except Exception as e:
@@ -7211,7 +7252,8 @@ class App(QtCore.QObject):
             try:
             try:
                 dxf_code = obj.export_dxf()
                 dxf_code = obj.export_dxf()
                 dxf_code.saveas(filename)
                 dxf_code.saveas(filename)
-
+                if self.defaults["global_open_style"] is False:
+                    self.file_opened.emit("DXF", filename)
                 self.file_saved.emit("DXF", filename)
                 self.file_saved.emit("DXF", filename)
                 self.inform.emit(_("[success] DXF file exported to %s") % filename)
                 self.inform.emit(_("[success] DXF file exported to %s") % filename)
             except:
             except:

+ 211 - 120
FlatCAMObj.py

@@ -17,9 +17,9 @@ import itertools
 
 
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
+import builtins
 
 
 fcTranslate.apply_language('strings')
 fcTranslate.apply_language('strings')
-import builtins
 if '_' not in builtins.__dict__:
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
     _ = gettext.gettext
 
 
@@ -35,9 +35,9 @@ class ValidationError(Exception):
 
 
         self.errors = errors
         self.errors = errors
 
 
-########################################
-##            FlatCAMObj              ##
-########################################
+# #######################################
+# #            FlatCAMObj              ##
+# #######################################
 
 
 
 
 class FlatCAMObj(QtCore.QObject):
 class FlatCAMObj(QtCore.QObject):
@@ -122,7 +122,8 @@ class FlatCAMObj(QtCore.QObject):
                 try:
                 try:
                     setattr(self, attr, d[attr])
                     setattr(self, attr, d[attr])
                 except KeyError:
                 except KeyError:
-                    log.debug("FlatCAMObj.from_dict() --> KeyError: %s. Means that we are loading an old project that don't"
+                    log.debug("FlatCAMObj.from_dict() --> KeyError: %s. "
+                              "Means that we are loading an old project that don't"
                               "have all attributes in the latest FlatCAM." % str(attr))
                               "have all attributes in the latest FlatCAM." % str(attr))
                     pass
                     pass
 
 
@@ -203,8 +204,8 @@ class FlatCAMObj(QtCore.QObject):
         self.app.report_usage("obj_on_offset_button")
         self.app.report_usage("obj_on_offset_button")
 
 
         self.read_form()
         self.read_form()
-        vect = self.ui.offsetvector_entry.get_value()
-        self.offset(vect)
+        vector_val = self.ui.offsetvector_entry.get_value()
+        self.offset(vector_val)
         self.plot()
         self.plot()
         self.app.object_changed.emit(self)
         self.app.object_changed.emit(self)
 
 
@@ -219,9 +220,9 @@ class FlatCAMObj(QtCore.QObject):
     def on_skew_button_click(self):
     def on_skew_button_click(self):
         self.app.report_usage("obj_on_skew_button")
         self.app.report_usage("obj_on_skew_button")
         self.read_form()
         self.read_form()
-        xangle = self.ui.xangle_entry.get_value()
-        yangle = self.ui.yangle_entry.get_value()
-        self.skew(xangle, yangle)
+        x_angle = self.ui.xangle_entry.get_value()
+        y_angle = self.ui.yangle_entry.get_value()
+        self.skew(x_angle, y_angle)
         self.plot()
         self.plot()
         self.app.object_changed.emit(self)
         self.app.object_changed.emit(self)
 
 
@@ -420,7 +421,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                     if option is not 'name':
                     if option is not 'name':
                         try:
                         try:
                             grb_final.options[option] = grb.options[option]
                             grb_final.options[option] = grb.options[option]
-                        except:
+                        except KeyError:
                             log.warning("Failed to copy option.", option)
                             log.warning("Failed to copy option.", option)
 
 
                 try:
                 try:
@@ -440,10 +441,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                         # and finally made string because the apertures dict keys are strings
                         # and finally made string because the apertures dict keys are strings
                         max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1)
                         max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1)
                         grb_final.apertures[max_ap] = {}
                         grb_final.apertures[max_ap] = {}
-                        grb_final.apertures[max_ap]['solid_geometry'] = []
+                        grb_final.apertures[max_ap]['geometry'] = []
 
 
                         for k, v in grb.apertures[ap].items():
                         for k, v in grb.apertures[ap].items():
-                            grb_final.apertures[max_ap][k] = v
+                            grb_final.apertures[max_ap][k] = deepcopy(v)
 
 
         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)
@@ -685,7 +686,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         horizontal_header.setMinimumSectionSize(10)
         horizontal_header.setMinimumSectionSize(10)
         horizontal_header.setDefaultSectionSize(70)
         horizontal_header.setDefaultSectionSize(70)
         horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
         horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
+        horizontal_header.resizeSection(0, 27)
         horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
@@ -1096,8 +1097,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                     elif type(g) == Point:
                     elif type(g) == Point:
                         pass
                         pass
                     else:
                     else:
-                        for el in g:
-                            self.add_shape(shape=el, color=color,
+                        try:
+                            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'])
+                        except TypeError:
+                            self.add_shape(shape=g, color=color,
                                            face_color=random_color() if self.options['multicolored']
                                            face_color=random_color() if self.options['multicolored']
                                            else face_color, visible=self.options['plot'])
                                            else face_color, visible=self.options['plot'])
             else:
             else:
@@ -1155,17 +1161,16 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                 self.app.progress.emit(30)
                 self.app.progress.emit(30)
                 try:
                 try:
                     if aperture_to_plot_mark in self.apertures:
                     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(apid=aperture_to_plot_mark, shape=geo, color=color,
-                                                    face_color=color, visible=visibility)
-                            else:
-                                for el in geo:
-                                    self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color,
+                        for elem in self.apertures[aperture_to_plot_mark]['geometry']:
+                            if 'solid' in elem:
+                                geo = elem['solid']
+                                if type(geo) == Polygon or type(geo) == LineString:
+                                    self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color,
                                                         face_color=color, visible=visibility)
                                                         face_color=color, visible=visibility)
+                                else:
+                                    for el in geo:
+                                        self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color,
+                                                            face_color=color, visible=visibility)
 
 
                     self.mark_shapes[aperture_to_plot_mark].redraw()
                     self.mark_shapes[aperture_to_plot_mark].redraw()
                     self.app.progress.emit(100)
                     self.app.progress.emit(100)
@@ -1305,38 +1310,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         try:
         try:
             length = whole + fract
             length = whole + fract
             if '0' in self.apertures:
             if '0' in self.apertures:
-                if 'solid_geometry' in self.apertures['0']:
-                    for geo in self.apertures['0']['solid_geometry']:
-                        gerber_code += 'G36*\n'
-                        geo_coords = list(geo.exterior.coords)
-                        # first command is a move with pen-up D02 at the beginning of the geo
-                        if g_zeros == 'T':
-                            x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                           yform=y_formatted)
-                        else:
-                            x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                           yform=y_formatted)
-                        for coord in geo_coords[1:]:
-                            if g_zeros == 'T':
-                                x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                               yform=y_formatted)
-                            else:
-                                x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                               yform=y_formatted)
-                        gerber_code += 'D02*\n'
-                        gerber_code += 'G37*\n'
-
-                        clear_list = list(geo.interiors)
-                        if clear_list:
-                            gerber_code += '%LPC*%\n'
-                            for clear_geo in clear_list:
+                if 'geometry' in self.apertures['0']:
+                    for geo_elem in self.apertures['0']['geometry']:
+                        if 'solid' in geo_elem:
+                            geo = geo_elem['solid']
+                            if not geo.is_empty:
                                 gerber_code += 'G36*\n'
                                 gerber_code += 'G36*\n'
-                                geo_coords = list(clear_geo.coords)
-
+                                geo_coords = list(geo.exterior.coords)
                                 # first command is a move with pen-up D02 at the beginning of the geo
                                 # first command is a move with pen-up D02 at the beginning of the geo
                                 if g_zeros == 'T':
                                 if g_zeros == 'T':
                                     x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
                                     x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
@@ -1357,27 +1337,48 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                                                                                        yform=y_formatted)
                                                                                        yform=y_formatted)
                                 gerber_code += 'D02*\n'
                                 gerber_code += 'D02*\n'
                                 gerber_code += 'G37*\n'
                                 gerber_code += 'G37*\n'
-                            gerber_code += '%LPD*%\n'
 
 
-            for apid in self.apertures:
-                if apid == '0':
-                    continue
-                else:
-                    gerber_code += 'D%s*\n' % str(apid)
-
-                    if 'follow_geometry' in self.apertures[apid]:
-                        for geo in self.apertures[apid]['follow_geometry']:
-                            if isinstance(geo, Point):
-                                if g_zeros == 'T':
-                                    x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
-                                    gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                                else:
-                                    x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
-                                    gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                            else:
-                                geo_coords = list(geo.coords)
+                                clear_list = list(geo.interiors)
+                                if clear_list:
+                                    gerber_code += '%LPC*%\n'
+                                    for clear_geo in clear_list:
+                                        gerber_code += 'G36*\n'
+                                        geo_coords = list(clear_geo.coords)
+
+                                        # first command is a move with pen-up D02 at the beginning of the geo
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+
+                                        prev_coord = geo_coords[0]
+                                        for coord in geo_coords[1:]:
+                                            if coord != prev_coord:
+                                                if g_zeros == 'T':
+                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                                else:
+                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                            prev_coord = coord
+
+                                        gerber_code += 'D02*\n'
+                                        gerber_code += 'G37*\n'
+                                    gerber_code += '%LPD*%\n'
+                        if 'clear' in geo_elem:
+                            geo = geo_elem['clear']
+                            if not geo.is_empty:
+                                gerber_code += '%LPC*%\n'
+                                gerber_code += 'G36*\n'
+                                geo_coords = list(geo.exterior.coords)
                                 # first command is a move with pen-up D02 at the beginning of the geo
                                 # first command is a move with pen-up D02 at the beginning of the geo
                                 if g_zeros == 'T':
                                 if g_zeros == 'T':
                                     x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
                                     x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
@@ -1387,48 +1388,115 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                                     x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
                                     x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
                                     gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
                                     gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
                                                                                    yform=y_formatted)
                                                                                    yform=y_formatted)
+
+                                prev_coord = geo_coords[0]
                                 for coord in geo_coords[1:]:
                                 for coord in geo_coords[1:]:
-                                    if g_zeros == 'T':
-                                        x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                       yform=y_formatted)
+                                    if coord != prev_coord:
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                            gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                            gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                    prev_coord = coord
+
+                                gerber_code += 'D02*\n'
+                                gerber_code += 'G37*\n'
+                                gerber_code += '%LPD*%\n'
+
+            for apid in self.apertures:
+                if apid == '0':
+                    continue
+                else:
+                    gerber_code += 'D%s*\n' % str(apid)
+                    if 'geometry' in self.apertures[apid]:
+                        for geo_elem in self.apertures[apid]['geometry']:
+                            if 'follow' in geo_elem:
+                                geo = geo_elem['follow']
+                                if not geo.is_empty:
+                                    if isinstance(geo, Point):
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
                                     else:
                                     else:
-                                        x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                       yform=y_formatted)
-                    if 'clear_follow_geometry' in self.apertures[apid]:
-                        gerber_code += '%LPC*%\n'
-                        for geo in self.apertures[apid]['clear_follow_geometry']:
-                            if isinstance(geo, Point):
-                                if g_zeros == 'T':
-                                    x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
-                                    gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                                else:
-                                    x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
-                                    gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                            else:
-                                geo_coords = list(geo.coords)
-                                # first command is a move with pen-up D02 at the beginning of the geo
-                                if g_zeros == 'T':
-                                    x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                                else:
-                                    x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
-                                    gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
-                                                                                   yform=y_formatted)
-                                for coord in geo_coords[1:]:
-                                    if g_zeros == 'T':
-                                        x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
-                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                       yform=y_formatted)
+                                        geo_coords = list(geo.coords)
+                                        # first command is a move with pen-up D02 at the beginning of the geo
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+
+                                        prev_coord = geo_coords[0]
+                                        for coord in geo_coords[1:]:
+                                            if coord != prev_coord:
+                                                if g_zeros == 'T':
+                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                                else:
+                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                            prev_coord = coord
+
+                                        # gerber_code += "D02*\n"
+
+                            if 'clear' in geo_elem:
+                                gerber_code += '%LPC*%\n'
+
+                                geo = geo_elem['clear']
+                                if not geo.is_empty:
+                                    if isinstance(geo, Point):
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
+                                            gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
                                     else:
                                     else:
-                                        x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
-                                        gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
-                                                                                       yform=y_formatted)
-                        gerber_code += '%LPD*%\n'
+                                        geo_coords = list(geo.coords)
+                                        # first command is a move with pen-up D02 at the beginning of the geo
+                                        if g_zeros == 'T':
+                                            x_formatted, y_formatted = tz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+                                        else:
+                                            x_formatted, y_formatted = lz_format(
+                                                geo_coords[0][0], geo_coords[0][1], factor)
+                                            gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
+                                                                                           yform=y_formatted)
+
+                                        prev_coord = geo_coords[0]
+                                        for coord in geo_coords[1:]:
+                                            if coord != prev_coord:
+                                                if g_zeros == 'T':
+                                                    x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                   yform=y_formatted)
+                                                else:
+                                                    x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
+                                                    gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
+                                                                                                  yform=y_formatted)
+
+                                            prev_coord = coord
+                                        # gerber_code += "D02*\n"
+                                    gerber_code += '%LPD*%\n'
 
 
         except Exception as e:
         except Exception as e:
             log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> %s" % str(e))
             log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> %s" % str(e))
@@ -2449,6 +2517,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             self.ui.feedrate_probe_entry.setVisible(False)
             self.ui.feedrate_probe_entry.setVisible(False)
             self.ui.feedrate_probe_label.hide()
             self.ui.feedrate_probe_label.hide()
 
 
+        if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
+            self.ui.feedrate_rapid_label.show()
+            self.ui.feedrate_rapid_entry.show()
+        else:
+            self.ui.feedrate_rapid_label.hide()
+            self.ui.feedrate_rapid_entry.hide()
+
     def on_create_cncjob_button_click(self, *args):
     def on_create_cncjob_button_click(self, *args):
         self.app.report_usage("excellon_on_create_cncjob_button")
         self.app.report_usage("excellon_on_create_cncjob_button")
         self.read_form()
         self.read_form()
@@ -2502,8 +2577,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
             job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
 
 
             job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] else None
             job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] else None
+            job_obj.spindledir = self.app.defaults['excellon_spindledir']
             job_obj.dwell = self.options["dwell"]
             job_obj.dwell = self.options["dwell"]
             job_obj.dwelltime = float(self.options["dwelltime"])
             job_obj.dwelltime = float(self.options["dwelltime"])
+
             job_obj.pp_excellon_name = pp_excellon_name
             job_obj.pp_excellon_name = pp_excellon_name
 
 
             job_obj.toolchange_xy_type = "excellon"
             job_obj.toolchange_xy_type = "excellon"
@@ -4093,6 +4170,13 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.feedrate_probe_entry.setVisible(False)
             self.ui.feedrate_probe_entry.setVisible(False)
             self.ui.feedrate_probe_label.hide()
             self.ui.feedrate_probe_label.hide()
 
 
+        if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
+            self.ui.fr_rapidlabel.show()
+            self.ui.cncfeedrate_rapid_entry.show()
+        else:
+            self.ui.fr_rapidlabel.hide()
+            self.ui.cncfeedrate_rapid_entry.hide()
+
     def on_generatecnc_button_click(self, *args):
     def on_generatecnc_button_click(self, *args):
 
 
         self.app.report_usage("geometry_on_generatecnc_button")
         self.app.report_usage("geometry_on_generatecnc_button")
@@ -4350,6 +4434,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     'offset_value': tool_offset
                     'offset_value': tool_offset
                 })
                 })
 
 
+                spindledir = self.app.defaults['geometry_spindledir']
+
                 job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
                 job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
                 job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
                 job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
 
 
@@ -4369,7 +4455,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     self, tooldia=tooldia_val, offset=tool_offset, tolerance=0.0005,
                     self, tooldia=tooldia_val, offset=tool_offset, tolerance=0.0005,
                     z_cut=z_cut, z_move=z_move,
                     z_cut=z_cut, z_move=z_move,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
-                    spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
+                    spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
                     multidepth=multidepth, depthpercut=depthpercut,
                     multidepth=multidepth, depthpercut=depthpercut,
                     extracut=extracut, startz=startz, endz=endz,
                     extracut=extracut, startz=startz, endz=endz,
                     toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
                     toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
@@ -4593,12 +4679,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
                 app_obj.progress.emit(40)
                 app_obj.progress.emit(40)
 
 
+                spindledir = self.app.defaults['geometry_spindledir']
+
                 tool_solid_geometry = self.tools[current_uid]['solid_geometry']
                 tool_solid_geometry = self.tools[current_uid]['solid_geometry']
                 res = job_obj.generate_from_multitool_geometry(
                 res = job_obj.generate_from_multitool_geometry(
                     tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset,
                     tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset,
                     tolerance=0.0005, z_cut=z_cut, z_move=z_move,
                     tolerance=0.0005, z_cut=z_cut, z_move=z_move,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
-                    spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
+                    spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
                     multidepth=multidepth, depthpercut=depthpercut,
                     multidepth=multidepth, depthpercut=depthpercut,
                     extracut=extracut, startz=startz, endz=endz,
                     extracut=extracut, startz=startz, endz=endz,
                     toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
                     toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
@@ -5468,6 +5556,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         if gc == 'fail':
         if gc == 'fail':
             return
             return
 
 
+        if self.app.defaults["global_open_style"] is False:
+            self.app.file_opened.emit("gcode", filename)
         self.app.file_saved.emit("gcode", filename)
         self.app.file_saved.emit("gcode", filename)
         self.app.inform.emit(_("[success] Machine Code file saved to: %s") % filename)
         self.app.inform.emit(_("[success] Machine Code file saved to: %s") % filename)
 
 
@@ -5687,7 +5777,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         # lines = StringIO(self.gcode)
         # lines = StringIO(self.gcode)
         lines = StringIO(g)
         lines = StringIO(g)
 
 
-        ## Write
+        # Write
         if filename is not None:
         if filename is not None:
             try:
             try:
                 with open(filename, 'w') as f:
                 with open(filename, 'w') as f:
@@ -5701,7 +5791,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
                 return
                 return
         elif to_file is False:
         elif to_file is False:
             # Just for adding it to the recent files list.
             # Just for adding it to the recent files list.
-            self.app.file_opened.emit("cncjob", filename)
+            if self.app.defaults["global_open_style"] is False:
+                self.app.file_opened.emit("cncjob", filename)
             self.app.file_saved.emit("cncjob", filename)
             self.app.file_saved.emit("cncjob", filename)
 
 
             self.app.inform.emit("[success] Saved to: " + filename)
             self.app.inform.emit("[success] Saved to: " + filename)

+ 83 - 1
README.md

@@ -9,10 +9,92 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+22.05.2019
+
+- Geo Editor - added a new editor tool, Eraser
+- some PEP8 cleanup of the Geo Editor
+- fixed some selection issues in the new tool Eraser in Geometry Editor
+- updated the translation files
+
+21.05.2019
+
+- added the file extension .ncd to the Excellon file extension list
+- solved parsing issue for Excellon files generated by older Eagle versions (v6.x)
+- Gerber Editor: finished a new tool: Eraser. It will erase certain parts of Gerber geometries having the shape of a selected shape.
+
+20.05.2019
+
+- more PEP8 changes in Gerber editor
+- Gerber Editor - started to work on a new editor tool: Eraser
+
+19.05.2019
+
+- fixed the Circle Steps parameter for both Gerber and Geometry objects not being applied and instead the app internal defaults were used.
+- fixed the Tcl command Geocutout issue that gave an error when using the 4 or 8 value for gaps parameter
+- made wider the '#' column for Apertures Table for Gerber Object and for Gerber Editor; in this way numbers with 3 digits can be seen
+- PEP8 corrections in FlatCAMGrbEditor.py
+- added a selection limit parameter for Geometry Editor
+- added entries in Edit -> Preferences for the new parameter Selection limit for both the Gerber and Geometry Editors.
+- set the buttons in the lower part of the Preferences Window to have a preferred minimum width instead of fixed width
+- updated the translation files
+
+18.05.2019
+
+- added a new toggle option in Edit -> Preferences -> General Tab -> App Preferences -> "Open" Behavior. It controls which path is used when opening a new file. If checked the last saved path is used when saving files and the last opened path is used when opening files. If unchecked then the path for the last action (either open or save) is used.
+- fixed App.convert_any2gerber to work with the new Gerber apertures data structure
+- fixed Tool Sub to work with the new Gerber apertures data structure
+- fixed Tool PDF to work with the new Gerber apertures data structure
+
+17.05.2019
+
+- remade the Tool Cutout to work on panels
+- remade the Tool Cutout such that on multiple applications on the same object it will yield the same result
+- fixed an issue in the remade Cutout Tool where when applied on a single Gerber object, the Freeform Cutout produced no cutout Geometry object
+- remade the Properties Tool such that it works with the new Gerber data structure in the obj.apertures. Also changed the view for the Gerber object in Properties
+- fixed issue with false warning that the Gerber object has no geometry after an empty Gerber was edited and added geometry elements
+
+16.05.2019
+
+- Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy
+- added a new parameter/feature: now the spindle can work in clockwise mode (CW) or counter clockwise mode (CCW)
+
+15.05.2019
+
+- rewrited the Gerber Parser in camlib - success
+- moved the self.apertures[aperture]['geometry'] processing for clear_geometry (geometry made with Gerber LPC command) in Gerber Editor
+- Gerber Editor: fixed the Poligonize Tool to work with new geometric structure and took care of a special case
+- Gerber Export is fixed to work with the new Gerber object data structure and it now works also for Gerber objects edited in Gerber Editor
+- Gerber Editor: fixed units conversion for obj.apertures keys that require it
+- camlib Gerber parser - made sure that we don't loose goemetry in regions
+- Gerber Editor - made sure that for some tools the added geometry is clean (the coordinates are non repeating)
+- covered some possible issues in Gerber Export
+
+12.05.2019
+
+- some modifications to ToolCutout
+
+11.05.2019
+
+- fixed issue in camlib.CNCjob.generate_from_excellon_by_tool() in the drill path optimization algorithm selection when selecting the MH algorithm. The new API's for Google OR-tools required some changes and also the time parameter can be now just an integer therefore I modified the GUI
+- made the Feedrate Rapids parameter to depend on the type of postprocessor choosed. It will be showed only for a postprocessor which the name contain 'marlin' and for any postprocessor's that have 'custom' in the name
+- fixed the camlib.Gerber functions of mirror, scale, offset, skew and rotate to work with the new data structure for apertures geometry
+- fixed Gerber Editor selection to work with the new Gerber data structure in self.apertures
+- fixed Gerber Editor FCPad class to work with the new Gerber data structure in self.apertures
+- fixed camlib.Gerber issues related to what happen after parsing rectangular apertures 
+- wip in camblib.Gerber
+- completely converted the Gerber editor to the new data structure
+- Gerber Editor: added a threshold limit for how many elements a move selection can have. If above the threshold only a bounding box Poly will be painted on canvas as utility geometry.
+
 10.05.2019
 10.05.2019
 
 
-- made sure that only units toggle done in Edit -> Preferences will toggle the data in Preferences. THe menu entry Edit -> Toggle Units and the shortcut key 'Q' will change only the display units in the app
+- Gerber Editor - working in conversion to the new data format
+- made sure that only units toggle done in Edit -> Preferences will toggle the data in Preferences. The menu entry Edit -> Toggle Units and the shortcut key 'Q' will change only the display units in the app
 - optimized Transform tool
 - optimized Transform tool
+- RELEASE 8.916
+
+9.05.2019
+
+- reworked the Gerber parser
 
 
 8.05.2019
 8.05.2019
 
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 270 - 342
camlib.py


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 285 - 169
flatcamEditors/FlatCAMGeoEditor.py


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 445 - 108
flatcamEditors/FlatCAMGrbEditor.py


+ 188 - 70
flatcamGUI/FlatCAMGUI.py

@@ -686,6 +686,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.geo_add_text_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/text32.png'), _('Add Text'))
         self.geo_add_text_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/text32.png'), _('Add Text'))
         self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Add Buffer'))
         self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Add Buffer'))
         self.geo_add_paint_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/paint20_1.png'), _('Paint Shape'))
         self.geo_add_paint_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/paint20_1.png'), _('Paint Shape'))
+        self.geo_eraser_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
 
 
         self.geo_edit_toolbar.addSeparator()
         self.geo_edit_toolbar.addSeparator()
         self.geo_union_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/union32.png'), _('Polygon Union'))
         self.geo_union_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/union32.png'), _('Polygon Union'))
@@ -720,6 +721,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
         self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
         self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
         self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
+        self.aperture_eraser_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
+
         self.grb_edit_toolbar.addSeparator()
         self.grb_edit_toolbar.addSeparator()
         self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
         self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
         self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
         self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
@@ -922,7 +925,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         self.pref_import_button = QtWidgets.QPushButton()
         self.pref_import_button = QtWidgets.QPushButton()
         self.pref_import_button.setText(_("Import Preferences"))
         self.pref_import_button.setText(_("Import Preferences"))
-        self.pref_import_button.setFixedWidth(130)
+        self.pref_import_button.setMinimumWidth(130)
         self.pref_import_button.setToolTip(
         self.pref_import_button.setToolTip(
             _("Import a full set of FlatCAM settings from a file\n"
             _("Import a full set of FlatCAM settings from a file\n"
             "previously saved on HDD.\n\n"
             "previously saved on HDD.\n\n"
@@ -932,7 +935,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         self.pref_export_button = QtWidgets.QPushButton()
         self.pref_export_button = QtWidgets.QPushButton()
         self.pref_export_button.setText(_("Export Preferences"))
         self.pref_export_button.setText(_("Export Preferences"))
-        self.pref_export_button.setFixedWidth(130)
+        self.pref_export_button.setMinimumWidth(130)
         self.pref_export_button.setToolTip(
         self.pref_export_button.setToolTip(
            _( "Export a full set of FlatCAM settings in a file\n"
            _( "Export a full set of FlatCAM settings in a file\n"
             "that is saved on HDD."))
             "that is saved on HDD."))
@@ -940,7 +943,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         self.pref_open_button = QtWidgets.QPushButton()
         self.pref_open_button = QtWidgets.QPushButton()
         self.pref_open_button.setText(_("Open Pref Folder"))
         self.pref_open_button.setText(_("Open Pref Folder"))
-        self.pref_open_button.setFixedWidth(130)
+        self.pref_open_button.setMinimumWidth(130)
         self.pref_open_button.setToolTip(
         self.pref_open_button.setToolTip(
             _("Open the folder where FlatCAM save the preferences files."))
             _("Open the folder where FlatCAM save the preferences files."))
         self.pref_tab_bottom_layout_1.addWidget(self.pref_open_button)
         self.pref_tab_bottom_layout_1.addWidget(self.pref_open_button)
@@ -951,7 +954,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         self.pref_save_button = QtWidgets.QPushButton()
         self.pref_save_button = QtWidgets.QPushButton()
         self.pref_save_button.setText(_("Save Preferences"))
         self.pref_save_button.setText(_("Save Preferences"))
-        self.pref_save_button.setFixedWidth(130)
+        self.pref_save_button.setMinimumWidth(130)
         self.pref_save_button.setToolTip(
         self.pref_save_button.setToolTip(
             _("Save the current settings in the 'current_defaults' file\n"
             _("Save the current settings in the 'current_defaults' file\n"
             "which is the file storing the working default preferences."))
             "which is the file storing the working default preferences."))
@@ -1908,6 +1911,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'),
         self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'),
                                                                   _('Add Buffer'))
                                                                   _('Add Buffer'))
         self.geo_add_paint_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/paint20_1.png'), _('Paint Shape'))
         self.geo_add_paint_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/paint20_1.png'), _('Paint Shape'))
+        self.geo_eraser_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
+
 
 
         self.geo_edit_toolbar.addSeparator()
         self.geo_edit_toolbar.addSeparator()
         self.geo_union_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/union32.png'), _('Polygon Union'))
         self.geo_union_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/union32.png'), _('Polygon Union'))
@@ -1942,6 +1947,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
         self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
         self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
         self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
+        self.aperture_eraser_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
+
         self.grb_edit_toolbar.addSeparator()
         self.grb_edit_toolbar.addSeparator()
         self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
         self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
         self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
         self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
@@ -3149,6 +3156,9 @@ class GerberPreferencesUI(QtWidgets.QWidget):
         self.gerber_exp_group.setFixedWidth(230)
         self.gerber_exp_group.setFixedWidth(230)
         self.gerber_adv_opt_group = GerberAdvOptPrefGroupUI()
         self.gerber_adv_opt_group = GerberAdvOptPrefGroupUI()
         self.gerber_adv_opt_group.setFixedWidth(200)
         self.gerber_adv_opt_group.setFixedWidth(200)
+        self.gerber_editor_group = GerberEditorPrefGroupUI()
+        self.gerber_editor_group.setFixedWidth(200)
+
 
 
         self.vlay = QtWidgets.QVBoxLayout()
         self.vlay = QtWidgets.QVBoxLayout()
         self.vlay.addWidget(self.gerber_opt_group)
         self.vlay.addWidget(self.gerber_opt_group)
@@ -3157,6 +3167,7 @@ class GerberPreferencesUI(QtWidgets.QWidget):
         self.layout.addWidget(self.gerber_gen_group)
         self.layout.addWidget(self.gerber_gen_group)
         self.layout.addLayout(self.vlay)
         self.layout.addLayout(self.vlay)
         self.layout.addWidget(self.gerber_adv_opt_group)
         self.layout.addWidget(self.gerber_adv_opt_group)
+        self.layout.addWidget(self.gerber_editor_group)
 
 
         self.layout.addStretch()
         self.layout.addStretch()
 
 
@@ -3201,10 +3212,13 @@ class GeometryPreferencesUI(QtWidgets.QWidget):
         self.geometry_opt_group.setFixedWidth(250)
         self.geometry_opt_group.setFixedWidth(250)
         self.geometry_adv_opt_group = GeometryAdvOptPrefGroupUI()
         self.geometry_adv_opt_group = GeometryAdvOptPrefGroupUI()
         self.geometry_adv_opt_group.setFixedWidth(250)
         self.geometry_adv_opt_group.setFixedWidth(250)
+        self.geometry_editor_group = GeometryEditorPrefGroupUI()
+        self.geometry_editor_group.setFixedWidth(250)
 
 
         self.layout.addWidget(self.geometry_gen_group)
         self.layout.addWidget(self.geometry_gen_group)
         self.layout.addWidget(self.geometry_opt_group)
         self.layout.addWidget(self.geometry_opt_group)
         self.layout.addWidget(self.geometry_adv_opt_group)
         self.layout.addWidget(self.geometry_adv_opt_group)
+        self.layout.addWidget(self.geometry_editor_group)
 
 
         self.layout.addStretch()
         self.layout.addStretch()
 
 
@@ -3336,12 +3350,12 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.workspace_lbl = QtWidgets.QLabel(_('Workspace:'))
         self.workspace_lbl = QtWidgets.QLabel(_('Workspace:'))
         self.workspace_lbl.setToolTip(
         self.workspace_lbl.setToolTip(
            _( "Draw a delimiting rectangle on canvas.\n"
            _( "Draw a delimiting rectangle on canvas.\n"
-            "The purpose is to illustrate the limits for our work.")
+              "The purpose is to illustrate the limits for our work.")
         )
         )
         self.workspace_type_lbl = QtWidgets.QLabel(_('Wk. format:'))
         self.workspace_type_lbl = QtWidgets.QLabel(_('Wk. format:'))
         self.workspace_type_lbl.setToolTip(
         self.workspace_type_lbl.setToolTip(
            _( "Select the type of rectangle to be used on canvas,\n"
            _( "Select the type of rectangle to be used on canvas,\n"
-            "as valid workspace.")
+              "as valid workspace.")
         )
         )
         self.workspace_cb = FCCheckBox()
         self.workspace_cb = FCCheckBox()
         self.wk_cb = FCComboBox()
         self.wk_cb = FCComboBox()
@@ -3356,8 +3370,8 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.pf_color_label = QtWidgets.QLabel(_('Plot Fill:'))
         self.pf_color_label = QtWidgets.QLabel(_('Plot Fill:'))
         self.pf_color_label.setToolTip(
         self.pf_color_label.setToolTip(
            _( "Set the fill color for plotted objects.\n"
            _( "Set the fill color for plotted objects.\n"
-            "First 6 digits are the color and the last 2\n"
-            "digits are for alpha (transparency) level.")
+              "First 6 digits are the color and the last 2\n"
+              "digits are for alpha (transparency) level.")
         )
         )
         self.pf_color_entry = FCEntry()
         self.pf_color_entry = FCEntry()
         self.pf_color_button = QtWidgets.QPushButton()
         self.pf_color_button = QtWidgets.QPushButton()
@@ -3743,18 +3757,18 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # Units for FlatCAM
         # Units for FlatCAM
         self.unitslabel = QtWidgets.QLabel(_('<b>Units:</b>'))
         self.unitslabel = QtWidgets.QLabel(_('<b>Units:</b>'))
         self.unitslabel.setToolTip(_("The default value for FlatCAM units.\n"
         self.unitslabel.setToolTip(_("The default value for FlatCAM units.\n"
-                                   "Whatever is selected here is set every time\n"
-                                   "FLatCAM is started."))
+                                     "Whatever is selected here is set every time\n"
+                                     "FLatCAM is started."))
         self.units_radio = RadioSet([{'label': 'IN', 'value': 'IN'},
         self.units_radio = RadioSet([{'label': 'IN', 'value': 'IN'},
                                      {'label': 'MM', 'value': 'MM'}])
                                      {'label': 'MM', 'value': 'MM'}])
 
 
         # Application Level for FlatCAM
         # Application Level for FlatCAM
         self.app_level_label = QtWidgets.QLabel(_('<b>APP. LEVEL:</b>'))
         self.app_level_label = QtWidgets.QLabel(_('<b>APP. LEVEL:</b>'))
         self.app_level_label.setToolTip(_("Choose the default level of usage for FlatCAM.\n"
         self.app_level_label.setToolTip(_("Choose the default level of usage for FlatCAM.\n"
-                                        "BASIC level -> reduced functionality, best for beginner's.\n"
-                                        "ADVANCED level -> full functionality.\n\n"
-                                        "The choice here will influence the parameters in\n"
-                                        "the Selected Tab for all kinds of FlatCAM objects."))
+                                          "BASIC level -> reduced functionality, best for beginner's.\n"
+                                          "ADVANCED level -> full functionality.\n\n"
+                                          "The choice here will influence the parameters in\n"
+                                          "the Selected Tab for all kinds of FlatCAM objects."))
         self.app_level_radio = RadioSet([{'label': 'Basic', 'value': 'b'},
         self.app_level_radio = RadioSet([{'label': 'Basic', 'value': 'b'},
                                          {'label': 'Advanced', 'value': 'a'}])
                                          {'label': 'Advanced', 'value': 'a'}])
 
 
@@ -3776,24 +3790,24 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         self.shell_startup_label = QtWidgets.QLabel(_('Shell at StartUp:'))
         self.shell_startup_label = QtWidgets.QLabel(_('Shell at StartUp:'))
         self.shell_startup_label.setToolTip(
         self.shell_startup_label.setToolTip(
             _("Check this box if you want the shell to\n"
             _("Check this box if you want the shell to\n"
-            "start automatically at startup.")
+              "start automatically at startup.")
         )
         )
         self.shell_startup_cb = FCCheckBox(label='')
         self.shell_startup_cb = FCCheckBox(label='')
         self.shell_startup_cb.setToolTip(
         self.shell_startup_cb.setToolTip(
             _("Check this box if you want the shell to\n"
             _("Check this box if you want the shell to\n"
-            "start automatically at startup.")
+              "start automatically at startup.")
         )
         )
 
 
         # Version Check CB
         # Version Check CB
         self.version_check_label = QtWidgets.QLabel(_('Version Check:'))
         self.version_check_label = QtWidgets.QLabel(_('Version Check:'))
         self.version_check_label.setToolTip(
         self.version_check_label.setToolTip(
             _("Check this box if you want to check\n"
             _("Check this box if you want to check\n"
-            "for a new version automatically at startup.")
+              "for a new version automatically at startup.")
         )
         )
         self.version_check_cb = FCCheckBox(label='')
         self.version_check_cb = FCCheckBox(label='')
         self.version_check_cb.setToolTip(
         self.version_check_cb.setToolTip(
             _("Check this box if you want to check\n"
             _("Check this box if you want to check\n"
-            "for a new version automatically at startup.")
+              "for a new version automatically at startup.")
         )
         )
 
 
         # Send Stats CB
         # Send Stats CB
@@ -3805,7 +3819,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         self.send_stats_cb= FCCheckBox(label='')
         self.send_stats_cb= FCCheckBox(label='')
         self.send_stats_cb.setToolTip(
         self.send_stats_cb.setToolTip(
             _("Check this box if you agree to send anonymous\n"
             _("Check this box if you agree to send anonymous\n"
-            "stats automatically at startup, to help improve FlatCAM.")
+              "stats automatically at startup, to help improve FlatCAM.")
         )
         )
 
 
         self.ois_version_check = OptionalInputSection(self.version_check_cb, [self.send_stats_cb])
         self.ois_version_check = OptionalInputSection(self.version_check_cb, [self.send_stats_cb])
@@ -3813,8 +3827,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # Select mouse pan button
         # Select mouse pan button
         self.panbuttonlabel = QtWidgets.QLabel(_('<b>Pan Button:</b>'))
         self.panbuttonlabel = QtWidgets.QLabel(_('<b>Pan Button:</b>'))
         self.panbuttonlabel.setToolTip(_("Select the mouse button to use for panning:\n"
         self.panbuttonlabel.setToolTip(_("Select the mouse button to use for panning:\n"
-                                       "- MMB --> Middle Mouse Button\n"
-                                       "- RMB --> Right Mouse Button"))
+                                         "- MMB --> Middle Mouse Button\n"
+                                         "- RMB --> Right Mouse Button"))
         self.pan_button_radio = RadioSet([{'label': 'MMB', 'value': '3'},
         self.pan_button_radio = RadioSet([{'label': 'MMB', 'value': '3'},
                                      {'label': 'RMB', 'value': '2'}])
                                      {'label': 'RMB', 'value': '2'}])
 
 
@@ -3822,44 +3836,44 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         self.mselectlabel = QtWidgets.QLabel(_('<b>Multiple Sel:</b>'))
         self.mselectlabel = QtWidgets.QLabel(_('<b>Multiple Sel:</b>'))
         self.mselectlabel.setToolTip(_("Select the key used for multiple selection."))
         self.mselectlabel.setToolTip(_("Select the key used for multiple selection."))
         self.mselect_radio = RadioSet([{'label': 'CTRL', 'value': 'Control'},
         self.mselect_radio = RadioSet([{'label': 'CTRL', 'value': 'Control'},
-                                     {'label': 'SHIFT', 'value': 'Shift'}])
+                                       {'label': 'SHIFT', 'value': 'Shift'}])
 
 
         # Project at StartUp CB
         # Project at StartUp CB
         self.project_startup_label = QtWidgets.QLabel(_('Project at StartUp:'))
         self.project_startup_label = QtWidgets.QLabel(_('Project at StartUp:'))
         self.project_startup_label.setToolTip(
         self.project_startup_label.setToolTip(
             _("Check this box if you want the project/selected/tool tab area to\n"
             _("Check this box if you want the project/selected/tool tab area to\n"
-            "to be shown automatically at startup.")
+              "to be shown automatically at startup.")
         )
         )
         self.project_startup_cb = FCCheckBox(label='')
         self.project_startup_cb = FCCheckBox(label='')
         self.project_startup_cb.setToolTip(
         self.project_startup_cb.setToolTip(
             _("Check this box if you want the project/selected/tool tab area to\n"
             _("Check this box if you want the project/selected/tool tab area to\n"
-            "to be shown automatically at startup.")
+              "to be shown automatically at startup.")
         )
         )
 
 
         # Project autohide CB
         # Project autohide CB
         self.project_autohide_label = QtWidgets.QLabel(_('Project AutoHide:'))
         self.project_autohide_label = QtWidgets.QLabel(_('Project AutoHide:'))
         self.project_autohide_label.setToolTip(
         self.project_autohide_label.setToolTip(
            _( "Check this box if you want the project/selected/tool tab area to\n"
            _( "Check this box if you want the project/selected/tool tab area to\n"
-            "hide automatically when there are no objects loaded and\n"
-            "to show whenever a new object is created.")
+              "hide automatically when there are no objects loaded and\n"
+              "to show whenever a new object is created.")
         )
         )
         self.project_autohide_cb = FCCheckBox(label='')
         self.project_autohide_cb = FCCheckBox(label='')
         self.project_autohide_cb.setToolTip(
         self.project_autohide_cb.setToolTip(
             _("Check this box if you want the project/selected/tool tab area to\n"
             _("Check this box if you want the project/selected/tool tab area to\n"
-            "hide automatically when there are no objects loaded and\n"
-            "to show whenever a new object is created.")
+              "hide automatically when there are no objects loaded and\n"
+              "to show whenever a new object is created.")
         )
         )
 
 
         # Enable/Disable ToolTips globally
         # Enable/Disable ToolTips globally
         self.toggle_tooltips_label = QtWidgets.QLabel(_('<b>Enable ToolTips:</b>'))
         self.toggle_tooltips_label = QtWidgets.QLabel(_('<b>Enable ToolTips:</b>'))
         self.toggle_tooltips_label.setToolTip(
         self.toggle_tooltips_label.setToolTip(
            _( "Check this box if you want to have toolTips displayed\n"
            _( "Check this box if you want to have toolTips displayed\n"
-            "when hovering with mouse over items throughout the App.")
+              "when hovering with mouse over items throughout the App.")
         )
         )
         self.toggle_tooltips_cb = FCCheckBox(label='')
         self.toggle_tooltips_cb = FCCheckBox(label='')
         self.toggle_tooltips_cb.setToolTip(
         self.toggle_tooltips_cb.setToolTip(
            _( "Check this box if you want to have toolTips displayed\n"
            _( "Check this box if you want to have toolTips displayed\n"
-            "when hovering with mouse over items throughout the App.")
+              "when hovering with mouse over items throughout the App.")
         )
         )
         self.worker_number_label = QtWidgets.QLabel(_('Workers number:'))
         self.worker_number_label = QtWidgets.QLabel(_('Workers number:'))
         self.worker_number_label.setToolTip(
         self.worker_number_label.setToolTip(
@@ -3928,15 +3942,22 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # to the main layout of this TAB
         # to the main layout of this TAB
         self.layout.addLayout(self.form_box)
         self.layout.addLayout(self.form_box)
 
 
-        # hlay = QtWidgets.QHBoxLayout()
-        # self.layout.addLayout(hlay)
-        # hlay.addStretch()
+        # Save compressed project CB
+        self.open_style_cb = FCCheckBox(_('"Open" behavior'))
+        self.open_style_cb.setToolTip(
+            _("When checked the path for the last saved file is used when saving files,\n"
+              "and the path for the last opened file is used when opening files.\n\n"
+              "When unchecked the path for opening files is the one used last: either the\n"
+              "path for saving files or the path for opening files.")
+        )
+        # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        self.layout.addWidget(self.open_style_cb)
 
 
         # Save compressed project CB
         # Save compressed project CB
         self.save_type_cb = FCCheckBox(_('Save Compressed Project'))
         self.save_type_cb = FCCheckBox(_('Save Compressed Project'))
         self.save_type_cb.setToolTip(
         self.save_type_cb.setToolTip(
             _("Whether to save a compressed or uncompressed project.\n"
             _("Whether to save a compressed or uncompressed project.\n"
-            "When checked it will save a compressed FlatCAM project.")
+              "When checked it will save a compressed FlatCAM project.")
         )
         )
         # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         self.layout.addWidget(self.save_type_cb)
         self.layout.addWidget(self.save_type_cb)
@@ -3949,8 +3970,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         self.compress_label = QtWidgets.QLabel(_('Compression Level:'))
         self.compress_label = QtWidgets.QLabel(_('Compression Level:'))
         self.compress_label.setToolTip(
         self.compress_label.setToolTip(
             _("The level of compression used when saving\n"
             _("The level of compression used when saving\n"
-            "a FlatCAM project. Higher value means better compression\n"
-            "but require more RAM usage and more processing time.")
+              "a FlatCAM project. Higher value means better compression\n"
+              "but require more RAM usage and more processing time.")
         )
         )
         # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         self.compress_combo.addItems([str(i) for i in range(10)])
         self.compress_combo.addItems([str(i) for i in range(10)])
@@ -4180,28 +4201,28 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.aperture_table_visibility_cb, 1, 0)
         grid0.addWidget(self.aperture_table_visibility_cb, 1, 0)
 
 
         # Scale Aperture Factor
         # 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)
+        # 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
         # 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.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()
         self.layout.addStretch()
 
 
@@ -4300,6 +4321,40 @@ class GerberExpPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
         self.layout.addStretch()
 
 
 
 
+class GerberEditorPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
+        super(GerberEditorPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Gerber Editor")))
+
+        # Advanced Gerber Parameters
+        self.param_label = QtWidgets.QLabel(_("<b>Parameters:</b>"))
+        self.param_label.setToolTip(
+            _("A list of Gerber Editor parameters.")
+        )
+        self.layout.addWidget(self.param_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Selection Limit
+        self.sel_limit_label = QtWidgets.QLabel(_("Selection limit:"))
+        self.sel_limit_label.setToolTip(
+            _("Set the number of selected Gerber geometry\n"
+              "items above which the utility geometry\n"
+              "becomes just a selection rectangle.\n"
+              "Increases the performance when moving a\n"
+              "large number of geometric elements.")
+        )
+        self.sel_limit_entry = IntEntry()
+
+        grid0.addWidget(self.sel_limit_label, 0, 0)
+        grid0.addWidget(self.sel_limit_entry, 0, 1)
+
+        self.layout.addStretch()
+
+
 class ExcellonGenPrefGroupUI(OptionsGroupUI):
 class ExcellonGenPrefGroupUI(OptionsGroupUI):
 
 
     def __init__(self, parent=None):
     def __init__(self, parent=None):
@@ -4525,7 +4580,8 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
 
 
         )
         )
 
 
-        self.optimization_time_entry = LengthEntry()
+        self.optimization_time_entry = IntEntry()
+        self.optimization_time_entry.setValidator(QtGui.QIntValidator(0, 999))
         form_box_excellon.addRow(self.optimization_time_label, self.optimization_time_entry)
         form_box_excellon.addRow(self.optimization_time_label, self.optimization_time_entry)
 
 
         current_platform = platform.architecture()[0]
         current_platform = platform.architecture()[0]
@@ -4627,6 +4683,20 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.spindlespeed_entry = IntEntry(allow_empty=True)
         self.spindlespeed_entry = IntEntry(allow_empty=True)
         grid2.addWidget(self.spindlespeed_entry, 5, 1)
         grid2.addWidget(self.spindlespeed_entry, 5, 1)
 
 
+        # Spindle direction
+        spindle_dir_label = QtWidgets.QLabel(_('Spindle dir.:'))
+        spindle_dir_label.setToolTip(
+            _("This sets the direction that the spindle is rotating.\n"
+              "It can be either:\n"
+              "- CW = clockwise or\n"
+              "- CCW = counter clockwise")
+        )
+
+        self.spindledir_radio = RadioSet([{'label': 'CW', 'value': 'CW'},
+                                          {'label': 'CCW', 'value': 'CCW'}])
+        grid2.addWidget(spindle_dir_label, 6, 0)
+        grid2.addWidget(self.spindledir_radio, 6, 1)
+
         # Dwell
         # Dwell
         dwelllabel = QtWidgets.QLabel(_('Dwell:'))
         dwelllabel = QtWidgets.QLabel(_('Dwell:'))
         dwelllabel.setToolTip(
         dwelllabel.setToolTip(
@@ -4639,10 +4709,10 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         )
         )
         self.dwell_cb = FCCheckBox()
         self.dwell_cb = FCCheckBox()
         self.dwelltime_entry = FCEntry()
         self.dwelltime_entry = FCEntry()
-        grid2.addWidget(dwelllabel, 6, 0)
-        grid2.addWidget(self.dwell_cb, 6, 1)
-        grid2.addWidget(dwelltime, 7, 0)
-        grid2.addWidget(self.dwelltime_entry, 7, 1)
+        grid2.addWidget(dwelllabel, 7, 0)
+        grid2.addWidget(self.dwell_cb, 7, 1)
+        grid2.addWidget(dwelltime, 8, 0)
+        grid2.addWidget(self.dwelltime_entry, 8, 1)
 
 
         self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
         self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
 
@@ -4652,10 +4722,10 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
             _("The postprocessor file that dictates\n"
             _("The postprocessor file that dictates\n"
             "gcode output.")
             "gcode output.")
         )
         )
-        grid2.addWidget(pp_excellon_label, 8, 0)
+        grid2.addWidget(pp_excellon_label, 9, 0)
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
         self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
-        grid2.addWidget(self.pp_excellon_name_cb, 8, 1)
+        grid2.addWidget(self.pp_excellon_name_cb, 9, 1)
 
 
 
 
         #### Choose what to use for Gcode creation: Drills, Slots or Both
         #### Choose what to use for Gcode creation: Drills, Slots or Both
@@ -4669,8 +4739,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
         self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
                                           {'label': 'Slots', 'value': 'slots'},
                                           {'label': 'Slots', 'value': 'slots'},
                                           {'label': 'Both', 'value': 'both'}])
                                           {'label': 'Both', 'value': 'both'}])
-        grid2.addWidget(excellon_gcode_type_label, 9, 0)
-        grid2.addWidget(self.excellon_gcode_type_radio, 9, 1)
+        grid2.addWidget(excellon_gcode_type_label, 10, 0)
+        grid2.addWidget(self.excellon_gcode_type_radio, 10, 1)
 
 
         # until I decide to implement this feature those remain disabled
         # until I decide to implement this feature those remain disabled
         excellon_gcode_type_label.hide()
         excellon_gcode_type_label.hide()
@@ -5120,6 +5190,20 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.cncspindlespeed_entry = IntEntry(allow_empty=True)
         self.cncspindlespeed_entry = IntEntry(allow_empty=True)
         grid1.addWidget(self.cncspindlespeed_entry, 8, 1)
         grid1.addWidget(self.cncspindlespeed_entry, 8, 1)
 
 
+        # Spindle direction
+        spindle_dir_label = QtWidgets.QLabel(_('Spindle dir.:'))
+        spindle_dir_label.setToolTip(
+            _("This sets the direction that the spindle is rotating.\n"
+              "It can be either:\n"
+              "- CW = clockwise or\n"
+              "- CCW = counter clockwise")
+        )
+
+        self.spindledir_radio = RadioSet([{'label': 'CW', 'value': 'CW'},
+                                          {'label': 'CCW', 'value': 'CCW'}])
+        grid1.addWidget(spindle_dir_label, 9, 0)
+        grid1.addWidget(self.spindledir_radio, 9, 1)
+
         # Dwell
         # Dwell
         self.dwell_cb = FCCheckBox(label=_('Dwell:'))
         self.dwell_cb = FCCheckBox(label=_('Dwell:'))
         self.dwell_cb.setToolTip(
         self.dwell_cb.setToolTip(
@@ -5131,9 +5215,9 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
             _("Number of milliseconds for spindle to dwell.")
             _("Number of milliseconds for spindle to dwell.")
         )
         )
         self.dwelltime_entry = FCEntry()
         self.dwelltime_entry = FCEntry()
-        grid1.addWidget(self.dwell_cb, 9, 0)
-        grid1.addWidget(dwelltime, 10, 0)
-        grid1.addWidget(self.dwelltime_entry, 10, 1)
+        grid1.addWidget(self.dwell_cb, 10, 0)
+        grid1.addWidget(dwelltime, 11, 0)
+        grid1.addWidget(self.dwelltime_entry, 11, 1)
 
 
         self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
         self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
 
@@ -5143,10 +5227,10 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
             _("The postprocessor file that dictates\n"
             _("The postprocessor file that dictates\n"
             "Machine Code output.")
             "Machine Code output.")
         )
         )
-        grid1.addWidget(pp_label, 11, 0)
+        grid1.addWidget(pp_label, 12, 0)
         self.pp_geometry_name_cb = FCComboBox()
         self.pp_geometry_name_cb = FCComboBox()
         self.pp_geometry_name_cb.setFocusPolicy(Qt.StrongFocus)
         self.pp_geometry_name_cb.setFocusPolicy(Qt.StrongFocus)
-        grid1.addWidget(self.pp_geometry_name_cb, 11, 1)
+        grid1.addWidget(self.pp_geometry_name_cb, 12, 1)
 
 
         self.layout.addStretch()
         self.layout.addStretch()
 
 
@@ -5280,6 +5364,40 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
         self.layout.addStretch()
 
 
 
 
+class GeometryEditorPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
+        super(GeometryEditorPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Geometry Editor")))
+
+        # Advanced Geometry Parameters
+        self.param_label = QtWidgets.QLabel(_("<b>Parameters:</b>"))
+        self.param_label.setToolTip(
+            _("A list of Geometry Editor parameters.")
+        )
+        self.layout.addWidget(self.param_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Selection Limit
+        self.sel_limit_label = QtWidgets.QLabel(_("Selection limit:"))
+        self.sel_limit_label.setToolTip(
+            _("Set the number of selected geometry\n"
+              "items above which the utility geometry\n"
+              "becomes just a selection rectangle.\n"
+              "Increases the performance when moving a\n"
+              "large number of geometric elements.")
+        )
+        self.sel_limit_entry = IntEntry()
+
+        grid0.addWidget(self.sel_limit_label, 0, 0)
+        grid0.addWidget(self.sel_limit_entry, 0, 1)
+
+        self.layout.addStretch()
+
+
 class CNCJobGenPrefGroupUI(OptionsGroupUI):
 class CNCJobGenPrefGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
     def __init__(self, parent=None):
         # OptionsGroupUI.__init__(self, "CNC Job General Preferences", parent=None)
         # OptionsGroupUI.__init__(self, "CNC Job General Preferences", parent=None)

+ 6 - 0
flatcamGUI/ObjectUI.py

@@ -674,6 +674,9 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.feedrate_rapid_label, 7, 0)
         grid1.addWidget(self.feedrate_rapid_label, 7, 0)
         self.feedrate_rapid_entry = LengthEntry()
         self.feedrate_rapid_entry = LengthEntry()
         grid1.addWidget(self.feedrate_rapid_entry, 7, 1)
         grid1.addWidget(self.feedrate_rapid_entry, 7, 1)
+        # default values is to hide
+        self.feedrate_rapid_label.hide()
+        self.feedrate_rapid_entry.hide()
 
 
         # Spindlespeed
         # Spindlespeed
         spdlabel = QtWidgets.QLabel(_('Spindle speed:'))
         spdlabel = QtWidgets.QLabel(_('Spindle speed:'))
@@ -1188,6 +1191,9 @@ class GeometryObjectUI(ObjectUI):
         self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
         self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
         self.cncfeedrate_rapid_entry = LengthEntry()
         self.cncfeedrate_rapid_entry = LengthEntry()
         self.grid3.addWidget(self.cncfeedrate_rapid_entry, 12, 1)
         self.grid3.addWidget(self.cncfeedrate_rapid_entry, 12, 1)
+        # default values is to hide
+        self.fr_rapidlabel.hide()
+        self.cncfeedrate_rapid_entry.hide()
 
 
         # Cut over 1st point in path
         # Cut over 1st point in path
         self.extracut_cb = FCCheckBox(_('Cut over 1st pt'))
         self.extracut_cb = FCCheckBox(_('Cut over 1st pt'))

+ 305 - 173
flatcamTools/ToolCutOut.py

@@ -5,9 +5,9 @@ from shapely.geometry import box
 
 
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
+import builtins
 
 
 fcTranslate.apply_language('strings')
 fcTranslate.apply_language('strings')
-import builtins
 if '_' not in builtins.__dict__:
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
     _ = gettext.gettext
 
 
@@ -23,7 +23,7 @@ class CutOut(FlatCAMTool):
         self.app = app
         self.app = app
         self.canvas = app.plotcanvas
         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("""
                         QLabel
                         QLabel
@@ -34,11 +34,11 @@ class CutOut(FlatCAMTool):
                         """)
                         """)
         self.layout.addWidget(title_label)
         self.layout.addWidget(title_label)
 
 
-        ## Form Layout
+        # Form Layout
         form_layout = QtWidgets.QFormLayout()
         form_layout = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout)
         self.layout.addLayout(form_layout)
 
 
-        ## Type of object to be cutout
+        # Type of object to be cutout
         self.type_obj_combo = QtWidgets.QComboBox()
         self.type_obj_combo = QtWidgets.QComboBox()
         self.type_obj_combo.addItem("Gerber")
         self.type_obj_combo.addItem("Gerber")
         self.type_obj_combo.addItem("Excellon")
         self.type_obj_combo.addItem("Excellon")
@@ -53,14 +53,14 @@ class CutOut(FlatCAMTool):
         self.type_obj_combo_label = QtWidgets.QLabel(_("Obj 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"
-            "What is selected here will dictate the kind\n"
-            "of objects that will populate the 'Object' combobox.")
+              "It can be of type: Gerber or Geometry.\n"
+              "What is selected here will dictate the kind\n"
+              "of objects that will populate the 'Object' combobox.")
         )
         )
         self.type_obj_combo_label.setFixedWidth(60)
         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
         self.obj_combo = QtWidgets.QComboBox()
         self.obj_combo = QtWidgets.QComboBox()
         self.obj_combo.setModel(self.app.collection)
         self.obj_combo.setModel(self.app.collection)
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
@@ -76,8 +76,8 @@ class CutOut(FlatCAMTool):
         self.dia = FCEntry()
         self.dia = FCEntry()
         self.dia_label = QtWidgets.QLabel(_("Tool Dia:"))
         self.dia_label = QtWidgets.QLabel(_("Tool Dia:"))
         self.dia_label.setToolTip(
         self.dia_label.setToolTip(
-           _( "Diameter of the tool used to cutout\n"
-            "the PCB shape out of the surrounding material.")
+           _("Diameter of the tool used to cutout\n"
+             "the PCB shape out of the surrounding material.")
         )
         )
         form_layout.addRow(self.dia_label, self.dia)
         form_layout.addRow(self.dia_label, self.dia)
 
 
@@ -85,9 +85,9 @@ class CutOut(FlatCAMTool):
         self.margin = FCEntry()
         self.margin = FCEntry()
         self.margin_label = QtWidgets.QLabel(_("Margin:"))
         self.margin_label = QtWidgets.QLabel(_("Margin:"))
         self.margin_label.setToolTip(
         self.margin_label.setToolTip(
-           _( "Margin over bounds. A positive value here\n"
-            "will make the cutout of the PCB further from\n"
-            "the actual PCB border")
+           _("Margin over bounds. A positive value here\n"
+             "will make the cutout of the PCB further from\n"
+             "the actual PCB border")
         )
         )
         form_layout.addRow(self.margin_label, self.margin)
         form_layout.addRow(self.margin_label, self.margin)
 
 
@@ -95,10 +95,10 @@ 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 bridge gaps in the cutout\n"
-            "used to keep the board connected to\n"
-            "the surrounding material (the one \n"
-            "from which the PCB is cutout).")
+           _("The size of the bridge gaps in the cutout\n"
+             "used to keep the board connected to\n"
+             "the surrounding material (the one \n"
+             "from which the PCB is cutout).")
         )
         )
         form_layout.addRow(self.gapsize_label, self.gapsize)
         form_layout.addRow(self.gapsize_label, self.gapsize)
 
 
@@ -114,18 +114,19 @@ class CutOut(FlatCAMTool):
         self.convex_box = FCCheckBox()
         self.convex_box = FCCheckBox()
         self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
         self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
         self.convex_box_label.setToolTip(
         self.convex_box_label.setToolTip(
-            _("Create a convex shape surrounding the entire PCB.")
+            _("Create a convex shape surrounding the entire PCB.\n"
+              "Used only if the source object type is Gerber.")
         )
         )
         form_layout.addRow(self.convex_box_label, self.convex_box)
         form_layout.addRow(self.convex_box_label, self.convex_box)
 
 
-        ## Title2
+        # Title2
         title_param_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('A. Automatic Bridge Gaps'))
         title_param_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('A. Automatic Bridge Gaps'))
         title_param_label.setToolTip(
         title_param_label.setToolTip(
             _("This section handle creation of automatic bridge gaps.")
             _("This section handle creation of automatic bridge gaps.")
         )
         )
         self.layout.addWidget(title_param_label)
         self.layout.addWidget(title_param_label)
 
 
-        ## Form Layout
+        # Form Layout
         form_layout_2 = QtWidgets.QFormLayout()
         form_layout_2 = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout_2)
         self.layout.addLayout(form_layout_2)
 
 
@@ -133,14 +134,14 @@ class CutOut(FlatCAMTool):
         gaps_label = QtWidgets.QLabel(_('Gaps:'))
         gaps_label = QtWidgets.QLabel(_('Gaps:'))
         gaps_label.setToolTip(
         gaps_label.setToolTip(
             _("Number of gaps used for the Automatic cutout.\n"
             _("Number of gaps used for the Automatic cutout.\n"
-            "There can be maximum 8 bridges/gaps.\n"
-            "The choices are:\n"
-            "- lr    - left + right\n"
-            "- tb    - top + bottom\n"
-            "- 4     - left + right +top + bottom\n"
-            "- 2lr   - 2*left + 2*right\n"
-            "- 2tb  - 2*top + 2*bottom\n"
-            "- 8     - 2*left + 2*right +2*top + 2*bottom")
+              "There can be maximum 8 bridges/gaps.\n"
+              "The choices are:\n"
+              "- lr    - left + right\n"
+              "- tb    - top + bottom\n"
+              "- 4     - left + right +top + bottom\n"
+              "- 2lr   - 2*left + 2*right\n"
+              "- 2tb  - 2*top + 2*bottom\n"
+              "- 8     - 2*left + 2*right +2*top + 2*bottom")
         )
         )
         gaps_label.setFixedWidth(60)
         gaps_label.setFixedWidth(60)
 
 
@@ -151,14 +152,14 @@ class CutOut(FlatCAMTool):
             self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
             self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
         form_layout_2.addRow(gaps_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>%s</b>" % _('FreeForm:'))
         title_ff_label = QtWidgets.QLabel("<b>%s</b>" % _('FreeForm:'))
         title_ff_label.setToolTip(
         title_ff_label.setToolTip(
             _("The cutout shape can be of ny shape.\n"
             _("The cutout shape can be of ny shape.\n"
-            "Useful when the PCB has a non-rectangular shape.")
+              "Useful when the PCB has a non-rectangular shape.")
         )
         )
         hlay.addWidget(title_ff_label)
         hlay.addWidget(title_ff_label)
 
 
@@ -167,8 +168,8 @@ class CutOut(FlatCAMTool):
         self.ff_cutout_object_btn = QtWidgets.QPushButton(_("Generate Geo"))
         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 of any shape.\n"
-            "Useful when the PCB has a non-rectangular 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)
 
 
@@ -178,8 +179,8 @@ class CutOut(FlatCAMTool):
         title_rct_label = QtWidgets.QLabel("<b>%s</b>" % _('Rectangular:'))
         title_rct_label = QtWidgets.QLabel("<b>%s</b>" % _('Rectangular:'))
         title_rct_label.setToolTip(
         title_rct_label.setToolTip(
             _("The resulting cutout shape is\n"
             _("The resulting cutout shape is\n"
-            "always a rectangle shape and it will be\n"
-            "the bounding box of the Object.")
+              "always a rectangle shape and it will be\n"
+              "the bounding box of the Object.")
         )
         )
         hlay2.addWidget(title_rct_label)
         hlay2.addWidget(title_rct_label)
 
 
@@ -187,26 +188,26 @@ class CutOut(FlatCAMTool):
         self.rect_cutout_object_btn = QtWidgets.QPushButton(_("Generate Geo"))
         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"
-            "always a rectangle shape and it will be\n"
-            "the bounding box of the Object.")
+              "The resulting cutout shape is\n"
+              "always a rectangle shape and it will be\n"
+              "the bounding box of the Object.")
         )
         )
         hlay2.addWidget(self.rect_cutout_object_btn)
         hlay2.addWidget(self.rect_cutout_object_btn)
 
 
-        ## Title5
+        # Title5
         title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
         title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
         title_manual_label.setToolTip(
         title_manual_label.setToolTip(
             _("This section handle creation of manual bridge gaps.\n"
             _("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. ")
+              "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)
         self.layout.addWidget(title_manual_label)
 
 
-        ## Form Layout
+        # Form Layout
         form_layout_3 = QtWidgets.QFormLayout()
         form_layout_3 = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout_3)
         self.layout.addLayout(form_layout_3)
 
 
-        ## Manual Geo Object
+        # Manual Geo Object
         self.man_object_combo = QtWidgets.QComboBox()
         self.man_object_combo = QtWidgets.QComboBox()
         self.man_object_combo.setModel(self.app.collection)
         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.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
@@ -228,9 +229,9 @@ class CutOut(FlatCAMTool):
         self.man_geo_label = QtWidgets.QLabel(_("Manual Geo:"))
         self.man_geo_label = QtWidgets.QLabel(_("Manual Geo:"))
         self.man_geo_label.setToolTip(
         self.man_geo_label.setToolTip(
             _("If the object to be cutout is a Gerber\n"
             _("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.")
+              "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.addWidget(self.man_geo_label)
 
 
@@ -238,9 +239,9 @@ class CutOut(FlatCAMTool):
         self.man_geo_creation_btn = QtWidgets.QPushButton(_("Generate Geo"))
         self.man_geo_creation_btn = QtWidgets.QPushButton(_("Generate Geo"))
         self.man_geo_creation_btn.setToolTip(
         self.man_geo_creation_btn.setToolTip(
             _("If the object to be cutout is a Gerber\n"
             _("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.")
+              "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)
         hlay3.addWidget(self.man_geo_creation_btn)
 
 
@@ -250,8 +251,8 @@ class CutOut(FlatCAMTool):
         self.man_bridge_gaps_label = QtWidgets.QLabel(_("Manual Add Bridge Gaps:"))
         self.man_bridge_gaps_label = QtWidgets.QLabel(_("Manual Add Bridge Gaps:"))
         self.man_bridge_gaps_label.setToolTip(
         self.man_bridge_gaps_label.setToolTip(
             _("Use the left mouse button (LMB) click\n"
             _("Use the left mouse button (LMB) click\n"
-            "to create a bridge gap to separate the PCB from\n"
-            "the surrounding material.")
+              "to create a bridge gap to separate the PCB from\n"
+              "the surrounding material.")
         )
         )
         hlay4.addWidget(self.man_bridge_gaps_label)
         hlay4.addWidget(self.man_bridge_gaps_label)
 
 
@@ -259,10 +260,10 @@ class CutOut(FlatCAMTool):
         self.man_gaps_creation_btn = QtWidgets.QPushButton(_("Generate Gap"))
         self.man_gaps_creation_btn = QtWidgets.QPushButton(_("Generate Gap"))
         self.man_gaps_creation_btn.setToolTip(
         self.man_gaps_creation_btn.setToolTip(
             _("Use the left mouse button (LMB) click\n"
             _("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.")
+              "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)
         hlay4.addWidget(self.man_gaps_creation_btn)
 
 
@@ -274,7 +275,9 @@ class CutOut(FlatCAMTool):
         # true if we want to repeat the gap without clicking again on the button
         # true if we want to repeat the gap without clicking again on the button
         self.repeat_gap = False
         self.repeat_gap = False
 
 
-        ## Signals
+        self.flat_geometry = []
+
+        # Signals
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
         self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
 
 
@@ -325,9 +328,9 @@ class CutOut(FlatCAMTool):
 
 
     def on_freeform_cutout(self):
     def on_freeform_cutout(self):
 
 
-        def subtract_rectangle(obj_, x0, y0, x1, y1):
-            pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
-            obj_.subtract_polygon(pts)
+        # 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()
 
 
@@ -353,7 +356,6 @@ class CutOut(FlatCAMTool):
                                      "Add it and retry."))
                                      "Add it and retry."))
                 return
                 return
 
 
-
         if 0 in {dia}:
         if 0 in {dia}:
             self.app.inform.emit(_("[WARNING_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
             self.app.inform.emit(_("[WARNING_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
             return "Tool Diameter is zero value. Change it to a positive real number."
             return "Tool Diameter is zero value. Change it to a positive real number."
@@ -393,75 +395,89 @@ class CutOut(FlatCAMTool):
 
 
         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"
-                                 "Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
-                                 "and after that perform Cutout."))
+                                   "Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
+                                   "and after that perform Cutout."))
             return
             return
 
 
         convex_box = self.convex_box.get_value()
         convex_box = self.convex_box.get_value()
 
 
-        # Get min and max data for each object as we just cut rectangles across X or Y
-        xmin, ymin, xmax, ymax = cutout_obj.bounds()
-        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)
         gapsize = gapsize / 2 + (dia / 2)
 
 
-        if isinstance(cutout_obj,FlatCAMGeometry):
-            # rename the obj name so it can be identified as cutout
-            cutout_obj.options["name"] += "_cutout"
-        else:
-            def geo_init(geo_obj, app_obj):
+        def geo_init(geo_obj, app_obj):
+            solid_geo = []
+
+            if isinstance(cutout_obj, FlatCAMGerber):
                 if convex_box:
                 if convex_box:
-                    geo = cutout_obj.solid_geometry.convex_hull
-                    geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+                    object_geo = cutout_obj.solid_geometry.convex_hull
                 else:
                 else:
-                    geo = cutout_obj.solid_geometry
-                    geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2)).exterior
-
-            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)
+                    object_geo = cutout_obj.solid_geometry
+            else:
+                object_geo = cutout_obj.solid_geometry
+
+            try:
+                _ = iter(object_geo)
+            except TypeError:
+                object_geo = [object_geo]
+
+            for geo in object_geo:
+                if isinstance(cutout_obj, FlatCAMGerber):
+                    geo = (geo.buffer(margin + abs(dia / 2))).exterior
+
+                # Get min and max data for each object as we just cut rectangles across X or Y
+                xmin, ymin, xmax, ymax = geo.bounds
+                px = 0.5 * (xmin + xmax) + margin
+                py = 0.5 * (ymin + ymax) + margin
+                lenx = (xmax - xmin) + (margin * 2)
+                leny = (ymax - ymin) + (margin * 2)
+
+                if gaps == '8' or gaps == '2LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,  # botleft_x
+                                                      py - gapsize + leny / 4,  # botleft_y
+                                                      xmax + gapsize,  # topright_x
+                                                      py + gapsize + leny / 4)  # topright_y
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize - leny / 4,
+                                                      xmax + gapsize,
+                                                      py + gapsize - leny / 4)
+
+                if gaps == '8' or gaps == '2TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize + lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize + lenx / 4,
+                                                      ymax + gapsize)
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize - lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize - lenx / 4,
+                                                      ymax + gapsize)
+
+                if gaps == '4' or gaps == 'LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize,
+                                                      xmax + gapsize,
+                                                      py + gapsize)
+
+                if gaps == '4' or gaps == 'TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize,
+                                                      ymin - gapsize,
+                                                      px + gapsize,
+                                                      ymax + gapsize)
+
+                try:
+                    for g in geo:
+                        solid_geo.append(g)
+                except TypeError:
+                    solid_geo.append(geo)
+
+            geo_obj.solid_geometry = deepcopy(solid_geo)
+
+        outname = cutout_obj.options["name"] + "_cutout"
+        self.app.new_object('geometry', outname, geo_init)
 
 
         cutout_obj.plot()
         cutout_obj.plot()
         self.app.inform.emit(_("[success] Any form CutOut operation finished."))
         self.app.inform.emit(_("[success] Any form CutOut operation finished."))
@@ -470,9 +486,9 @@ class CutOut(FlatCAMTool):
 
 
     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)
+        # 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()
 
 
@@ -541,63 +557,82 @@ class CutOut(FlatCAMTool):
             return
             return
 
 
         # Get min and max data for each object as we just cut rectangles across X or Y
         # 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)
         gapsize = gapsize / 2 + (dia / 2)
 
 
         def geo_init(geo_obj, app_obj):
         def geo_init(geo_obj, app_obj):
-            geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+            solid_geo = []
+            object_geo = cutout_obj.solid_geometry
+
+            try:
+                _ = iter(object_geo)
+            except TypeError:
+                object_geo = [object_geo]
+
+            for poly in object_geo:
+
+                xmin, ymin, xmax, ymax = poly.bounds
+                geo = box(xmin, ymin, xmax, ymax)
+
+                # if Gerber create a buffer at a distance
+                # if Geometry then cut through the geometry
+                if isinstance(cutout_obj, FlatCAMGerber):
+                    geo = geo.buffer(margin + abs(dia / 2))
+
+                px = 0.5 * (xmin + xmax) + margin
+                py = 0.5 * (ymin + ymax) + margin
+                lenx = (xmax - xmin) + (margin * 2)
+                leny = (ymax - ymin) + (margin * 2)
+
+                if gaps == '8' or gaps == '2LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,  # botleft_x
+                                                      py - gapsize + leny / 4,  # botleft_y
+                                                      xmax + gapsize,  # topright_x
+                                                      py + gapsize + leny / 4)  # topright_y
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize - leny / 4,
+                                                      xmax + gapsize,
+                                                      py + gapsize - leny / 4)
+
+                if gaps == '8' or gaps == '2TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize + lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize + lenx / 4,
+                                                      ymax + gapsize)
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize - lenx / 4,
+                                                      ymin - gapsize,
+                                                      px + gapsize - lenx / 4,
+                                                      ymax + gapsize)
+
+                if gaps == '4' or gaps == 'LR':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      xmin - gapsize,
+                                                      py - gapsize,
+                                                      xmax + gapsize,
+                                                      py + gapsize)
+
+                if gaps == '4' or gaps == 'TB':
+                    geo = self.subtract_poly_from_geo(geo,
+                                                      px - gapsize,
+                                                      ymin - gapsize,
+                                                      px + gapsize,
+                                                      ymax + gapsize)
+                try:
+                    for g in geo:
+                        solid_geo.append(g)
+                except TypeError:
+                    solid_geo.append(geo)
+
+            geo_obj.solid_geometry = deepcopy(solid_geo)
 
 
         outname = cutout_obj.options["name"] + "_cutout"
         outname = cutout_obj.options["name"] + "_cutout"
         self.app.new_object('geometry', outname, geo_init)
         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()
+        # cutout_obj.plot()
         self.app.inform.emit(_("[success] Any form CutOut operation finished."))
         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
         self.app.should_we_save = True
@@ -745,7 +780,14 @@ class CutOut(FlatCAMTool):
                 geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
                 geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
             else:
             else:
                 geo = cutout_obj.solid_geometry
                 geo = cutout_obj.solid_geometry
-                geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2)).exterior
+                geo = geo.buffer(margin + abs(dia / 2))
+                if isinstance(geo, Polygon):
+                    geo_obj.solid_geometry = geo.exterior
+                elif isinstance(geo, MultiPolygon):
+                    solid_geo = []
+                    for poly in geo:
+                        solid_geo.append(poly.exterior)
+                    geo_obj.solid_geometry = deepcopy(solid_geo)
 
 
         outname = cutout_obj.options["name"] + "_cutout"
         outname = cutout_obj.options["name"] + "_cutout"
         self.app.new_object('geometry', outname, geo_init)
         self.app.new_object('geometry', outname, geo_init)
@@ -819,5 +861,95 @@ class CutOut(FlatCAMTool):
             self.app.geo_editor.tool_shape.clear(update=True)
             self.app.geo_editor.tool_shape.clear(update=True)
             self.app.geo_editor.tool_shape.enabled = False
             self.app.geo_editor.tool_shape.enabled = False
 
 
+    def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
+        """
+        Subtract polygon made from points from the given object.
+        This only operates on the paths in the original geometry,
+        i.e. it converts polygons into paths.
+
+        :param x0: x coord for lower left vertice of the polygon.
+        :param y0: y coord for lower left vertice of the polygon.
+        :param x1: x coord for upper right vertice of the polygon.
+        :param y1: y coord for upper right vertice of the polygon.
+
+        :param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
+        :return: none
+        """
+        points = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
+
+        # pathonly should be allways True, otherwise polygons are not subtracted
+        flat_geometry = flatten(geometry=solid_geo)
+
+        log.debug("%d paths" % len(flat_geometry))
+
+        polygon = Polygon(points)
+        toolgeo = cascaded_union(polygon)
+        diffs = []
+        for target in flat_geometry:
+            if type(target) == LineString or type(target) == LinearRing:
+                diffs.append(target.difference(toolgeo))
+            else:
+                log.warning("Not implemented.")
+
+        return unary_union(diffs)
+
     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()))
+
+
+def flatten(geometry):
+    """
+    Creates a list of non-iterable linear geometry objects.
+    Polygons are expanded into its exterior and interiors.
+
+    Results are placed in self.flat_geometry
+
+    :param geometry: Shapely type or list or list of list of such.
+    """
+    flat_geo = []
+    try:
+        for geo in geometry:
+            if type(geo) == Polygon:
+                flat_geo.append(geo.exterior)
+                for subgeo in geo.interiors:
+                    flat_geo.append(subgeo)
+            else:
+                flat_geo.append(geo)
+    except TypeError:
+        if type(geometry) == Polygon:
+            flat_geo.append(geometry.exterior)
+            for subgeo in geometry.interiors:
+                flat_geo.append(subgeo)
+        else:
+            flat_geo.append(geometry)
+
+    return flat_geo
+
+
+def recursive_bounds(geometry):
+    """
+    Returns coordinates of rectangular bounds
+    of geometry: (xmin, ymin, xmax, ymax).
+    """
+
+    # now it can get bounds for nested lists of objects
+
+    def bounds_rec(obj):
+        try:
+            minx = Inf
+            miny = Inf
+            maxx = -Inf
+            maxy = -Inf
+
+            for k in obj:
+                minx_, miny_, maxx_, maxy_ = bounds_rec(k)
+                minx = min(minx, minx_)
+                miny = min(miny, miny_)
+                maxx = max(maxx, maxx_)
+                maxy = max(maxy, maxy_)
+            return minx, miny, maxx, maxy
+        except TypeError:
+            # it's a Shapely object, return it's bounds
+            return obj.bounds
+
+    return bounds_rec(geometry)

+ 354 - 76
flatcamTools/ToolPDF.py

@@ -97,7 +97,7 @@ class ToolPDF(FlatCAMTool):
         self.save_gs_re = re.compile(r'^q.*?$')
         self.save_gs_re = re.compile(r'^q.*?$')
 
 
         # detect restore graphic state from graphic stack
         # detect restore graphic state from graphic stack
-        self.restore_gs_re = re.compile(r'^Q.*$')
+        self.restore_gs_re = re.compile(r'^.*Q.*$')
 
 
         # graphic stack where we save parameters like transformation, line_width
         # graphic stack where we save parameters like transformation, line_width
         self.gs = dict()
         self.gs = dict()
@@ -219,8 +219,9 @@ class ToolPDF(FlatCAMTool):
         points = {}
         points = {}
 
 
         def obj_init(exc_obj, app_obj):
         def obj_init(exc_obj, app_obj):
+            clear_geo = [geo_el['clear'] for geo_el in ap_dict['0']['geometry']]
 
 
-            for geo in ap_dict['0']['solid_geometry']:
+            for geo in clear_geo:
                 xmin, ymin, xmax, ymax = geo.bounds
                 xmin, ymin, xmax, ymax = geo.bounds
                 center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
                 center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
 
 
@@ -280,12 +281,48 @@ class ToolPDF(FlatCAMTool):
             grb_obj.apertures = ap_dict
             grb_obj.apertures = ap_dict
 
 
             poly_buff = []
             poly_buff = []
+            follow_buf = []
             for ap in grb_obj.apertures:
             for ap in grb_obj.apertures:
                 for k in grb_obj.apertures[ap]:
                 for k in grb_obj.apertures[ap]:
-                    if k == 'solid_geometry':
-                        poly_buff += ap_dict[ap][k]
-
+                    if k == 'geometry':
+                        for geo_el in ap_dict[ap][k]:
+                            if 'solid' in geo_el:
+                                poly_buff.append(geo_el['solid'])
+                            if 'follow' in geo_el:
+                                follow_buf.append(geo_el['follow'])
             poly_buff = unary_union(poly_buff)
             poly_buff = unary_union(poly_buff)
+
+            if '0' in grb_obj.apertures:
+                global_clear_geo = []
+                if 'geometry' in grb_obj.apertures['0']:
+                    for geo_el in ap_dict['0']['geometry']:
+                        if 'clear' in geo_el:
+                            global_clear_geo.append(geo_el['clear'])
+
+                if global_clear_geo:
+                    solid= []
+                    for apid in grb_obj.apertures:
+                        if 'geometry' in grb_obj.apertures[apid]:
+                            for elem in grb_obj.apertures[apid]['geometry']:
+                                if 'solid' in elem:
+                                    solid_geo = deepcopy(elem['solid'])
+                                    for clear_geo in global_clear_geo:
+                                        # Make sure that the clear_geo is within the solid_geo otherwise we loose
+                                        # the solid_geometry. We want for clear_geometry just to cut into solid_geometry
+                                        # not to delete it
+                                        if clear_geo.within(solid_geo):
+                                            solid_geo = solid_geo.difference(clear_geo)
+                                        if solid_geo.is_empty:
+                                            solid_geo = elem['solid']
+                                    try:
+                                        for poly in solid_geo:
+                                            solid.append(poly)
+                                    except TypeError:
+                                        solid.append(solid_geo)
+                    poly_buff = deepcopy(MultiPolygon(solid))
+
+            follow_buf = unary_union(follow_buf)
+
             try:
             try:
                 poly_buff = poly_buff.buffer(0.0000001)
                 poly_buff = poly_buff.buffer(0.0000001)
             except ValueError:
             except ValueError:
@@ -296,6 +333,7 @@ class ToolPDF(FlatCAMTool):
                 pass
                 pass
 
 
             grb_obj.solid_geometry = deepcopy(poly_buff)
             grb_obj.solid_geometry = deepcopy(poly_buff)
+            grb_obj.follow_geometry = deepcopy(follow_buf)
 
 
         with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
         with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
 
 
@@ -416,7 +454,7 @@ class ToolPDF(FlatCAMTool):
         clear_apertures_dict['0'] = dict()
         clear_apertures_dict['0'] = dict()
         clear_apertures_dict['0']['size'] = 0.0
         clear_apertures_dict['0']['size'] = 0.0
         clear_apertures_dict['0']['type'] = 'C'
         clear_apertures_dict['0']['type'] = 'C'
-        clear_apertures_dict['0']['solid_geometry'] = []
+        clear_apertures_dict['0']['geometry'] = []
 
 
         # on stroke color change we create a new apertures dictionary and store the old one in a storage from where
         # on stroke color change we create a new apertures dictionary and store the old one in a storage from where
         # it will be transformed into Gerber object
         # it will be transformed into Gerber object
@@ -430,7 +468,7 @@ class ToolPDF(FlatCAMTool):
 
 
         for pline in lines:
         for pline in lines:
             line_nr += 1
             line_nr += 1
-            # log.debug("line %d: %s" % (line_nr, pline))
+            log.debug("line %d: %s" % (line_nr, pline))
 
 
             # COLOR DETECTION / OBJECT DETECTION
             # COLOR DETECTION / OBJECT DETECTION
             match = self.stroke_color_re.search(pline)
             match = self.stroke_color_re.search(pline)
@@ -518,8 +556,6 @@ class ToolPDF(FlatCAMTool):
             # detect restore from graphic stack event
             # detect restore from graphic stack event
             match = self.restore_gs_re.search(pline)
             match = self.restore_gs_re.search(pline)
             if match:
             if match:
-                log.debug(
-                    "ToolPDF.parse_pdf() --> Restore from GS found on line: %s --> %s" % (line_nr, pline))
                 try:
                 try:
                     restored_transform = self.gs['transform'].pop(-1)
                     restored_transform = self.gs['transform'].pop(-1)
                     offset_geo = restored_transform[0]
                     offset_geo = restored_transform[0]
@@ -535,6 +571,11 @@ class ToolPDF(FlatCAMTool):
                     log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
                     log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
                     # nothing to remove
                     # nothing to remove
                     pass
                     pass
+
+                log.debug(
+                    "ToolPDF.parse_pdf() --> Restore from GS found on line: %s --> "
+                    "restored_offset=[%f, %f] ||| restored_scale=[%f, %f]" %
+                    (line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
                 # log.debug("Restored Offset= [%f, %f]" % (offset_geo[0], offset_geo[1]))
                 # log.debug("Restored Offset= [%f, %f]" % (offset_geo[0], offset_geo[1]))
                 # log.debug("Restored Scale= [%f, %f]" % (scale_geo[0], scale_geo[1]))
                 # log.debug("Restored Scale= [%f, %f]" % (scale_geo[0], scale_geo[1]))
 
 
@@ -659,7 +700,7 @@ class ToolPDF(FlatCAMTool):
                 subpath['lines'] = []
                 subpath['lines'] = []
                 subpath['bezier'] = []
                 subpath['bezier'] = []
                 subpath['rectangle'] = []
                 subpath['rectangle'] = []
-                # it measns that we've already added the subpath to path and we need to delete it
+                # it means that we've already added the subpath to path and we need to delete it
                 # clipping path is usually either rectangle or lines
                 # clipping path is usually either rectangle or lines
                 if close_subpath is True:
                 if close_subpath is True:
                     close_subpath = False
                     close_subpath = False
@@ -711,20 +752,25 @@ class ToolPDF(FlatCAMTool):
             if match:
             if match:
                 # scale the size here; some PDF printers apply transformation after the size is declared
                 # scale the size here; some PDF printers apply transformation after the size is declared
                 applied_size = size * scale_geo[0] * self.point_to_unit_factor
                 applied_size = size * scale_geo[0] * self.point_to_unit_factor
-
                 path_geo = list()
                 path_geo = list()
                 if current_subpath == 'lines':
                 if current_subpath == 'lines':
                     if path['lines']:
                     if path['lines']:
                         for subp in path['lines']:
                         for subp in path['lines']:
                             geo = copy(subp)
                             geo = copy(subp)
-                            geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
-                            path_geo.append(geo)
+                            try:
+                                geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
+                                path_geo.append(geo)
+                            except ValueError:
+                                pass
                         # the path was painted therefore initialize it
                         # the path was painted therefore initialize it
                         path['lines'] = []
                         path['lines'] = []
                     else:
                     else:
                         geo = copy(subpath['lines'])
                         geo = copy(subpath['lines'])
-                        geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
-                        path_geo.append(geo)
+                        try:
+                            geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
+                            path_geo.append(geo)
+                        except ValueError:
+                            pass
                         subpath['lines'] = []
                         subpath['lines'] = []
 
 
                 if current_subpath == 'bezier':
                 if current_subpath == 'bezier':
@@ -733,30 +779,44 @@ class ToolPDF(FlatCAMTool):
                             geo = []
                             geo = []
                             for b in subp:
                             for b in subp:
                                 geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                                 geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
-                            geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
-                            path_geo.append(geo)
+                            try:
+                                geo = LineString(geo).buffer((float(applied_size) / 2),
+                                                             resolution=self.step_per_circles)
+                                path_geo.append(geo)
+                            except ValueError:
+                                pass
                         # the path was painted therefore initialize it
                         # the path was painted therefore initialize it
                         path['bezier'] = []
                         path['bezier'] = []
                     else:
                     else:
                         geo = []
                         geo = []
                         for b in subpath['bezier']:
                         for b in subpath['bezier']:
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
-                        geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
-                        path_geo.append(geo)
+                        try:
+                            geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
+                            path_geo.append(geo)
+                        except ValueError:
+                            pass
                         subpath['bezier'] = []
                         subpath['bezier'] = []
 
 
                 if current_subpath == 'rectangle':
                 if current_subpath == 'rectangle':
                     if path['rectangle']:
                     if path['rectangle']:
                         for subp in path['rectangle']:
                         for subp in path['rectangle']:
                             geo = copy(subp)
                             geo = copy(subp)
-                            geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
-                            path_geo.append(geo)
+                            try:
+                                geo = LineString(geo).buffer((float(applied_size) / 2),
+                                                             resolution=self.step_per_circles)
+                                path_geo.append(geo)
+                            except ValueError:
+                                pass
                         # the path was painted therefore initialize it
                         # the path was painted therefore initialize it
                         path['rectangle'] = []
                         path['rectangle'] = []
                     else:
                     else:
                         geo = copy(subpath['rectangle'])
                         geo = copy(subpath['rectangle'])
-                        geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
-                        path_geo.append(geo)
+                        try:
+                            geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
+                            path_geo.append(geo)
+                        except ValueError:
+                            pass
                         subpath['rectangle'] = []
                         subpath['rectangle'] = []
 
 
                 # store the found geometry
                 # store the found geometry
@@ -769,7 +829,18 @@ class ToolPDF(FlatCAMTool):
                             break
                             break
 
 
                     if found_aperture:
                     if found_aperture:
-                        apertures_dict[copy(found_aperture)]['solid_geometry'] += path_geo
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                         found_aperture = None
                         found_aperture = None
                     else:
                     else:
                         if str(aperture) in apertures_dict.keys():
                         if str(aperture) in apertures_dict.keys():
@@ -777,14 +848,36 @@ class ToolPDF(FlatCAMTool):
                         apertures_dict[str(aperture)] = {}
                         apertures_dict[str(aperture)] = {}
                         apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                         apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                         apertures_dict[str(aperture)]['type'] = 'C'
                         apertures_dict[str(aperture)]['type'] = 'C'
-                        apertures_dict[str(aperture)]['solid_geometry'] = []
-                        apertures_dict[str(aperture)]['solid_geometry'] += path_geo
+                        apertures_dict[str(aperture)]['geometry'] = []
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                 else:
                 else:
                     apertures_dict[str(aperture)] = {}
                     apertures_dict[str(aperture)] = {}
                     apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                     apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                     apertures_dict[str(aperture)]['type'] = 'C'
                     apertures_dict[str(aperture)]['type'] = 'C'
-                    apertures_dict[str(aperture)]['solid_geometry'] = []
-                    apertures_dict[str(aperture)]['solid_geometry'] += path_geo
+                    apertures_dict[str(aperture)]['geometry'] = []
+                    for pdf_geo in path_geo:
+                        if isinstance(pdf_geo, MultiPolygon):
+                            for poly in pdf_geo:
+                                new_el = dict()
+                                new_el['solid'] = poly
+                                new_el['follow'] = poly.exterior
+                                apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
+                        else:
+                            new_el = dict()
+                            new_el['solid'] = pdf_geo
+                            new_el['follow'] = pdf_geo.exterior
+                            apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
 
 
                 continue
                 continue
 
 
@@ -802,8 +895,11 @@ class ToolPDF(FlatCAMTool):
                             # close the subpath if it was not closed already
                             # close the subpath if it was not closed already
                             if close_subpath is False:
                             if close_subpath is False:
                                 geo.append(geo[0])
                                 geo.append(geo[0])
-                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                            path_geo.append(geo_el)
+                            try:
+                                geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                                path_geo.append(geo_el)
+                            except ValueError:
+                                pass
                         # the path was painted therefore initialize it
                         # the path was painted therefore initialize it
                         path['lines'] = []
                         path['lines'] = []
                     else:
                     else:
@@ -811,8 +907,11 @@ class ToolPDF(FlatCAMTool):
                         # close the subpath if it was not closed already
                         # close the subpath if it was not closed already
                         if close_subpath is False:
                         if close_subpath is False:
                             geo.append(start_point)
                             geo.append(start_point)
-                        geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                        path_geo.append(geo_el)
+                        try:
+                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                            path_geo.append(geo_el)
+                        except ValueError:
+                            pass
                         subpath['lines'] = []
                         subpath['lines'] = []
 
 
                 if current_subpath == 'bezier':
                 if current_subpath == 'bezier':
@@ -824,8 +923,11 @@ class ToolPDF(FlatCAMTool):
                                 # close the subpath if it was not closed already
                                 # close the subpath if it was not closed already
                                 if close_subpath is False:
                                 if close_subpath is False:
                                     geo.append(geo[0])
                                     geo.append(geo[0])
-                                geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                                path_geo.append(geo_el)
+                                try:
+                                    geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                                    path_geo.append(geo_el)
+                                except ValueError:
+                                    pass
                         # the path was painted therefore initialize it
                         # the path was painted therefore initialize it
                         path['bezier'] = []
                         path['bezier'] = []
                     else:
                     else:
@@ -833,8 +935,11 @@ class ToolPDF(FlatCAMTool):
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                         if close_subpath is False:
                         if close_subpath is False:
                             geo.append(start_point)
                             geo.append(start_point)
-                        geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                        path_geo.append(geo_el)
+                        try:
+                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                            path_geo.append(geo_el)
+                        except ValueError:
+                            pass
                         subpath['bezier'] = []
                         subpath['bezier'] = []
 
 
                 if current_subpath == 'rectangle':
                 if current_subpath == 'rectangle':
@@ -844,8 +949,11 @@ class ToolPDF(FlatCAMTool):
                             # # close the subpath if it was not closed already
                             # # close the subpath if it was not closed already
                             # if close_subpath is False and start_point is not None:
                             # if close_subpath is False and start_point is not None:
                             #     geo.append(start_point)
                             #     geo.append(start_point)
-                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                            path_geo.append(geo_el)
+                            try:
+                                geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                                path_geo.append(geo_el)
+                            except ValueError:
+                                pass
                         # the path was painted therefore initialize it
                         # the path was painted therefore initialize it
                         path['rectangle'] = []
                         path['rectangle'] = []
                     else:
                     else:
@@ -853,32 +961,96 @@ class ToolPDF(FlatCAMTool):
                         # # close the subpath if it was not closed already
                         # # close the subpath if it was not closed already
                         # if close_subpath is False and start_point is not None:
                         # if close_subpath is False and start_point is not None:
                         #     geo.append(start_point)
                         #     geo.append(start_point)
-                        geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                        path_geo.append(geo_el)
+                        try:
+                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                            path_geo.append(geo_el)
+                        except ValueError:
+                            pass
                         subpath['rectangle'] = []
                         subpath['rectangle'] = []
 
 
                 # we finished painting and also closed the path if it was the case
                 # we finished painting and also closed the path if it was the case
                 close_subpath = True
                 close_subpath = True
 
 
-                # if there was a fill color change we look for circular geometries from which we can make drill holes
-                # for the Excellon file
+                # in case that a color change to white (transparent) occurred
                 if flag_clear_geo is True:
                 if flag_clear_geo is True:
-                    # we llok for circular geometries
+                    # if there was a fill color change we look for circular geometries from which we can make
+                    # drill holes for the Excellon file
                     if current_subpath == 'bezier':
                     if current_subpath == 'bezier':
                         # if there are geometries in the list
                         # if there are geometries in the list
                         if path_geo:
                         if path_geo:
-                            clear_apertures_dict['0']['solid_geometry'] += path_geo
+                            try:
+                                for g in path_geo:
+                                    new_el = dict()
+                                    new_el['clear'] = g
+                                    clear_apertures_dict['0']['geometry'].append(new_el)
+                            except TypeError:
+                                new_el = dict()
+                                new_el['clear'] = path_geo
+                                clear_apertures_dict['0']['geometry'].append(new_el)
+
+                    # now that we finished searching for drill holes (this is not very precise because holes in the
+                    # polygon pours may appear as drill too, but .. hey you can't have it all ...) we add
+                    # clear_geometry
+                    try:
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['clear'] = poly
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['clear'] = pdf_geo
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                    except KeyError:
+                        # in case there is no stroke width yet therefore no aperture
+                        apertures_dict['0'] = {}
+                        apertures_dict['0']['size'] = applied_size
+                        apertures_dict['0']['type'] = 'C'
+                        apertures_dict['0']['geometry'] = []
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['clear'] = poly
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['clear'] = pdf_geo
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
                 else:
                 else:
                     # else, add the geometry as usual
                     # else, add the geometry as usual
                     try:
                     try:
-                        apertures_dict['0']['solid_geometry'] += path_geo
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     except KeyError:
                     except KeyError:
                         # in case there is no stroke width yet therefore no aperture
                         # in case there is no stroke width yet therefore no aperture
                         apertures_dict['0'] = {}
                         apertures_dict['0'] = {}
                         apertures_dict['0']['size'] = applied_size
                         apertures_dict['0']['size'] = applied_size
                         apertures_dict['0']['type'] = 'C'
                         apertures_dict['0']['type'] = 'C'
-                        apertures_dict['0']['solid_geometry'] = []
-                        apertures_dict['0']['solid_geometry'] += path_geo
+                        apertures_dict['0']['geometry'] = []
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     continue
                     continue
 
 
             # Fill and Stroke the path
             # Fill and Stroke the path
@@ -897,8 +1069,11 @@ class ToolPDF(FlatCAMTool):
                             # close the subpath if it was not closed already
                             # close the subpath if it was not closed already
                             if close_subpath is False:
                             if close_subpath is False:
                                 geo.append(geo[0])
                                 geo.append(geo[0])
-                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                            fill_geo.append(geo_el)
+                            try:
+                                geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                                fill_geo.append(geo_el)
+                            except ValueError:
+                                pass
                         # stroke
                         # stroke
                         for subp in path['lines']:
                         for subp in path['lines']:
                             geo = copy(subp)
                             geo = copy(subp)
@@ -912,8 +1087,11 @@ class ToolPDF(FlatCAMTool):
                         # close the subpath if it was not closed already
                         # close the subpath if it was not closed already
                         if close_subpath is False:
                         if close_subpath is False:
                             geo.append(start_point)
                             geo.append(start_point)
-                        geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                        fill_geo.append(geo_el)
+                        try:
+                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                            fill_geo.append(geo_el)
+                        except ValueError:
+                            pass
                         # stroke
                         # stroke
                         geo = copy(subpath['lines'])
                         geo = copy(subpath['lines'])
                         geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
                         geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
@@ -931,8 +1109,11 @@ class ToolPDF(FlatCAMTool):
                                 # close the subpath if it was not closed already
                                 # close the subpath if it was not closed already
                                 if close_subpath is False:
                                 if close_subpath is False:
                                     geo.append(geo[0])
                                     geo.append(geo[0])
-                                geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                                fill_geo.append(geo_el)
+                                try:
+                                    geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                                    fill_geo.append(geo_el)
+                                except ValueError:
+                                    pass
                         # stroke
                         # stroke
                         for subp in path['bezier']:
                         for subp in path['bezier']:
                             geo = []
                             geo = []
@@ -948,8 +1129,11 @@ class ToolPDF(FlatCAMTool):
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                         if close_subpath is False:
                         if close_subpath is False:
                             geo.append(start_point)
                             geo.append(start_point)
-                        geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                        fill_geo.append(geo_el)
+                        try:
+                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                            fill_geo.append(geo_el)
+                        except ValueError:
+                            pass
                         # stroke
                         # stroke
                         geo = []
                         geo = []
                         for b in subpath['bezier']:
                         for b in subpath['bezier']:
@@ -966,8 +1150,11 @@ class ToolPDF(FlatCAMTool):
                             # # close the subpath if it was not closed already
                             # # close the subpath if it was not closed already
                             # if close_subpath is False:
                             # if close_subpath is False:
                             #     geo.append(geo[0])
                             #     geo.append(geo[0])
-                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                            fill_geo.append(geo_el)
+                            try:
+                                geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                                fill_geo.append(geo_el)
+                            except ValueError:
+                                pass
                         # stroke
                         # stroke
                         for subp in path['rectangle']:
                         for subp in path['rectangle']:
                             geo = copy(subp)
                             geo = copy(subp)
@@ -981,8 +1168,11 @@ class ToolPDF(FlatCAMTool):
                         # # close the subpath if it was not closed already
                         # # close the subpath if it was not closed already
                         # if close_subpath is False:
                         # if close_subpath is False:
                         #     geo.append(start_point)
                         #     geo.append(start_point)
-                        geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
-                        fill_geo.append(geo_el)
+                        try:
+                            geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
+                            fill_geo.append(geo_el)
+                        except ValueError:
+                            pass
                         # stroke
                         # stroke
                         geo = copy(subpath['rectangle'])
                         geo = copy(subpath['rectangle'])
                         geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
                         geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
@@ -1002,7 +1192,18 @@ class ToolPDF(FlatCAMTool):
                             break
                             break
 
 
                     if found_aperture:
                     if found_aperture:
-                        apertures_dict[copy(found_aperture)]['solid_geometry'] += path_geo
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                         found_aperture = None
                         found_aperture = None
                     else:
                     else:
                         if str(aperture) in apertures_dict.keys():
                         if str(aperture) in apertures_dict.keys():
@@ -1010,25 +1211,102 @@ class ToolPDF(FlatCAMTool):
                         apertures_dict[str(aperture)] = {}
                         apertures_dict[str(aperture)] = {}
                         apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                         apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                         apertures_dict[str(aperture)]['type'] = 'C'
                         apertures_dict[str(aperture)]['type'] = 'C'
-                        apertures_dict[str(aperture)]['solid_geometry'] = []
-                        apertures_dict[str(aperture)]['solid_geometry'] += path_geo
+                        apertures_dict[str(aperture)]['geometry'] = []
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                 else:
                 else:
                     apertures_dict[str(aperture)] = {}
                     apertures_dict[str(aperture)] = {}
                     apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                     apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
                     apertures_dict[str(aperture)]['type'] = 'C'
                     apertures_dict[str(aperture)]['type'] = 'C'
-                    apertures_dict[str(aperture)]['solid_geometry'] = []
-                    apertures_dict[str(aperture)]['solid_geometry'] += path_geo
-
-                # store the found geometry for filling the path
-                try:
-                    apertures_dict['0']['solid_geometry'] += fill_geo
-                except KeyError:
-                    # in case there is no stroke width yet therefore no aperture
-                    apertures_dict['0'] = {}
-                    apertures_dict['0']['size'] = round(applied_size, 5)
-                    apertures_dict['0']['type'] = 'C'
-                    apertures_dict['0']['solid_geometry'] = []
-                    apertures_dict['0']['solid_geometry'] += fill_geo
+                    apertures_dict[str(aperture)]['geometry'] = []
+                    for pdf_geo in path_geo:
+                        if isinstance(pdf_geo, MultiPolygon):
+                            for poly in pdf_geo:
+                                new_el = dict()
+                                new_el['solid'] = poly
+                                new_el['follow'] = poly.exterior
+                                apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
+                        else:
+                            new_el = dict()
+                            new_el['solid'] = pdf_geo
+                            new_el['follow'] = pdf_geo.exterior
+                            apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
+
+                # ###############################################
+                # store the found geometry for filling the path #
+                # ###############################################
+
+                # in case that a color change to white (transparent) occurred
+                if flag_clear_geo is True:
+                    try:
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in fill_geo:
+                                    new_el = dict()
+                                    new_el['clear'] = poly
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['clear'] = pdf_geo
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                    except KeyError:
+                        # in case there is no stroke width yet therefore no aperture
+                        apertures_dict['0'] = {}
+                        apertures_dict['0']['size'] = round(applied_size, 5)
+                        apertures_dict['0']['type'] = 'C'
+                        apertures_dict['0']['geometry'] = []
+                        for pdf_geo in fill_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['clear'] = poly
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['clear'] = pdf_geo
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                else:
+                    try:
+                        for pdf_geo in path_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in fill_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                    except KeyError:
+                        # in case there is no stroke width yet therefore no aperture
+                        apertures_dict['0'] = {}
+                        apertures_dict['0']['size'] = round(applied_size, 5)
+                        apertures_dict['0']['type'] = 'C'
+                        apertures_dict['0']['geometry'] = []
+                        for pdf_geo in fill_geo:
+                            if isinstance(pdf_geo, MultiPolygon):
+                                for poly in pdf_geo:
+                                    new_el = dict()
+                                    new_el['solid'] = poly
+                                    new_el['follow'] = poly.exterior
+                                    apertures_dict['0']['geometry'].append(deepcopy(new_el))
+                            else:
+                                new_el = dict()
+                                new_el['solid'] = pdf_geo
+                                new_el['follow'] = pdf_geo.exterior
+                                apertures_dict['0']['geometry'].append(deepcopy(new_el))
 
 
                 continue
                 continue
 
 
@@ -1036,7 +1314,7 @@ class ToolPDF(FlatCAMTool):
         if apertures_dict:
         if apertures_dict:
             object_dict[layer_nr] = deepcopy(apertures_dict)
             object_dict[layer_nr] = deepcopy(apertures_dict)
 
 
-        if clear_apertures_dict['0']['solid_geometry']:
+        if clear_apertures_dict['0']['geometry']:
             object_dict[0] = deepcopy(clear_apertures_dict)
             object_dict[0] = deepcopy(clear_apertures_dict)
 
 
         # delete keys (layers) with empty values
         # delete keys (layers) with empty values

+ 5 - 1
flatcamTools/ToolPaint.py

@@ -795,7 +795,11 @@ class ToolPaint(FlatCAMTool, Gerber):
                 if event.button == 1:
                 if event.button == 1:
                     self.app.inform.emit(_("Painting polygon..."))
                     self.app.inform.emit(_("Painting polygon..."))
                     self.app.plotcanvas.vis_disconnect('mouse_press', doit)
                     self.app.plotcanvas.vis_disconnect('mouse_press', doit)
+
                     pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
                     pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                    if self.app.grid_status():
+                        pos = self.app.geo_editor.snap(pos[0], pos[1])
+
                     self.paint_poly(self.paint_obj,
                     self.paint_poly(self.paint_obj,
                                     inside_pt=[pos[0], pos[1]],
                                     inside_pt=[pos[0], pos[1]],
                                     tooldia=tooldia,
                                     tooldia=tooldia,
@@ -827,7 +831,7 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
         # Which polygon.
         # Which polygon.
         # poly = find_polygon(self.solid_geometry, inside_pt)
         # poly = find_polygon(self.solid_geometry, inside_pt)
-        poly = obj.find_polygon(inside_pt)
+        poly = self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)
         paint_method = self.paintmethod_combo.get_value()
         paint_method = self.paintmethod_combo.get_value()
 
 
         try:
         try:

+ 21 - 10
flatcamTools/ToolProperties.py

@@ -175,16 +175,27 @@ class Properties(FlatCAMTool):
             for ap in obj.apertures:
             for ap in obj.apertures:
                 temp_ap.clear()
                 temp_ap.clear()
                 temp_ap = deepcopy(obj.apertures[ap])
                 temp_ap = deepcopy(obj.apertures[ap])
-                if obj.apertures[ap]['solid_geometry']:
-                    elems = len(obj.apertures[ap]['solid_geometry'])
-                    temp_ap['solid_geometry'] = '%s Polygons' % str(elems)
-                try:
-                    if obj.apertures[ap]['follow_geometry']:
-                        elems = len(obj.apertures[ap]['follow_geometry'])
-                        temp_ap['follow_geometry'] = '%s Polygons' % str(elems)
-                except KeyError:
-                    pass
-                self.addChild(apertures, [str(ap), str(temp_ap)], True)
+                temp_ap.pop('geometry', None)
+                if obj.apertures[ap]['geometry']:
+                    solid_nr = 0
+                    follow_nr = 0
+                    clear_nr = 0
+
+                    for el in obj.apertures[ap]['geometry']:
+                        if 'solid' in el:
+                            solid_nr += 1
+                        if 'follow' in el:
+                            follow_nr += 1
+                        if 'clear' in el:
+                            clear_nr += 1
+                    temp_ap['Solid_Geo'] = '%s Polygons' % str(solid_nr)
+                    temp_ap['Follow_Geo'] = '%s LineStrings' % str(follow_nr)
+                    temp_ap['Clear_Geo'] = '%s Polygons' % str(clear_nr)
+
+                apid = self.addParent(apertures, str(ap), expanded=False, color=QtGui.QColor("#000000"), font=font)
+                for key in temp_ap:
+                    self.addChild(apid, [str(key), str(temp_ap[key])], True)
+
         elif obj.kind.lower() == 'excellon':
         elif obj.kind.lower() == 'excellon':
             for tool, value in obj.tools.items():
             for tool, value in obj.tools.items():
                 self.addChild(tools, [str(tool), str(value['C'])], True)
                 self.addChild(tools, [str(tool), str(value['C'])], True)

+ 2 - 0
flatcamTools/ToolSolderPaste.py

@@ -1390,6 +1390,8 @@ class SolderPaste(FlatCAMTool):
                 self.app.inform.emit(_("[WARNING_NOTCL] No such file or directory"))
                 self.app.inform.emit(_("[WARNING_NOTCL] No such file or directory"))
                 return
                 return
 
 
+        if self.app.defaults["global_open_style"] is False:
+            self.app.file_opened.emit("gcode", filename)
         self.app.file_saved.emit("gcode", filename)
         self.app.file_saved.emit("gcode", filename)
         self.app.inform.emit(_("[success] Solder paste dispenser GCode file saved to: %s") % filename)
         self.app.inform.emit(_("[success] Solder paste dispenser GCode file saved to: %s") % filename)
 
 

+ 89 - 25
flatcamTools/ToolSub.py

@@ -151,7 +151,10 @@ class ToolSub(FlatCAMTool):
         self.new_tools = {}
         self.new_tools = {}
         self.new_solid_geometry = []
         self.new_solid_geometry = []
 
 
-        self.sub_union = None
+        self.sub_solid_union = None
+        self.sub_follow_union = None
+        self.sub_clear_union = None
+
 
 
         self.sub_grb_obj = None
         self.sub_grb_obj = None
         self.sub_grb_obj_name = None
         self.sub_grb_obj_name = None
@@ -251,12 +254,25 @@ class ToolSub(FlatCAMTool):
             self.new_apertures[apid] = {}
             self.new_apertures[apid] = {}
             self.new_apertures[apid]['type'] = 'C'
             self.new_apertures[apid]['type'] = 'C'
             self.new_apertures[apid]['size'] = self.target_grb_obj.apertures[apid]['size']
             self.new_apertures[apid]['size'] = self.target_grb_obj.apertures[apid]['size']
-            self.new_apertures[apid]['solid_geometry'] = []
+            self.new_apertures[apid]['geometry'] = []
+
+        geo_solid_union_list = []
+        geo_follow_union_list = []
+        geo_clear_union_list = []
 
 
-        geo_union_list = []
         for apid1 in self.sub_grb_obj.apertures:
         for apid1 in self.sub_grb_obj.apertures:
-            geo_union_list += self.sub_grb_obj.apertures[apid1]['solid_geometry']
-        self.sub_union = cascaded_union(geo_union_list)
+            if 'geometry' in self.sub_grb_obj.apertures[apid1]:
+                for elem in self.sub_grb_obj.apertures[apid1]['geometry']:
+                    if 'solid' in elem:
+                        geo_solid_union_list.append(elem['solid'])
+                    if 'follow' in elem:
+                        geo_follow_union_list.append(elem['follow'])
+                    if 'clear' in elem:
+                        geo_clear_union_list.append(elem['clear'])
+
+        self.sub_solid_union = cascaded_union(geo_solid_union_list)
+        self.sub_follow_union = cascaded_union(geo_follow_union_list)
+        self.sub_clear_union = cascaded_union(geo_clear_union_list)
 
 
         # add the promises
         # add the promises
         for apid in self.target_grb_obj.apertures:
         for apid in self.target_grb_obj.apertures:
@@ -266,32 +282,78 @@ class ToolSub(FlatCAMTool):
         self.periodic_check(500, reset=True)
         self.periodic_check(500, reset=True)
 
 
         for apid in self.target_grb_obj.apertures:
         for apid in self.target_grb_obj.apertures:
-            geo = self.target_grb_obj.apertures[apid]['solid_geometry']
+            geo = self.target_grb_obj.apertures[apid]['geometry']
             self.app.worker_task.emit({'fcn': self.aperture_intersection,
             self.app.worker_task.emit({'fcn': self.aperture_intersection,
                                        'params': [apid, geo]})
                                        'params': [apid, geo]})
 
 
     def aperture_intersection(self, apid, geo):
     def aperture_intersection(self, apid, geo):
-        new_solid_geometry = []
+        new_geometry = []
+
         log.debug("Working on promise: %s" % str(apid))
         log.debug("Working on promise: %s" % str(apid))
 
 
         with self.app.proc_container.new(_("Parsing aperture %s geometry ..." % str(apid))):
         with self.app.proc_container.new(_("Parsing aperture %s geometry ..." % str(apid))):
-            for geo_silk in geo:
-                if geo_silk.intersects(self.sub_union):
-                    new_geo = geo_silk.difference(self.sub_union)
-                    new_geo = new_geo.buffer(0)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_solid_geometry.append(new_geo)
+            for geo_el in geo:
+                new_el = dict()
+
+                if 'solid' in geo_el:
+                    work_geo = geo_el['solid']
+                    if self.sub_solid_union:
+                        if work_geo.intersects(self.sub_solid_union):
+                            new_geo = work_geo.difference(self.sub_solid_union)
+                            new_geo = new_geo.buffer(0)
+                            if new_geo:
+                                if not new_geo.is_empty:
+                                    new_el['solid'] = new_geo
+                                else:
+                                    new_el['solid'] = work_geo
+                            else:
+                                new_el['solid'] = work_geo
                         else:
                         else:
-                            new_solid_geometry.append(geo_silk)
+                            new_el['solid'] = work_geo
                     else:
                     else:
-                        new_solid_geometry.append(geo_silk)
-                else:
-                    new_solid_geometry.append(geo_silk)
+                        new_el['solid'] = work_geo
+
+                if 'follow' in geo_el:
+                    work_geo = geo_el['follow']
+                    if self.sub_follow_union:
+                        if work_geo.intersects(self.sub_follow_union):
+                            new_geo = work_geo.difference(self.sub_follow_union)
+                            new_geo = new_geo.buffer(0)
+                            if new_geo:
+                                if not new_geo.is_empty:
+                                    new_el['follow'] = new_geo
+                                else:
+                                    new_el['follow'] = work_geo
+                            else:
+                                new_el['follow'] = work_geo
+                        else:
+                            new_el['follow'] = work_geo
+                    else:
+                        new_el['follow'] = work_geo
+
+                if 'clear' in geo_el:
+                    work_geo = geo_el['clear']
+                    if self.sub_clear_union:
+                        if work_geo.intersects(self.sub_clear_union):
+                            new_geo = work_geo.difference(self.sub_clear_union)
+                            new_geo = new_geo.buffer(0)
+                            if new_geo:
+                                if not new_geo.is_empty:
+                                    new_el['clear'] = new_geo
+                                else:
+                                    new_el['clear'] = work_geo
+                            else:
+                                new_el['clear'] = work_geo
+                        else:
+                            new_el['clear'] = work_geo
+                    else:
+                        new_el['clear'] = work_geo
+
+                new_geometry.append(deepcopy(new_el))
 
 
-        if new_solid_geometry:
-            while not self.new_apertures[apid]['solid_geometry']:
-                self.new_apertures[apid]['solid_geometry'] = deepcopy(new_solid_geometry)
+        if new_geometry:
+            while not self.new_apertures[apid]['geometry']:
+                self.new_apertures[apid]['geometry'] = deepcopy(new_geometry)
                 time.sleep(0.5)
                 time.sleep(0.5)
 
 
         while True:
         while True:
@@ -312,9 +374,11 @@ class ToolSub(FlatCAMTool):
             grb_obj.apertures = deepcopy(self.new_apertures)
             grb_obj.apertures = deepcopy(self.new_apertures)
 
 
             poly_buff = []
             poly_buff = []
+            follow_buff = []
             for ap in self.new_apertures:
             for ap in self.new_apertures:
-                for poly in self.new_apertures[ap]['solid_geometry']:
-                    poly_buff.append(poly)
+                for elem in self.new_apertures[ap]['geometry']:
+                    poly_buff.append(elem['solid'])
+                    follow_buff.append(elem['follow'])
 
 
             work_poly_buff = cascaded_union(poly_buff)
             work_poly_buff = cascaded_union(poly_buff)
             try:
             try:
@@ -327,14 +391,14 @@ class ToolSub(FlatCAMTool):
                 pass
                 pass
 
 
             grb_obj.solid_geometry = deepcopy(poly_buff)
             grb_obj.solid_geometry = deepcopy(poly_buff)
+            grb_obj.follow_geometry = deepcopy(follow_buff)
 
 
         with self.app.proc_container.new(_("Generating new object ...")):
         with self.app.proc_container.new(_("Generating new object ...")):
             ret = self.app.new_object('gerber', outname, obj_init, autoselected=False)
             ret = self.app.new_object('gerber', outname, obj_init, autoselected=False)
             if ret == 'fail':
             if ret == 'fail':
                 self.app.inform.emit(_('[ERROR_NOTCL] Generating new object failed.'))
                 self.app.inform.emit(_('[ERROR_NOTCL] Generating new object failed.'))
                 return
                 return
-            # Register recent file
-            self.app.file_opened.emit('gerber', outname)
+
             # GUI feedback
             # GUI feedback
             self.app.inform.emit(_("[success] Created: %s") % outname)
             self.app.inform.emit(_("[success] Created: %s") % outname)
 
 

BIN
locale/de/LC_MESSAGES/strings.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 223 - 186
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 242 - 211
locale/en/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 232 - 195
locale/ro/LC_MESSAGES/strings.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 245 - 213
locale_template/strings.pot


+ 3 - 2
postprocessors/Toolchange_Custom.py

@@ -157,10 +157,11 @@ M6
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M03 S' + str(p.spindlespeed)
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
         else:
-            return 'M03'
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         if p.dwelltime:
         if p.dwelltime:

+ 3 - 2
postprocessors/Toolchange_Probe_MACH3.py

@@ -260,10 +260,11 @@ M0
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M03 S' + str(p.spindlespeed)
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
         else:
-            return 'M03'
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         if p.dwelltime:
         if p.dwelltime:

+ 4 - 3
postprocessors/Toolchange_manual.py

@@ -220,11 +220,12 @@ M0
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
 
-    def spindle_code(self,p):
+    def spindle_code(self, p):
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M03 S' + str(p.spindlespeed)
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
         else:
-            return 'M03'
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         if p.dwelltime:
         if p.dwelltime:

+ 3 - 2
postprocessors/default.py

@@ -192,10 +192,11 @@ M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchang
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M03 S' + str(p.spindlespeed)
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
         else:
-            return 'M03'
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         if p.dwelltime:
         if p.dwelltime:

+ 4 - 3
postprocessors/grbl_11.py

@@ -191,11 +191,12 @@ M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchang
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
 
-    def spindle_code(self,p):
+    def spindle_code(self, p):
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M03 S%d' % p.spindlespeed
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
         else:
-            return 'M03'
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         if p.dwelltime:
         if p.dwelltime:

+ 5 - 1
postprocessors/grbl_laser.py

@@ -90,7 +90,11 @@ class grbl_laser(FlatCAMPostProc):
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
-        return ''
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
+        if p.spindlespeed:
+            return '%s S%s' % (sdir, str(p.spindlespeed))
+        else:
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         return ''
         return ''

+ 3 - 2
postprocessors/line_xyz.py

@@ -193,10 +193,11 @@ M0""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchang
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
         return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
+        sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M03 S' + str(p.spindlespeed)
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
         else:
-            return 'M03'
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         if p.dwelltime:
         if p.dwelltime:

+ 4 - 3
postprocessors/marlin.py

@@ -198,11 +198,12 @@ M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchang
     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)
 
 
-    def spindle_code(self,p):
+    def spindle_code(self, p):
+        sdir = {'CW': 'M3', 'CCW': 'M4'}[p.spindledir]
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M3 S%d' % p.spindlespeed
+            return '%s S%s' % (sdir, str(p.spindlespeed))
         else:
         else:
-            return 'M3'
+            return sdir
 
 
     def dwell_code(self, p):
     def dwell_code(self, p):
         if p.dwelltime:
         if p.dwelltime:

BIN
share/eraser26.png


+ 35 - 39
tclCommands/TclCommandGeoCutout.py

@@ -2,6 +2,7 @@ from ObjectCollection import *
 from tclCommands.TclCommand import TclCommandSignaled
 from tclCommands.TclCommand import TclCommandSignaled
 from copy import deepcopy
 from copy import deepcopy
 
 
+
 class TclCommandGeoCutout(TclCommandSignaled):
 class TclCommandGeoCutout(TclCommandSignaled):
     """
     """
         Tcl shell command to create a board cutout geometry. Allow cutout for any shape. Cuts holding gaps from geometry.
         Tcl shell command to create a board cutout geometry. Allow cutout for any shape. Cuts holding gaps from geometry.
@@ -65,7 +66,6 @@ class TclCommandGeoCutout(TclCommandSignaled):
         :return:
         :return:
         """
         """
 
 
-
         def subtract_rectangle(obj_, x0, y0, x1, y1):
         def subtract_rectangle(obj_, x0, y0, x1, y1):
             pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
             pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
             obj_.subtract_polygon(pts)
             obj_.subtract_polygon(pts)
@@ -73,7 +73,6 @@ class TclCommandGeoCutout(TclCommandSignaled):
         def substract_rectangle_geo(geo, x0, y0, x1, y1):
         def substract_rectangle_geo(geo, x0, y0, x1, y1):
             pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
             pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
 
 
-
             def flatten(geometry=None, reset=True, pathonly=False):
             def flatten(geometry=None, reset=True, pathonly=False):
                 """
                 """
                 Creates a list of non-iterable linear geometry objects.
                 Creates a list of non-iterable linear geometry objects.
@@ -89,15 +88,15 @@ class TclCommandGeoCutout(TclCommandSignaled):
                 if reset:
                 if reset:
                     self.flat_geometry = []
                     self.flat_geometry = []
 
 
-                ## If iterable, expand recursively.
+                # If iterable, expand recursively.
                 try:
                 try:
-                    for geo in geometry:
-                        if geo is not None:
-                            flatten(geometry=geo,
+                    for geo_el in geometry:
+                        if geo_el is not None:
+                            flatten(geometry=geo_el,
                                     reset=False,
                                     reset=False,
                                     pathonly=pathonly)
                                     pathonly=pathonly)
 
 
-                ## Not iterable, do the actual indexing and add.
+                # Not iterable, do the actual indexing and add.
                 except TypeError:
                 except TypeError:
                     if pathonly and type(geometry) == Polygon:
                     if pathonly and type(geometry) == Polygon:
                         self.flat_geometry.append(geometry.exterior)
                         self.flat_geometry.append(geometry.exterior)
@@ -151,14 +150,15 @@ class TclCommandGeoCutout(TclCommandSignaled):
         # Get source object.
         # Get source object.
         try:
         try:
             cutout_obj = self.app.collection.get_by_name(str(name))
             cutout_obj = self.app.collection.get_by_name(str(name))
-        except:
+        except Exception as e:
+            log.debug("TclCommandGeoCutout --> %s" % str(e))
             return "Could not retrieve object: %s" % name
             return "Could not retrieve object: %s" % name
 
 
         if 0 in {dia}:
         if 0 in {dia}:
             self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive real number.")
             self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive real number.")
             return "Tool Diameter is zero value. Change it to a positive real number."
             return "Tool Diameter is zero value. Change it to a positive real number."
 
 
-        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]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
             self.app.inform.emit("[WARNING]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. ")
             return
             return
@@ -226,47 +226,47 @@ class TclCommandGeoCutout(TclCommandSignaled):
             def geo_init(geo_obj, app_obj):
             def geo_init(geo_obj, app_obj):
                 try:
                 try:
                     geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2, follow=None)
                     geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2, follow=None)
-                except Exception as e:
-                    log.debug("TclCommandGeoCutout.execute() --> %s" % str(e))
+                except Exception as exc:
+                    log.debug("TclCommandGeoCutout.execute() --> %s" % str(exc))
                     return 'fail'
                     return 'fail'
 
 
                 if gaps_u == 8 or gaps_u == '2lr':
                 if gaps_u == 8 or gaps_u == '2lr':
                     geo = substract_rectangle_geo(geo,
                     geo = substract_rectangle_geo(geo,
-                                       xmin - gapsize,  # botleft_x
-                                       py - gapsize + lenghty / 4,  # botleft_y
-                                       xmax + gapsize,  # topright_x
-                                       py + gapsize + lenghty / 4)  # topright_y
+                                                  xmin - gapsize,  # botleft_x
+                                                  py - gapsize + lenghty / 4,  # botleft_y
+                                                  xmax + gapsize,  # topright_x
+                                                  py + gapsize + lenghty / 4)  # topright_y
                     geo = substract_rectangle_geo(geo,
                     geo = substract_rectangle_geo(geo,
-                                       xmin - gapsize,
-                                       py - gapsize - lenghty / 4,
-                                       xmax + gapsize,
-                                       py + gapsize - lenghty / 4)
+                                                  xmin - gapsize,
+                                                  py - gapsize - lenghty / 4,
+                                                  xmax + gapsize,
+                                                  py + gapsize - lenghty / 4)
 
 
                 if gaps_u == 8 or gaps_u == '2tb':
                 if gaps_u == 8 or gaps_u == '2tb':
                     geo = substract_rectangle_geo(geo,
                     geo = substract_rectangle_geo(geo,
-                                       px - gapsize + lenghtx / 4,
-                                       ymin - gapsize,
-                                       px + gapsize + lenghtx / 4,
-                                       ymax + gapsize)
+                                                  px - gapsize + lenghtx / 4,
+                                                  ymin - gapsize,
+                                                  px + gapsize + lenghtx / 4,
+                                                  ymax + gapsize)
                     geo = substract_rectangle_geo(geo,
                     geo = substract_rectangle_geo(geo,
-                                       px - gapsize - lenghtx / 4,
-                                       ymin - gapsize,
-                                       px + gapsize - lenghtx / 4,
-                                       ymax + gapsize)
+                                                  px - gapsize - lenghtx / 4,
+                                                  ymin - gapsize,
+                                                  px + gapsize - lenghtx / 4,
+                                                  ymax + gapsize)
 
 
                 if gaps_u == 4 or gaps_u == 'lr':
                 if gaps_u == 4 or gaps_u == 'lr':
                     geo = substract_rectangle_geo(geo,
                     geo = substract_rectangle_geo(geo,
-                                       xmin - gapsize,
-                                       py - gapsize,
-                                       xmax + gapsize,
-                                       py + gapsize)
+                                                  xmin - gapsize,
+                                                  py - gapsize,
+                                                  xmax + gapsize,
+                                                  py + gapsize)
 
 
                 if gaps_u == 4 or gaps_u == 'tb':
                 if gaps_u == 4 or gaps_u == 'tb':
                     geo = substract_rectangle_geo(geo,
                     geo = substract_rectangle_geo(geo,
-                                       px - gapsize,
-                                       ymin - gapsize,
-                                       px + gapsize,
-                                       ymax + gapsize)
+                                                  px - gapsize,
+                                                  ymin - gapsize,
+                                                  px + gapsize,
+                                                  ymax + gapsize)
                 geo_obj.solid_geometry = geo
                 geo_obj.solid_geometry = geo
 
 
             outname = cutout_obj.options["name"] + "_cutout"
             outname = cutout_obj.options["name"] + "_cutout"
@@ -276,7 +276,3 @@ class TclCommandGeoCutout(TclCommandSignaled):
         else:
         else:
             self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
             self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
             return
             return
-
-
-
-

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است