Преглед изворни кода

- added to GUI new options for the Gerber object related to area subtraction
- added new feature in the Gerber object isolation allowing for the isolation to avoid an area defined by another object (Gerber or Geometry)

Marius Stanciu пре 6 година
родитељ
комит
f164dae7a9
5 измењених фајлова са 294 додато и 122 уклоњено
  1. 59 49
      FlatCAMApp.py
  2. 101 30
      FlatCAMObj.py
  3. 2 0
      README.md
  4. 39 0
      flatcamGUI/GUIElements.py
  5. 93 43
      flatcamGUI/ObjectUI.py

+ 59 - 49
FlatCAMApp.py

@@ -1090,9 +1090,9 @@ class App(QtCore.QObject):
         if user_defaults:
             self.load_defaults(filename='current_defaults')
 
-        # ###########################
-        # #### APPLY APP LANGUAGE ###
-        # ###########################
+        # #############################################################
+        # ############## APPLY APP LANGUAGE ###########################
+        # #############################################################
 
         ret_val = fcTranslate.apply_language('strings')
 
@@ -1104,9 +1104,9 @@ class App(QtCore.QObject):
             self.ui.general_defaults_form.general_app_group.language_cb.setCurrentText(ret_val)
             log.debug("App.__init__() --> Applied %s language." % str(ret_val).capitalize())
 
-        # ##################################
-        # ### CREATE UNIQUE SERIAL NUMBER ##
-        # ##################################
+        # ##############################################################
+        # ################# CREATE UNIQUE SERIAL NUMBER ################
+        # ##############################################################
 
         chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
         if self.defaults['global_serial'] == 0 or len(str(self.defaults['global_serial'])) < 10:
@@ -1495,20 +1495,27 @@ class App(QtCore.QObject):
         # ############### Signal handling ################
         # ################################################
 
-        # ### Custom signals  ###
+        # ############# Custom signals  ##################
+
+        # signal for displaying messages in status bar
         self.inform.connect(self.info)
+        # signal to be called when the app is quiting
         self.app_quit.connect(self.quit_application)
         self.message.connect(self.message_dialog)
         self.progress.connect(self.set_progress_bar)
+
+        # signals that are emitted when object state changes
         self.object_created.connect(self.on_object_created)
         self.object_changed.connect(self.on_object_changed)
         self.object_plotted.connect(self.on_object_plotted)
         self.plots_updated.connect(self.on_plots_updated)
+
+        # signals emitted when file state change
         self.file_opened.connect(self.register_recent)
         self.file_opened.connect(lambda kind, filename: self.register_folder(filename))
         self.file_saved.connect(lambda kind, filename: self.register_save_folder(filename))
 
-        # ### Standard signals
+        # ############# Standard signals ###################
         # ### Menu
         self.ui.menufilenewproject.triggered.connect(self.on_file_new_click)
         self.ui.menufilenewgeo.triggered.connect(self.new_geometry_object)
@@ -1675,9 +1682,9 @@ class App(QtCore.QObject):
         self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
             lambda: self.on_toggle_units(no_pref=False))
 
-        # ##############################
-        # ### GUI PREFERENCES SIGNALS ##
-        # ##############################
+        # ###############################################################
+        # ################### GUI COLORS SIGNALS ########################
+        # ###############################################################
 
         # Setting plot colors signals
         self.ui.general_defaults_form.general_gui_group.pf_color_entry.editingFinished.connect(
@@ -1739,11 +1746,13 @@ class App(QtCore.QObject):
         self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.clicked.connect(
             self.on_proj_color_dis_button)
 
+        # ########## workspace setting signals ###########
         self.ui.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified)
         self.ui.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace)
 
         self.ui.general_defaults_form.general_gui_set_group.layout_combo.activated.connect(self.on_layout)
 
+        # ########## CNC Job related signals #############
         self.ui.cncjob_defaults_form.cncjob_adv_opt_group.tc_variable_combo.currentIndexChanged[str].connect(
             self.on_cnc_custom_parameters)
         self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_entry.editingFinished.connect(
@@ -1751,7 +1760,7 @@ class App(QtCore.QObject):
         self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_button.clicked.connect(
             self.on_annotation_fontcolor_button)
 
-        # Modify G-CODE Plot Area TAB
+        # ########## Modify G-CODE Plot Area TAB ###########
         self.ui.code_editor.textChanged.connect(self.handleTextChanged)
         self.ui.buttonOpen.clicked.connect(self.handleOpen)
         self.ui.buttonSave.clicked.connect(self.handleSaveGCode)
@@ -1760,7 +1769,7 @@ class App(QtCore.QObject):
         self.ui.buttonFind.clicked.connect(self.handleFindGCode)
         self.ui.buttonReplace.clicked.connect(self.handleReplaceGCode)
 
-        # portability changed
+        # portability changed signal
         self.ui.general_defaults_form.general_app_group.portability_cb.stateChanged.connect(self.on_portable_checked)
 
         # Object list
@@ -1791,8 +1800,15 @@ class App(QtCore.QObject):
         # connect the abort_all_tasks related slots to the related signals
         self.proc_container.idle_flag.connect(self.app_is_idle)
 
+        # #####################################################################################
+        # ########### FINISHED CONNECTING SIGNALS #############################################
+        # #####################################################################################
         self.log.debug("Finished connecting Signals.")
 
+        # #####################################################################################
+        # ########################## Other setups #############################################
+        # #####################################################################################
+
         # this is a flag to signal to other tools that the ui tooltab is locked and not accessible
         self.tool_tab_locked = False
 
@@ -1802,18 +1818,14 @@ class App(QtCore.QObject):
         else:
             self.ui.splitter.setSizes([0, 1])
 
-        # ###########################################
-        # ################# Other setups ############
-        # ###########################################
-
         # Sets up FlatCAMObj, FCProcess and FCProcessContainer.
         self.setup_obj_classes()
         self.setup_recent_items()
         self.setup_component_editor()
 
-        # ###########################################
-        # #######Auto-complete KEYWORDS #############
-        # ###########################################
+        # #####################################################################################
+        # ######################### Auto-complete KEYWORDS ####################################
+        # #####################################################################################
         self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle',
                                   'aligndrill', 'aligndrillgrid', 'bbox', 'bounding_box', 'clear', 'cncjob', 'cutout',
                                   'delete', 'drillcncjob', 'export_gcode', 'export_svg', 'ext', 'exteriors', 'follow',
@@ -2028,9 +2040,9 @@ class App(QtCore.QObject):
 
         self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords
 
-        # ###########################################
-        # ########### Shell SETUP ###################
-        # ###########################################
+        # ####################################################################################
+        # ####################### Shell SETUP ################################################
+        # ####################################################################################
 
         self.shell = FCShell(self, version=self.version)
         self.shell._edit.set_model_data(self.myKeywords)
@@ -2058,9 +2070,9 @@ class App(QtCore.QObject):
         else:
             self.ui.shell_dock.hide()
 
-        # ###########################################
-        # ######### Tools and Plugins ###############
-        # ###########################################
+        # ##################################################################################
+        # ###################### Tools and Plugins #########################################
+        # ##################################################################################
 
         self.dblsidedtool = None
         self.measurement_tool = None
@@ -2086,9 +2098,9 @@ class App(QtCore.QObject):
         # self.f_parse = ParseFont(self)
         # self.parse_system_fonts()
 
-        # ###############################################
-        # ######## START-UP ARGUMENTS ###################
-        # ###############################################
+        # #####################################################################################
+        # ########################## START-UP ARGUMENTS #######################################
+        # #####################################################################################
 
         # test if the program was started with a script as parameter
         if self.cmd_line_shellfile:
@@ -2100,9 +2112,9 @@ class App(QtCore.QObject):
                 print("ERROR: ", ext)
                 sys.exit(2)
 
-        # ###############################################
-        # ############# Check for updates ###############
-        # ###############################################
+        # #####################################################################################
+        # ######################## Check for updates ##########################################
+        # #####################################################################################
 
         # Separate thread (Not worker)
         # Check for updates on startup but only if the user consent and the app is not in Beta version
@@ -2115,9 +2127,9 @@ class App(QtCore.QObject):
                                    'params': []})
             self.thr2.start(QtCore.QThread.LowPriority)
 
-        # ################################################
-        # ######### Variables for global usage ###########
-        # ################################################
+        # #####################################################################################
+        # ###################### Variables for global usage ###################################
+        # #####################################################################################
 
         # register files with FlatCAM; it works only for Windows for now
         if sys.platform == 'win32' and self.defaults["first_run"] is True:
@@ -2199,10 +2211,10 @@ class App(QtCore.QObject):
         # when True, the app has to return from any thread
         self.abort_flag = False
 
-        # #########################################################
-        # ### Save defaults to factory_defaults.FlatConfig file ###
-        # ### It's done only once after install ###################
-        # #########################################################
+        # ###############################################################################
+        # ############# Save defaults to factory_defaults.FlatConfig file ###############
+        # ############# It's done only once after install                 ###############
+        # ###############################################################################
         factory_file = open(self.data_path + '/factory_defaults.FlatConfig')
         fac_def_from_file = factory_file.read()
         factory_defaults = json.loads(fac_def_from_file)
@@ -2223,9 +2235,9 @@ class App(QtCore.QObject):
         filename_factory = self.data_path + '/factory_defaults.FlatConfig'
         os.chmod(filename_factory, S_IREAD | S_IRGRP | S_IROTH)
 
-        ####################################################
-        # ### ADDING FlatCAM EDITORS section ###############
-        ####################################################
+        # ###############################################################################
+        # ################# ADDING FlatCAM EDITORS section ##############################
+        # ###############################################################################
 
         # watch out for the position of the editors instantiation ... if it is done before a save of the default values
         # at the first launch of the App , the editors will not be functional.
@@ -2234,18 +2246,16 @@ class App(QtCore.QObject):
         self.grb_editor = FlatCAMGrbEditor(self)
         self.log.debug("Finished adding FlatCAM Editor's.")
 
-        # Post-GUI initialization: Experimental attempt
-        # to perform unit tests on the GUI.
-        # if post_gui is not None:
-        #     post_gui(self)
-
-        App.log.debug("END of constructor. Releasing control.")
-
         self.set_ui_title(name=_("New Project - Not saved"))
 
         # after the first run, this object should be False
         self.defaults["first_run"] = False
 
+        # ###############################################################################
+        # ####################### Finished the CONSTRUCTOR ##############################
+        # ###############################################################################
+        App.log.debug("END of constructor. Releasing control.")
+
         # accept some type file as command line parameter: FlatCAM project, FlatCAM preferences or scripts
         # the path/file_name must be enclosed in quotes if it contain spaces
         if App.args:

+ 101 - 30
FlatCAMObj.py

@@ -339,6 +339,10 @@ class FlatCAMObj(QtCore.QObject):
             key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, **kwargs)
         return key
 
+    @staticmethod
+    def poly2rings(poly):
+        return [poly.exterior] + [interior for interior in poly.interiors]
+
     @property
     def visible(self):
         return self.shapes.visible
@@ -551,6 +555,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
         self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
 
+        # set the model for the Area Exception comboboxes
+        self.ui.obj_combo.setModel(self.app.collection)
+        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ui.obj_combo.setCurrentIndex(1)
+        self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
             self.ui.level.setText(_(
@@ -563,12 +572,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.ui.generate_ext_iso_button.hide()
             self.ui.generate_int_iso_button.hide()
             self.ui.follow_cb.hide()
-            self.ui.padding_area_label.show()
+            self.ui.except_cb.setChecked(False)
+            self.ui.except_cb.hide()
         else:
             self.ui.level.setText(_(
                 '<span style="color:red;"><b>Advanced</b></span>'
             ))
-            self.ui.padding_area_label.hide()
 
         # add the shapes storage for marking apertures
         for ap_code in self.apertures:
@@ -579,6 +588,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
         self.build_ui()
 
+    def on_type_obj_index_changed(self, index):
+        obj_type = self.ui.type_obj_combo.currentIndex()
+        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ui.obj_combo.setCurrentIndex(0)
+
     def build_ui(self):
         FlatCAMObj.build_ui(self)
 
@@ -881,23 +895,22 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
             if invert:
                 try:
-                    try:
-                        pl = []
-                        for p in geom:
-                            if p is not None:
-                                if isinstance(p, Polygon):
-                                    pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
-                                elif isinstance(p, LinearRing):
-                                    pl.append(Polygon(p.coords[::-1]))
-                        geom = MultiPolygon(pl)
-                    except TypeError:
-                        if isinstance(geom, Polygon) and geom is not None:
-                            geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
-                        elif isinstance(geom, LinearRing) and geom is not None:
-                            geom = Polygon(geom.coords[::-1])
-                        else:
-                            log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" %
-                                      type(geom))
+                    pl = []
+                    for p in geom:
+                        if p is not None:
+                            if isinstance(p, Polygon):
+                                pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
+                            elif isinstance(p, LinearRing):
+                                pl.append(Polygon(p.coords[::-1]))
+                    geom = MultiPolygon(pl)
+                except TypeError:
+                    if isinstance(geom, Polygon) and geom is not None:
+                        geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
+                    elif isinstance(geom, LinearRing) and geom is not None:
+                        geom = Polygon(geom.coords[::-1])
+                    else:
+                        log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" %
+                                  type(geom))
                 except Exception as e:
                     log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> %s" % str(e))
                     return 'fail'
@@ -924,6 +937,54 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         # if float(self.options["isotooldia"]) < 0:
         #     self.options["isotooldia"] = -self.options["isotooldia"]
 
+        def area_subtraction(geo):
+            new_geometry = []
+
+            name = self.ui.obj_combo.currentText()
+            subtractor_obj = self.app.collection.get_by_name(name)
+            sub_union = cascaded_union(subtractor_obj.solid_geometry)
+
+            try:
+                for geo_elem in geo:
+                    if isinstance(geo_elem, Polygon):
+                        for ring in self.poly2rings(geo_elem):
+                            new_geo = ring.difference(sub_union)
+                            if new_geo and not new_geo.is_empty:
+                                new_geometry.append(new_geo)
+                    elif isinstance(geo_elem, MultiPolygon):
+                        for poly in geo_elem:
+                            for ring in self.poly2rings(poly):
+                                new_geo = ring.difference(sub_union)
+                                if new_geo and not new_geo.is_empty:
+                                    new_geometry.append(new_geo)
+                    elif isinstance(geo_elem, LineString):
+                        new_geo = geo_elem.difference(sub_union)
+                        if new_geo:
+                            if not new_geo.is_empty:
+                                new_geometry.append(new_geo)
+                    elif isinstance(geo_elem, MultiLineString):
+                        for line_elem in geo_elem:
+                            new_geo = line_elem.difference(sub_union)
+                            if new_geo and not new_geo.is_empty:
+                                new_geometry.append(new_geo)
+            except TypeError:
+                if isinstance(geo, Polygon):
+                    for ring in self.poly2rings(geo):
+                        new_geo = ring.difference(sub_union)
+                        if new_geo:
+                            if not new_geo.is_empty:
+                                new_geometry.append(new_geo)
+                elif isinstance(geo, LineString):
+                    new_geo = geo.difference(sub_union)
+                    if new_geo and not new_geo.is_empty:
+                        new_geometry.append(new_geo)
+                elif isinstance(geo, MultiLineString):
+                    for line_elem in geo:
+                        new_geo = line_elem.difference(sub_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+            return new_geometry
+
         if combine:
             if self.iso_type == 0:
                 iso_name = self.options["name"] + "_ext_iso"
@@ -1001,21 +1062,24 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
                 for g in geo_obj.solid_geometry:
                     if g:
-                        app_obj.inform.emit(_(
-                            "[success] Isolation geometry created: %s"
-                        ) % geo_obj.options["name"])
                         break
                     else:
                         empty_cnt += 1
 
                 if empty_cnt == len(geo_obj.solid_geometry):
                     raise ValidationError("Empty Geometry", None)
-
-                # even if combine is checked, one pass is still singlegeo
-                if passes > 1:
-                    geo_obj.multigeo = True
                 else:
-                    geo_obj.multigeo = False
+                    app_obj.inform.emit('[success] %s" %s' %
+                                        (_("Isolation geometry created"), geo_obj.options["name"]))
+
+                # even if combine is checked, one pass is still single-geo
+                geo_obj.multigeo = True if passes > 1 else False
+
+                # ############################################################
+                # ########## AREA SUBTRACTION ################################
+                # ############################################################
+                if self.ui.except_cb.get_value():
+                    geo_obj.solid_geometry = area_subtraction(geo_obj.solid_geometry)
 
             # TODO: Do something if this is None. Offer changing name?
             self.app.new_object("geometry", iso_name, iso_init)
@@ -1065,16 +1129,23 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
                     for g in geo_obj.solid_geometry:
                         if g:
-                            app_obj.inform.emit(_(
-                                "[success] Isolation geometry created: %s"
-                            ) % geo_obj.options["name"])
                             break
                         else:
                             empty_cnt += 1
+
                     if empty_cnt == len(geo_obj.solid_geometry):
                         raise ValidationError("Empty Geometry", None)
+                    else:
+                        app_obj.inform.emit('[success] %s: %s' %
+                                            (_("Isolation geometry created"), geo_obj.options["name"]))
                     geo_obj.multigeo = False
 
+                    # ############################################################
+                    # ########## AREA SUBTRACTION ################################
+                    # ############################################################
+                    if self.ui.except_cb.get_value():
+                        geo_obj.solid_geometry = area_subtraction(geo_obj.solid_geometry)
+
                 # TODO: Do something if this is None. Offer changing name?
                 self.app.new_object("geometry", iso_name, iso_init)
 

+ 2 - 0
README.md

@@ -14,6 +14,8 @@ CAD program, and create G-Code for Isolation routing.
 - added a method to gracefully exit from threaded tasks and implemented it for the NCC Tool and for the Paint Tool
 - modified the on_about() function to reflect the reality in 2019 - FlatCAM it is an Open Source contributed software
 - remade the handlers for the Enable/Disable Project Tree context menu so they are threaded and activity is shown in the lower right corner of the main window
+- added to GUI new options for the Gerber object related to area subtraction
+- added new feature in the Gerber object isolation allowing for the isolation to avoid an area defined by another object (Gerber or Geometry)
 
 6.09.2019
 

+ 39 - 0
flatcamGUI/GUIElements.py

@@ -1524,6 +1524,45 @@ class OptionalInputSection:
                     widget.setEnabled(True)
 
 
+class OptionalHideInputSection:
+
+    def __init__(self, cb, optinputs, logic=True):
+        """
+        Associates the a checkbox with a set of inputs.
+
+        :param cb: Checkbox that enables the optional inputs.
+        :param optinputs: List of widgets that are optional.
+        :param logic: When True the logic is normal, when False the logic is in reverse
+        It means that for logic=True, when the checkbox is checked the widgets are Enabled, and
+        for logic=False, when the checkbox is checked the widgets are Disabled
+        :return:
+        """
+        assert isinstance(cb, FCCheckBox), \
+            "Expected an FCCheckBox, got %s" % type(cb)
+
+        self.cb = cb
+        self.optinputs = optinputs
+        self.logic = logic
+
+        self.on_cb_change()
+        self.cb.stateChanged.connect(self.on_cb_change)
+
+    def on_cb_change(self):
+
+        if self.cb.checkState():
+            for widget in self.optinputs:
+                if self.logic is True:
+                    widget.show()
+                else:
+                    widget.hide()
+        else:
+            for widget in self.optinputs:
+                if self.logic is True:
+                    widget.hide()
+                else:
+                    widget.show()
+
+
 class FCTable(QtWidgets.QTableWidget):
     def __init__(self, parent=None):
         super(FCTable, self).__init__(parent)

+ 93 - 43
flatcamGUI/ObjectUI.py

@@ -254,8 +254,13 @@ class GerberObjectUI(ObjectUI):
         )
         self.custom_box.addWidget(self.isolation_routing_label)
 
+        # ###########################################
+        # ########## NEW GRID #######################
+        # ###########################################
+
         grid1 = QtWidgets.QGridLayout()
         self.custom_box.addLayout(grid1)
+
         tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
         tdlabel.setToolTip(
             _("Diameter of the cutting tool.\n"
@@ -265,9 +270,9 @@ class GerberObjectUI(ObjectUI):
               "this parameter.")
         )
         tdlabel.setMinimumWidth(90)
-        grid1.addWidget(tdlabel, 0, 0)
         self.iso_tool_dia_entry = LengthEntry()
-        grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
+        grid1.addWidget(tdlabel, 0, 0)
+        grid1.addWidget(self.iso_tool_dia_entry, 0, 1, 1, 2)
 
         passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
         passlabel.setToolTip(
@@ -275,10 +280,10 @@ class GerberObjectUI(ObjectUI):
               "number (integer) of tool widths.")
         )
         passlabel.setMinimumWidth(90)
-        grid1.addWidget(passlabel, 1, 0)
         self.iso_width_entry = FCSpinner()
         self.iso_width_entry.setRange(1, 999)
-        grid1.addWidget(self.iso_width_entry, 1, 1)
+        grid1.addWidget(passlabel, 1, 0)
+        grid1.addWidget(self.iso_width_entry, 1, 1, 1, 2)
 
         overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
         overlabel.setToolTip(
@@ -287,9 +292,9 @@ class GerberObjectUI(ObjectUI):
               "A value here of 0.25 means an overlap of 25% from the tool diameter found above.")
         )
         overlabel.setMinimumWidth(90)
-        grid1.addWidget(overlabel, 2, 0)
         self.iso_overlap_entry = FloatEntry()
-        grid1.addWidget(self.iso_overlap_entry, 2, 1)
+        grid1.addWidget(overlabel, 2, 0)
+        grid1.addWidget(self.iso_overlap_entry, 2, 1, 1, 2)
 
         # Milling Type Radio Button
         self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
@@ -298,27 +303,69 @@ class GerberObjectUI(ObjectUI):
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
-        grid1.addWidget(self.milling_type_label, 3, 0)
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
                                             {'label': _('Conv.'), 'value': 'cv'}])
-        grid1.addWidget(self.milling_type_radio, 3, 1)
+        grid1.addWidget(self.milling_type_label, 3, 0)
+        grid1.addWidget(self.milling_type_radio, 3, 1, 1, 2)
 
         # combine all passes CB
         self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
         self.combine_passes_cb.setToolTip(
             _("Combine all passes into one object")
         )
-        grid1.addWidget(self.combine_passes_cb, 4, 0)
 
         # generate follow
         self.follow_cb = FCCheckBox(label=_('"Follow"'))
-        self.follow_cb.setToolTip(
-           _("Generate a 'Follow' geometry.\n"
-             "This means that it will cut through\n"
-             "the middle of the trace.")
+        self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
+                                    "This means that it will cut through\n"
+                                    "the middle of the trace."))
 
-        )
+        # avoid an area from isolation
+        self.except_cb = FCCheckBox(label=_('Except'))
+        self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
+                                    "by checking this, the area of the object bellow\n"
+                                    "will be subtracted from the isolation geometry."))
+
+        grid1.addWidget(self.combine_passes_cb, 4, 0)
         grid1.addWidget(self.follow_cb, 4, 1)
+        grid1.addWidget(self.except_cb, 4, 2)
+
+        # ## Form Layout
+        form_layout = QtWidgets.QFormLayout()
+        grid1.addLayout(form_layout, 5, 0, 1, 3)
+
+        # ################################################
+        # ##### Type of object to be excepted ############
+        # ################################################
+        self.type_obj_combo = QtWidgets.QComboBox()
+        self.type_obj_combo.addItem("Gerber")
+        self.type_obj_combo.addItem("Excellon")
+        self.type_obj_combo.addItem("Geometry")
+
+        # we get rid of item1 ("Excellon") as it is not suitable
+        self.type_obj_combo.view().setRowHidden(1, True)
+        self.type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
+        self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
+
+        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
+        self.type_obj_combo_label.setToolTip(
+            _("Specify the type of object to be excepted from isolation.\n"
+              "It can be of type: Gerber or Geometry.\n"
+              "What is selected here will dictate the kind\n"
+              "of objects that will populate the 'Object' combobox.")
+        )
+        # self.type_obj_combo_label.setMinimumWidth(60)
+        form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
+
+        # ################################################
+        # ##### The object to be excepted ################
+        # ################################################
+        self.obj_combo = QtWidgets.QComboBox()
+
+        self.obj_label = QtWidgets.QLabel('%s:' % _("Object"))
+        self.obj_label.setToolTip(_("Object whose area will be removed from isolation geometry."))
+
+        form_layout.addRow(self.obj_label, self.obj_combo)
 
         self.gen_iso_label = QtWidgets.QLabel("<b>%s:</b>" % _("Generate Isolation Geometry"))
         self.gen_iso_label.setToolTip(
@@ -332,14 +379,7 @@ class GerberObjectUI(ObjectUI):
               "inside the actual Gerber feature, use a negative tool\n"
               "diameter above.")
         )
-        self.custom_box.addWidget(self.gen_iso_label)
-
-        hlay_1 = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(hlay_1)
-
-        self.padding_area_label = QtWidgets.QLabel('')
-        self.padding_area_label.setMinimumWidth(90)
-        hlay_1.addWidget(self.padding_area_label)
+        grid1.addWidget(self.gen_iso_label, 6, 0, 1, 3)
 
         self.generate_iso_button = QtWidgets.QPushButton(_('FULL Geo'))
         self.generate_iso_button.setToolTip(
@@ -347,10 +387,10 @@ class GerberObjectUI(ObjectUI):
               "for isolation routing. It contains both\n"
               "the interiors and exteriors geometry.")
         )
-        self.generate_iso_button.setMinimumWidth(90)
-        hlay_1.addWidget(self.generate_iso_button, alignment=Qt.AlignLeft)
+        grid1.addWidget(self.generate_iso_button, 7, 0)
 
-        # hlay_1.addStretch()
+        hlay_1 = QtWidgets.QHBoxLayout()
+        grid1.addLayout(hlay_1, 7, 1, 1, 2)
 
         self.generate_ext_iso_button = QtWidgets.QPushButton(_('Ext Geo'))
         self.generate_ext_iso_button.setToolTip(
@@ -370,14 +410,28 @@ class GerberObjectUI(ObjectUI):
         # self.generate_ext_iso_button.setMinimumWidth(90)
         hlay_1.addWidget(self.generate_int_iso_button)
 
+        self.ohis_iso = OptionalHideInputSection(
+            self.except_cb,
+            [self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
+            logic=True
+        )
         # when the follow checkbox is checked then the exteriors and interiors isolation generation buttons
         # are disabled as is doesn't make sense to have them enabled due of the nature of "follow"
         self.ois_iso = OptionalInputSection(self.follow_cb,
                                             [self.generate_int_iso_button, self.generate_ext_iso_button], logic=False)
 
+        grid1.addWidget(QtWidgets.QLabel(''), 8, 0)
+
+        # ###########################################
+        # ########## NEW GRID #######################
+        # ###########################################
+
         grid2 = QtWidgets.QGridLayout()
         self.custom_box.addLayout(grid2)
 
+        self.tool_lbl = QtWidgets.QLabel('<b>%s</b>:' % _("TOOLS"))
+        grid2.addWidget(self.tool_lbl, 0, 0, 1, 2)
+
         # ## Clear non-copper regions
         self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Clear N-copper"))
         self.clearcopper_label.setToolTip(
@@ -385,14 +439,14 @@ class GerberObjectUI(ObjectUI):
               "toolpaths to cut all non-copper regions.")
         )
         self.clearcopper_label.setMinimumWidth(90)
-        grid2.addWidget(self.clearcopper_label, 0, 0)
 
         self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
         self.generate_ncc_button.setToolTip(
             _("Create the Geometry Object\n"
               "for non-copper routing.")
         )
-        grid2.addWidget(self.generate_ncc_button, 0, 1)
+        grid2.addWidget(self.clearcopper_label, 1, 0)
+        grid2.addWidget(self.generate_ncc_button, 1, 1)
 
         # ## Board cutout
         self.board_cutout_label = QtWidgets.QLabel("<b>%s:</b>" % _("Board cutout"))
@@ -401,14 +455,14 @@ class GerberObjectUI(ObjectUI):
               "the PCB and separate it from\n"
               "the original board.")
         )
-        grid2.addWidget(self.board_cutout_label, 1, 0)
 
         self.generate_cutout_button = QtWidgets.QPushButton(_('Cutout Tool'))
         self.generate_cutout_button.setToolTip(
             _("Generate the geometry for\n"
               "the board cutout.")
         )
-        grid2.addWidget(self.generate_cutout_button, 1, 1)
+        grid2.addWidget(self.board_cutout_label, 2, 0)
+        grid2.addWidget(self.generate_cutout_button, 2, 1)
 
         # ## Non-copper regions
         self.noncopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
@@ -419,10 +473,8 @@ class GerberObjectUI(ObjectUI):
               "object. Can be used to remove all\n"
               "copper from a specified region.")
         )
-        self.custom_box.addWidget(self.noncopper_label)
 
-        grid4 = QtWidgets.QGridLayout()
-        self.custom_box.addLayout(grid4)
+        grid2.addWidget(self.noncopper_label, 3, 0, 1, 2)
 
         # Margin
         bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
@@ -433,9 +485,9 @@ class GerberObjectUI(ObjectUI):
               "distance.")
         )
         bmlabel.setMinimumWidth(90)
-        grid4.addWidget(bmlabel, 0, 0)
         self.noncopper_margin_entry = LengthEntry()
-        grid4.addWidget(self.noncopper_margin_entry, 0, 1)
+        grid2.addWidget(bmlabel, 4, 0)
+        grid2.addWidget(self.noncopper_margin_entry, 4, 1)
 
         # Rounded corners
         self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
@@ -443,10 +495,10 @@ class GerberObjectUI(ObjectUI):
             _("Resulting geometry will have rounded corners.")
         )
         self.noncopper_rounded_cb.setMinimumWidth(90)
-        grid4.addWidget(self.noncopper_rounded_cb, 1, 0)
 
         self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo'))
-        grid4.addWidget(self.generate_noncopper_button, 1, 1)
+        grid2.addWidget(self.noncopper_rounded_cb, 5, 0)
+        grid2.addWidget(self.generate_noncopper_button, 5, 1)
 
         # ## Bounding box
         self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
@@ -454,10 +506,8 @@ class GerberObjectUI(ObjectUI):
             _("Create a geometry surrounding the Gerber object.\n"
               "Square shape.")
         )
-        self.custom_box.addWidget(self.boundingbox_label)
 
-        grid5 = QtWidgets.QGridLayout()
-        self.custom_box.addLayout(grid5)
+        grid2.addWidget(self.boundingbox_label, 6, 0, 1, 2)
 
         bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
         bbmargin.setToolTip(
@@ -465,9 +515,9 @@ class GerberObjectUI(ObjectUI):
               "to the nearest polygon.")
         )
         bbmargin.setMinimumWidth(90)
-        grid5.addWidget(bbmargin, 0, 0)
         self.bbmargin_entry = LengthEntry()
-        grid5.addWidget(self.bbmargin_entry, 0, 1)
+        grid2.addWidget(bbmargin, 7, 0)
+        grid2.addWidget(self.bbmargin_entry, 7, 1)
 
         self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo"))
         self.bbrounded_cb.setToolTip(
@@ -477,13 +527,13 @@ class GerberObjectUI(ObjectUI):
               "the margin.")
         )
         self.bbrounded_cb.setMinimumWidth(90)
-        grid5.addWidget(self.bbrounded_cb, 1, 0)
 
         self.generate_bb_button = QtWidgets.QPushButton(_('Generate Geo'))
         self.generate_bb_button.setToolTip(
             _("Generate the Geometry object.")
         )
-        grid5.addWidget(self.generate_bb_button, 1, 1)
+        grid2.addWidget(self.bbrounded_cb, 8, 0)
+        grid2.addWidget(self.generate_bb_button, 8, 1)
 
 
 class ExcellonObjectUI(ObjectUI):