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

- fixed open handlers
- fixed issue in NCC Tool where the tool table context menu could be installed multiple times
- added new ability to create simple isolation's in the NCC Tool

Marius Stanciu 6 лет назад
Родитель
Сommit
1295a94af1
4 измененных файлов с 292 добавлено и 83 удалено
  1. 8 11
      FlatCAMApp.py
  2. 6 0
      README.md
  3. 1 1
      flatcamGUI/ObjectUI.py
  4. 277 71
      flatcamTools/ToolNonCopperClear.py

+ 8 - 11
FlatCAMApp.py

@@ -6764,7 +6764,7 @@ class App(QtCore.QObject):
         self.report_usage("obj_move()")
         self.report_usage("obj_move()")
         self.move_tool.run(toggle=False)
         self.move_tool.run(toggle=False)
 
 
-    def on_fileopengerber(self, name=None):
+    def on_fileopengerber(self, checked=None, name=None):
         """
         """
         File menu callback for opening a Gerber.
         File menu callback for opening a Gerber.
 
 
@@ -6802,10 +6802,9 @@ class App(QtCore.QObject):
         else:
         else:
             for filename in filenames:
             for filename in filenames:
                 if filename != '':
                 if filename != '':
-                    self.worker_task.emit({'fcn': self.open_gerber,
-                                           'params': [filename]})
+                    self.worker_task.emit({'fcn': self.open_gerber, 'params': [filename]})
 
 
-    def on_fileopenexcellon(self, name=None):
+    def on_fileopenexcellon(self, checked=None, name=None):
         """
         """
         File menu callback for opening an Excellon file.
         File menu callback for opening an Excellon file.
 
 
@@ -6833,10 +6832,9 @@ class App(QtCore.QObject):
         else:
         else:
             for filename in filenames:
             for filename in filenames:
                 if filename != '':
                 if filename != '':
-                    self.worker_task.emit({'fcn': self.open_excellon,
-                                           'params': [filename]})
+                    self.worker_task.emit({'fcn': self.open_excellon, 'params': [filename]})
 
 
-    def on_fileopengcode(self, name=None):
+    def on_fileopengcode(self, checked=None, name=None):
         """
         """
         File menu call back for opening gcode.
         File menu call back for opening gcode.
 
 
@@ -6868,10 +6866,9 @@ class App(QtCore.QObject):
         else:
         else:
             for filename in filenames:
             for filename in filenames:
                 if filename != '':
                 if filename != '':
-                    self.worker_task.emit({'fcn': self.open_gcode,
-                                           'params': [filename]})
+                    self.worker_task.emit({'fcn': self.open_gcode, 'params': [filename]})
 
 
-    def on_file_openproject(self):
+    def on_file_openproject(self, checked=None):
         """
         """
         File menu callback for opening a project.
         File menu callback for opening a project.
 
 
@@ -6901,7 +6898,7 @@ class App(QtCore.QObject):
             # thread safe. The new_project()
             # thread safe. The new_project()
             self.open_project(filename)
             self.open_project(filename)
 
 
-    def on_file_openconfig(self):
+    def on_file_openconfig(self, checked=None):
         """
         """
         File menu callback for opening a config file.
         File menu callback for opening a config file.
 
 

+ 6 - 0
README.md

@@ -9,6 +9,12 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+1.09.2019
+
+- fixed open handlers
+- fixed issue in NCC Tool where the tool table context menu could be installed multiple times
+- added new ability to create simple isolation's in the NCC Tool
+
 27.08.2019
 27.08.2019
 
 
 - made FlatCAM so that whenever an associated file is double clicked, if there is an opened instance of FlatCAM, the file will be opened in the first instance without launching a new instance of FlatCAM. If FlatCAM is launched again it will spawn a new process (hopefully it will work when freezed).
 - made FlatCAM so that whenever an associated file is double clicked, if there is an opened instance of FlatCAM, the file will be opened in the first instance without launching a new instance of FlatCAM. If FlatCAM is launched again it will spawn a new process (hopefully it will work when freezed).

+ 1 - 1
flatcamGUI/ObjectUI.py

@@ -912,7 +912,7 @@ class GeometryObjectUI(ObjectUI):
         self.geo_tools_table.horizontalHeaderItem(3).setToolTip(
         self.geo_tools_table.horizontalHeaderItem(3).setToolTip(
             _(
             _(
                 "The (Operation) Type has only informative value. Usually the UI form values \n"
                 "The (Operation) Type has only informative value. Usually the UI form values \n"
-                "are choosed based on the operation type and this will serve as a reminder.\n"
+                "are choose based on the operation type and this will serve as a reminder.\n"
                 "Can be 'Roughing', 'Finishing' or 'Isolation'.\n"
                 "Can be 'Roughing', 'Finishing' or 'Isolation'.\n"
                 "For Roughing we may choose a lower Feedrate and multiDepth cut.\n"
                 "For Roughing we may choose a lower Feedrate and multiDepth cut.\n"
                 "For Finishing we may choose a higher Feedrate, without multiDepth.\n"
                 "For Finishing we may choose a higher Feedrate, without multiDepth.\n"

+ 277 - 71
flatcamTools/ToolNonCopperClear.py

@@ -103,8 +103,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.tools_table = FCTable()
         self.tools_table = FCTable()
         self.tools_box.addWidget(self.tools_table)
         self.tools_box.addWidget(self.tools_table)
 
 
-        self.tools_table.setColumnCount(4)
-        self.tools_table.setHorizontalHeaderLabels(['#', _('Diameter'), _('TT'), ''])
+        self.tools_table.setColumnCount(5)
+        self.tools_table.setHorizontalHeaderLabels(['#', _('Diameter'), _('TT'), '', _("Operation")])
         self.tools_table.setColumnHidden(3, True)
         self.tools_table.setColumnHidden(3, True)
         self.tools_table.setSortingEnabled(False)
         self.tools_table.setSortingEnabled(False)
         # self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
         # self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
@@ -118,22 +118,28 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "this function will not be able to create painting geometry.")
               "this function will not be able to create painting geometry.")
             )
             )
         self.tools_table.horizontalHeaderItem(1).setToolTip(
         self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool Diameter. It's value (in current FlatCAM units) \n"
+            _("Tool Diameter. It's value (in current FlatCAM units)\n"
               "is the cut width into the material."))
               "is the cut width into the material."))
 
 
         self.tools_table.horizontalHeaderItem(2).setToolTip(
         self.tools_table.horizontalHeaderItem(2).setToolTip(
-            _("The Tool Type (TT) can be:<BR>"
-              "- <B>Circular</B> with 1 ... 4 teeth -> it is informative only. Being circular, <BR>"
-              "the cut width in material is exactly the tool diameter.<BR>"
-              "- <B>Ball</B> -> informative only and make reference to the Ball type endmill.<BR>"
-              "- <B>V-Shape</B> -> it will disable de Z-Cut parameter in the resulting geometry UI form "
-              "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and "
-              "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such "
-              "as the cut width into material will be equal with the value in the Tool Diameter "
-              "column of this table.<BR>"
-              "Choosing the <B>V-Shape</B> Tool Type automatically will select the Operation Type "
+            _("The Tool Type (TT) can be:\n"
+              "- Circular with 1 ... 4 teeth -> it is informative only. Being circular,\n"
+              "the cut width in material is exactly the tool diameter.\n"
+              "- Ball -> informative only and make reference to the Ball type endmill.\n"
+              "- V-Shape -> it will disable de Z-Cut parameter in the resulting geometry UI form\n"
+              "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and\n"
+              "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such\n"
+              "as the cut width into material will be equal with the value in the Tool Diameter\n"
+              "column of this table.\n"
+              "Choosing the 'V-Shape' Tool Type automatically will select the Operation Type\n"
               "in the resulting geometry as Isolation."))
               "in the resulting geometry as Isolation."))
 
 
+        self.tools_table.horizontalHeaderItem(4).setToolTip(
+            _("The 'Operation' can be:\n"
+              "- Isolation -> will ensure that the non-copper clearing is always complete.\n"
+              "If it's not successful then the non-copper clearing will fail, too.\n"
+              "- Clear -> the regular non-copper clearing."))
+
         self.ncc_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
         self.ncc_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
         self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
         self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
                                           "'No' --> means that the used order is the one in the tool table\n"
                                           "'No' --> means that the used order is the one in the tool table\n"
@@ -363,6 +369,13 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.tools_box.addWidget(self.generate_ncc_button)
         self.tools_box.addWidget(self.generate_ncc_button)
         self.tools_box.addStretch()
         self.tools_box.addStretch()
 
 
+        self.tools_table.setupContextMenu()
+        self.tools_table.addContextMenu(
+            "Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
+        self.tools_table.addContextMenu(
+            "Delete", lambda:
+            self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
+
         self.units = ''
         self.units = ''
         self.ncc_tools = {}
         self.ncc_tools = {}
         self.tooluid = 0
         self.tooluid = 0
@@ -381,6 +394,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.cursor_pos = None
         self.cursor_pos = None
         self.mouse_is_dragging = False
         self.mouse_is_dragging = False
 
 
+        # store here solid_geometry when there are tool with isolation job
+        self.solid_geometry = []
+
         self.addtool_btn.clicked.connect(self.on_tool_add)
         self.addtool_btn.clicked.connect(self.on_tool_add)
         self.addtool_entry.returnPressed.connect(self.on_tool_add)
         self.addtool_entry.returnPressed.connect(self.on_tool_add)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
@@ -448,13 +464,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.ncc_rest_cb.set_value(self.app.defaults["tools_nccrest"])
         self.ncc_rest_cb.set_value(self.app.defaults["tools_nccrest"])
         self.reference_radio.set_value(self.app.defaults["tools_nccref"])
         self.reference_radio.set_value(self.app.defaults["tools_nccref"])
 
 
-        self.tools_table.setupContextMenu()
-        self.tools_table.addContextMenu(
-            "Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
-        self.tools_table.addContextMenu(
-            "Delete", lambda:
-            self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
-
         # init the working variables
         # init the working variables
         self.default_data.clear()
         self.default_data.clear()
         self.default_data.update({
         self.default_data.update({
@@ -515,6 +524,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     'offset_value': 0.0,
                     'offset_value': 0.0,
                     'type': 'Iso',
                     'type': 'Iso',
                     'tool_type': 'V',
                     'tool_type': 'V',
+                    'operation': 'clear',
                     'data': dict(self.default_data),
                     'data': dict(self.default_data),
                     'solid_geometry': []
                     'solid_geometry': []
                 }
                 }
@@ -583,12 +593,22 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                     tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
                     tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
 
 
+                    operation_type = QtWidgets.QComboBox()
+                    operation_type.addItem('iso')
+                    operation_type.setStyleSheet('background-color: rgb(255,255,255)')
+                    operation_type.addItem('clear')
+                    operation_type.setStyleSheet('background-color: rgb(255,255,255)')
+                    op_idx = operation_type.findText(tooluid_value['operation'])
+                    operation_type.setCurrentIndex(op_idx)
+
                     self.tools_table.setItem(row_no, 1, dia)  # Diameter
                     self.tools_table.setItem(row_no, 1, dia)  # Diameter
                     self.tools_table.setCellWidget(row_no, 2, tool_type_item)
                     self.tools_table.setCellWidget(row_no, 2, tool_type_item)
 
 
                     # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
                     # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
                     self.tools_table.setItem(row_no, 3, tool_uid_item)  # Tool unique ID
                     self.tools_table.setItem(row_no, 3, tool_uid_item)  # Tool unique ID
 
 
+                    self.tools_table.setCellWidget(row_no, 4, operation_type)
+
         # make the diameter column editable
         # make the diameter column editable
         for row in range(tool_id):
         for row in range(tool_id):
             self.tools_table.item(row, 1).setFlags(
             self.tools_table.item(row, 1).setFlags(
@@ -728,6 +748,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     'offset_value': 0.0,
                     'offset_value': 0.0,
                     'type': 'Iso',
                     'type': 'Iso',
                     'tool_type': 'V',
                     'tool_type': 'V',
+                    'operation': 'clear',
                     'data': dict(self.default_data),
                     'data': dict(self.default_data),
                     'solid_geometry': []
                     'solid_geometry': []
                 }
                 }
@@ -863,8 +884,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
             self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % self.ncc_obj)
             self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % self.ncc_obj)
             return
             return
 
 
-        # use the selected tools in the tool table; get diameters
-        tooldia_list = list()
+        # use the selected tools in the tool table; get diameters for non-copper clear
+        iso_dia_list = list()
+        # use the selected tools in the tool table; get diameters for non-copper clear
+        ncc_dia_list = list()
         if self.tools_table.selectedItems():
         if self.tools_table.selectedItems():
             for x in self.tools_table.selectedItems():
             for x in self.tools_table.selectedItems():
                 try:
                 try:
@@ -877,7 +900,11 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         self.app.inform.emit(_("[ERROR_NOTCL] Wrong Tool Dia value format entered, "
                         self.app.inform.emit(_("[ERROR_NOTCL] Wrong Tool Dia value format entered, "
                                                "use a number."))
                                                "use a number."))
                         continue
                         continue
-                tooldia_list.append(tooldia)
+
+                if self.tools_table.cellWidget(x.row(), 4).currentText() == 'iso':
+                    iso_dia_list.append(tooldia)
+                else:
+                    ncc_dia_list.append(tooldia)
         else:
         else:
             self.app.inform.emit(_("[ERROR_NOTCL] No selected tools in Tool Table."))
             self.app.inform.emit(_("[ERROR_NOTCL] No selected tools in Tool Table."))
             return
             return
@@ -895,7 +922,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 return "Could not retrieve object: %s" % self.obj_name
                 return "Could not retrieve object: %s" % self.obj_name
 
 
             self.clear_copper(ncc_obj=self.ncc_obj,
             self.clear_copper(ncc_obj=self.ncc_obj,
-                              tooldia=tooldia_list,
+                              ncctooldia=ncc_dia_list,
+                              isotooldia=iso_dia_list,
                               has_offset=has_offset,
                               has_offset=has_offset,
                               outname=o_name,
                               outname=o_name,
                               overlap=overlap,
                               overlap=overlap,
@@ -951,7 +979,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         self.sel_rect = cascaded_union(self.sel_rect)
                         self.sel_rect = cascaded_union(self.sel_rect)
                         self.clear_copper(ncc_obj=self.ncc_obj,
                         self.clear_copper(ncc_obj=self.ncc_obj,
                                           sel_obj=self.bound_obj,
                                           sel_obj=self.bound_obj,
-                                          tooldia=tooldia_list,
+                                          ncctooldia=ncc_dia_list,
+                                          isotooldia=iso_dia_list,
                                           has_offset=has_offset,
                                           has_offset=has_offset,
                                           outname=o_name,
                                           outname=o_name,
                                           overlap=overlap,
                                           overlap=overlap,
@@ -977,7 +1006,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     self.sel_rect = cascaded_union(self.sel_rect)
                     self.sel_rect = cascaded_union(self.sel_rect)
                     self.clear_copper(ncc_obj=self.ncc_obj,
                     self.clear_copper(ncc_obj=self.ncc_obj,
                                       sel_obj=self.bound_obj,
                                       sel_obj=self.bound_obj,
-                                      tooldia=tooldia_list,
+                                      ncctooldia=ncc_dia_list,
+                                      isotooldia=iso_dia_list,
                                       has_offset=has_offset,
                                       has_offset=has_offset,
                                       outname=o_name,
                                       outname=o_name,
                                       overlap=overlap,
                                       overlap=overlap,
@@ -1026,7 +1056,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
             self.clear_copper(ncc_obj=self.ncc_obj,
             self.clear_copper(ncc_obj=self.ncc_obj,
                               sel_obj=self.bound_obj,
                               sel_obj=self.bound_obj,
-                              tooldia=tooldia_list,
+                              ncctooldia=ncc_dia_list,
+                              isotooldia=iso_dia_list,
                               has_offset=has_offset,
                               has_offset=has_offset,
                               outname=o_name,
                               outname=o_name,
                               overlap=overlap,
                               overlap=overlap,
@@ -1036,7 +1067,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
     def clear_copper(self, ncc_obj,
     def clear_copper(self, ncc_obj,
                      sel_obj=None,
                      sel_obj=None,
-                     tooldia=None,
+                     ncctooldia=None,
+                     isotooldia=None,
                      margin=None,
                      margin=None,
                      has_offset=None,
                      has_offset=None,
                      offset=None,
                      offset=None,
@@ -1053,7 +1085,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
         Clear the excess copper from the entire object.
         Clear the excess copper from the entire object.
 
 
         :param ncc_obj: ncc cleared object
         :param ncc_obj: ncc cleared object
-        :param tooldia: a tuple or single element made out of diameters of the tools to be used
+        :param ncctooldia: a tuple or single element made out of diameters of the tools to be used to ncc clear
+        :param isotooldia: a tuple or single element made out of diameters of the tools to be used for isolation
         :param overlap: value by which the paths will overlap
         :param overlap: value by which the paths will overlap
         :param order: if the tools are ordered and how
         :param order: if the tools are ordered and how
         :param select_method: if to do ncc on the whole object, on an defined area or on an area defined by
         :param select_method: if to do ncc on the whole object, on an defined area or on an area defined by
@@ -1124,17 +1157,18 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ##
         # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ##
         # ######################################################################################################
         # ######################################################################################################
         sorted_tools = []
         sorted_tools = []
-        if tooldia is not None:
+        if ncctooldia is not None:
             try:
             try:
-                sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
+                sorted_tools = [float(eval(dia)) for dia in ncctooldia.split(",") if dia != '']
             except AttributeError:
             except AttributeError:
-                if not isinstance(tooldia, list):
-                    sorted_tools = [float(tooldia)]
+                if not isinstance(ncctooldia, list):
+                    sorted_tools = [float(ncctooldia)]
                 else:
                 else:
-                    sorted_tools = tooldia
+                    sorted_tools = ncctooldia
         else:
         else:
             for row in range(self.tools_table.rowCount()):
             for row in range(self.tools_table.rowCount()):
-                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
+                if self.tools_table.cellWidget(row, 1).currentText() == 'clear':
+                    sorted_tools.append(float(self.tools_table.item(row, 1).text()))
 
 
         # ##############################################################################################################
         # ##############################################################################################################
         # Prepare non-copper polygons. Create the bounding box area from which the copper features will be subtracted ##
         # Prepare non-copper polygons. Create the bounding box area from which the copper features will be subtracted ##
@@ -1200,37 +1234,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
             log.debug("NonCopperClear.clear_copper() --> %s" % str(e))
             log.debug("NonCopperClear.clear_copper() --> %s" % str(e))
             return 'fail'
             return 'fail'
 
 
-        # ###################################################################################################
-        # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
-        # ###################################################################################################
-        if isinstance(ncc_obj, FlatCAMGerber):
-            if has_offset is True:
-                self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
-                offseted_geo = ncc_obj.solid_geometry.buffer(distance=ncc_offset)
-                self.app.inform.emit(_("[success] Buffering finished ..."))
-                empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
-            else:
-                empty = self.get_ncc_empty_area(target=ncc_obj.solid_geometry, boundary=bounding_box)
-        elif isinstance(ncc_obj, FlatCAMGeometry):
-            sol_geo = cascaded_union(ncc_obj.solid_geometry)
-            if has_offset is True:
-                self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
-                offseted_geo = sol_geo.buffer(distance=ncc_offset)
-                self.app.inform.emit(_("[success] Buffering finished ..."))
-                empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
-            else:
-                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
-        else:
-            self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
-            return
-
-        if empty.is_empty:
-            self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
-            return
-
-        if type(empty) is Polygon:
-            empty = MultiPolygon([empty])
-
         # ########################################################################################################
         # ########################################################################################################
         # set the name for the future Geometry object
         # set the name for the future Geometry object
         # I do it here because it is also stored inside the gen_clear_area() and gen_clear_area_rest() methods
         # I do it here because it is also stored inside the gen_clear_area() and gen_clear_area_rest() methods
@@ -1267,8 +1270,110 @@ class NonCopperClear(FlatCAMTool, Gerber):
             current_uid = int(1)
             current_uid = int(1)
             tool = eval(self.app.defaults["tools_ncctools"])[0]
             tool = eval(self.app.defaults["tools_ncctools"])[0]
 
 
+            # ###################################################################################################
+            # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
+            # ###################################################################################################
+            if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia:
+                sol_geo = ncc_obj.solid_geometry
+                if has_offset is True:
+                    app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                    sol_geo = sol_geo.buffer(distance=ncc_offset)
+                    app_obj.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
+            elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia:
+                isolated_geo = []
+                self.solid_geometry = ncc_obj.solid_geometry
+
+                # if milling type is climb then the move is counter-clockwise around features
+                milling_type = 'cl'
+
+                for tool_iso in isotooldia:
+                    new_geometry = []
+
+                    if milling_type == 'cl':
+                        isolated_geo = self.generate_envelope(tool_iso, 1)
+                    else:
+                        isolated_geo = self.generate_envelope(tool_iso, 0)
+
+                    if isolated_geo == 'fail':
+                        app_obj.inform.emit(_("[ERROR_NOTCL] Isolation geometry could not be generated."))
+                    else:
+                        try:
+                            for geo_elem in isolated_geo:
+                                if isinstance(geo_elem, Polygon):
+                                    for ring in self.poly2rings(geo_elem):
+                                        new_geo = ring.intersection(bounding_box)
+                                        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.intersection(bounding_box)
+                                            if new_geo and not new_geo.is_empty:
+                                                new_geometry.append(new_geo)
+                                elif isinstance(geo_elem, LineString):
+                                    new_geo = geo_elem.intersection(bounding_box)
+                                    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.intersection(bounding_box)
+                                        if new_geo and not new_geo.is_empty:
+                                            new_geometry.append(new_geo)
+                        except TypeError:
+                            if isinstance(isolated_geo, Polygon):
+                                for ring in self.poly2rings(isolated_geo):
+                                    new_geo = ring.intersection(bounding_box)
+                                    if new_geo:
+                                        if not new_geo.is_empty:
+                                            new_geometry.append(new_geo)
+                            elif isinstance(isolated_geo, LineString):
+                                new_geo = isolated_geo.intersection(bounding_box)
+                                if new_geo and not new_geo.is_empty:
+                                    new_geometry.append(new_geo)
+                            elif isinstance(isolated_geo, MultiLineString):
+                                for line_elem in isolated_geo:
+                                    new_geo = line_elem.intersection(bounding_box)
+                                    if new_geo and not new_geo.is_empty:
+                                        new_geometry.append(new_geo)
+
+                        for k, v in tools_storage.items():
+                            if float('%.4f' % v['tooldia']) == float('%.4f' % tool_iso):
+                                current_uid = int(k)
+                                # add the solid_geometry to the current too in self.paint_tools dictionary
+                                # and then reset the temporary list that stored that solid_geometry
+                                v['solid_geometry'] = deepcopy(new_geometry)
+                                v['data']['name'] = name
+                                break
+                        geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
+
+                sol_geo = cascaded_union(isolated_geo)
+                if has_offset is True:
+                    app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                    sol_geo = sol_geo.buffer(distance=ncc_offset)
+                    app_obj.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
+            elif isinstance(ncc_obj, FlatCAMGeometry):
+                sol_geo = cascaded_union(ncc_obj.solid_geometry)
+                if has_offset is True:
+                    app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                    sol_geo = sol_geo.buffer(distance=ncc_offset)
+                    app_obj.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
+            else:
+                app_obj.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
+                return
+
+            if empty.is_empty:
+                app_obj.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
+                return 'fail'
+
+            if type(empty) is Polygon:
+                empty = MultiPolygon([empty])
+
             for tool in sorted_tools:
             for tool in sorted_tools:
-                self.app.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool))
+                app_obj.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool))
                 cleared_geo[:] = []
                 cleared_geo[:] = []
 
 
                 # Get remaining tools offset
                 # Get remaining tools offset
@@ -1352,9 +1457,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 if geo_obj.tools[tooluid]['solid_geometry']:
                 if geo_obj.tools[tooluid]['solid_geometry']:
                     has_solid_geo += 1
                     has_solid_geo += 1
             if has_solid_geo == 0:
             if has_solid_geo == 0:
-                self.app.inform.emit(_("[ERROR] There is no Painting Geometry in the file.\n"
-                                       "Usually it means that the tool diameter is too big for the painted geometry.\n"
-                                       "Change the painting parameters and try again."))
+                app_obj.inform.emit(_("[ERROR] There is no Painting Geometry in the file.\n"
+                                      "Usually it means that the tool diameter is too big for the painted geometry.\n"
+                                      "Change the painting parameters and try again."))
                 return
                 return
 
 
             # Experimental...
             # Experimental...
@@ -1381,11 +1486,71 @@ class NonCopperClear(FlatCAMTool, Gerber):
             # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
             # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
             app_obj.poly_not_cleared = True
             app_obj.poly_not_cleared = True
 
 
+            # ###################################################################################################
+            # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
+            # ###################################################################################################
+            if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia:
+                sol_geo = ncc_obj.solid_geometry
+                if has_offset is True:
+                    app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                    sol_geo = sol_geo.buffer(distance=ncc_offset)
+                    app_obj.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
+            elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia:
+                isolated_geo = []
+                self.solid_geometry = ncc_obj.solid_geometry
+
+                # if milling type is climb then the move is counter-clockwise around features
+                milling_type = 'cl'
+
+                for tool_iso in isotooldia:
+                    if milling_type == 'cl':
+                        isolated_geo = self.generate_envelope(tool_iso, 1)
+                    else:
+                        isolated_geo = self.generate_envelope(tool_iso, 0)
+
+                    if isolated_geo == 'fail':
+                        app_obj.inform.emit(_("[ERROR_NOTCL] Isolation geometry could not be generated."))
+                    else:
+                        for k, v in tools_storage.items():
+                            if float('%.4f' % v['tooldia']) == float('%.4f' % tool_iso):
+                                current_uid = int(k)
+                                # add the solid_geometry to the current too in self.paint_tools dictionary
+                                # and then reset the temporary list that stored that solid_geometry
+                                v['solid_geometry'] = deepcopy(isolated_geo)
+                                v['data']['name'] = name
+                                break
+                        geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
+
+                sol_geo = cascaded_union(isolated_geo)
+                if has_offset is True:
+                    app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                    sol_geo = sol_geo.buffer(distance=ncc_offset)
+                    app_obj.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
+            elif isinstance(ncc_obj, FlatCAMGeometry):
+                sol_geo = cascaded_union(ncc_obj.solid_geometry)
+                if has_offset is True:
+                    app_obj.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                    sol_geo = sol_geo.buffer(distance=ncc_offset)
+                    app_obj.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
+            else:
+                app_obj.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
+                return
+
+            if empty.is_empty:
+                app_obj.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
+                return 'fail'
+
+            if type(empty) is Polygon:
+                empty = MultiPolygon([empty])
+
             area = empty.buffer(0)
             area = empty.buffer(0)
             # Generate area for each tool
             # Generate area for each tool
             while sorted_tools:
             while sorted_tools:
                 tool = sorted_tools.pop(0)
                 tool = sorted_tools.pop(0)
-                self.app.inform.emit(_('[success] Non-Copper Rest Clearing with ToolDia = %s started.') % str(tool))
+                app_obj.inform.emit(_('[success] Non-Copper Rest Clearing with ToolDia = %s started.') % str(tool))
 
 
                 tool_used = tool - 1e-12
                 tool_used = tool - 1e-12
                 cleared_geo[:] = []
                 cleared_geo[:] = []
@@ -1879,3 +2044,44 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.mouse_is_dragging = False
         self.mouse_is_dragging = False
 
 
         self.sel_rect = []
         self.sel_rect = []
+
+    @staticmethod
+    def poly2rings(poly):
+        return [poly.exterior] + [interior for interior in poly.interiors]
+
+    def generate_envelope(self, offset, invert, envelope_iso_type=2, follow=None):
+        # isolation_geometry produces an envelope that is going on the left of the geometry
+        # (the copper features). To leave the least amount of burrs on the features
+        # the tool needs to travel on the right side of the features (this is called conventional milling)
+        # the first pass is the one cutting all of the features, so it needs to be reversed
+        # the other passes overlap preceding ones and cut the left over copper. It is better for them
+        # to cut on the right side of the left over copper i.e on the left side of the features.
+        try:
+            geom = self.isolation_geometry(offset, iso_type=envelope_iso_type, follow=follow)
+        except Exception as e:
+            log.debug('NonCopperClear.generate_envelope() --> %s' % str(e))
+            return 'fail'
+
+        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("NonCopperClear.generate_envelope() Error --> Unexpected Geometry %s" %
+                                  type(geom))
+            except Exception as e:
+                log.debug("NonCopperClear.generate_envelope() Error --> %s" % str(e))
+                return 'fail'
+        return geom