Parcourir la source

- 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

Marius Stanciu il y a 6 ans
Parent
commit
5d854a6f1b
8 fichiers modifiés avec 196 ajouts et 57 suppressions
  1. 33 4
      FlatCAMApp.py
  2. 3 0
      FlatCAMProcess.py
  3. 5 0
      README.md
  4. 16 7
      camlib.py
  5. 14 1
      flatcamGUI/FlatCAMGUI.py
  6. 1 1
      flatcamGUI/GUIElements.py
  7. 64 14
      flatcamTools/ToolNonCopperClear.py
  8. 60 30
      flatcamTools/ToolPaint.py

+ 33 - 4
FlatCAMApp.py

@@ -105,7 +105,7 @@ class App(QtCore.QObject):
     # Version and VERSION DATE ###########
     # ####################################
     version = 8.97
-    version_date = "2019/08/31"
+    version_date = "2019/09/07"
     beta = True
 
     # current date now
@@ -1788,6 +1788,9 @@ class App(QtCore.QObject):
         self.ui.fa_defaults_form.fa_gerber_group.grb_list_btn.clicked.connect(
             lambda: self.on_register_files(obj_type='gerber'))
 
+        # connect the abort_all_tasks related slots to the related signals
+        self.proc_container.idle_flag.connect(self.app_is_idle)
+
         self.log.debug("Finished connecting Signals.")
 
         # this is a flag to signal to other tools that the ui tooltab is locked and not accessible
@@ -2036,7 +2039,7 @@ class App(QtCore.QObject):
         self.shell.setWindowTitle("FlatCAM Shell")
         self.shell.resize(*self.defaults["global_shell_shape"])
         self.shell.append_output("FlatCAM %s - " % self.version)
-        self.shell.append_output(_("Type help to get started\n\n"))
+        self.shell.append_output(_("Open Source Software - Type help to get started\n\n"))
 
         self.init_tcl()
 
@@ -2193,6 +2196,9 @@ class App(QtCore.QObject):
         self.isHovering = False
         self.notHovering = True
 
+        # 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 ###################
@@ -3544,9 +3550,12 @@ class App(QtCore.QObject):
                         "2D Computer-Aided Printed Circuit Board<BR>"
                         "Manufacturing.<BR>"
                         "<BR>"
-                        "(c) 2014-2019 <B>Juan Pablo Caram</B><BR>"
+                        "<B> License: </B><BR>"
+                        "Licensed under MIT license (c)2014 - 2019"
+                        "<BR>"
                         "<BR>"
-                        "<B> Main Contributors:</B><BR>"
+                        "<B> Programmers:</B><BR>"
+                        "<B> Juan Pablo Caram </B><BR>"
                         "Denis Hayrullin<BR>"
                         "Kamil Sopko<BR>"
                         "Marius Stanciu<BR>"
@@ -5693,6 +5702,17 @@ class App(QtCore.QObject):
             except Exception as e:
                 return "Operation failed: %s" % str(e)
 
+    def abort_all_tasks(self):
+        if self.abort_flag is False:
+            self.inform.emit(_("Aborting. The current task will be gracefully closed as soon as possible..."))
+            self.abort_flag = True
+
+    def app_is_idle(self):
+        if self.abort_flag:
+            self.inform.emit('[WARNING_NOTCL] %s' %
+                             _("The current task was gracefully closed on user request..."))
+            self.abort_flag = False
+
     def on_set_zero_click(self, event):
         # this function will be available only for mouse left click
         pos = []
@@ -9686,4 +9706,13 @@ class ArgsThread(QtCore.QObject):
     def run(self):
         self.my_loop(self.address)
 
+
+class GracefulException(Exception):
+    # Graceful Exception raised when the user is requesting to cancel the current threaded task
+    def __init__(self):
+        super().__init__()
+
+    def __str__(self):
+        return ('\n\n%s' % _("The user requested a graceful exit of the current task."))
+
 # end of file

+ 3 - 0
FlatCAMProcess.py

@@ -128,6 +128,8 @@ class FCProcessContainer(object):
 
 class FCVisibleProcessContainer(QtCore.QObject, FCProcessContainer):
     something_changed = QtCore.pyqtSignal()
+    # this will signal that the application is IDLE
+    idle_flag = QtCore.pyqtSignal()
 
     def __init__(self, view):
         assert isinstance(view, FlatCAMActivityView), \
@@ -161,6 +163,7 @@ class FCVisibleProcessContainer(QtCore.QObject, FCProcessContainer):
     def update_view(self):
         if len(self.procs) == 0:
             self.view.set_idle()
+            self.idle_flag.emit()
             self.new_text = ''
 
         elif len(self.procs) == 1:

+ 5 - 0
README.md

@@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+7.09.2019
+
+- 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
+
 6.09.2019
 
 - remade visibility threaded

+ 16 - 7
camlib.py

@@ -797,8 +797,8 @@ class Geometry(object):
             boundary = self.solid_geometry.envelope
         return boundary.difference(self.solid_geometry)
         
-    @staticmethod
-    def clear_polygon(polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True):
+
+    def clear_polygon(self, polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True):
         """
         Creates geometry inside a polygon for a tool to cover
         the whole area.
@@ -852,6 +852,9 @@ class Geometry(object):
                 geoms.insert(i)
 
         while True:
+            if self.app.abort_flag:
+                # graceful abort requested by the user
+                raise FlatCAMApp.GracefulException
 
             # Can only result in a Polygon or MultiPolygon
             current = current.buffer(-tooldia * (1 - overlap), int(int(steps_per_circle) / 4))
@@ -880,8 +883,7 @@ class Geometry(object):
 
         return geoms
 
-    @staticmethod
-    def clear_polygon2(polygon_to_clear, tooldia, steps_per_circle, seedpoint=None, overlap=0.15,
+    def clear_polygon2(self, polygon_to_clear, tooldia, steps_per_circle, seedpoint=None, overlap=0.15,
                        connect=True, contour=True):
         """
         Creates geometry inside a polygon for a tool to cover
@@ -928,7 +930,11 @@ class Geometry(object):
 
         # Grow from seed until outside the box. The polygons will
         # never have an interior, so take the exterior LinearRing.
-        while 1:
+        while True:
+            if self.app.abort_flag:
+                # graceful abort requested by the user
+                raise FlatCAMApp.GracefulException
+
             path = Point(seedpoint).buffer(radius, int(steps_per_circle / 4)).exterior
             path = path.intersection(path_margin)
 
@@ -971,8 +977,7 @@ class Geometry(object):
 
         return geoms
 
-    @staticmethod
-    def clear_polygon3(polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True):
+    def clear_polygon3(self, polygon, tooldia, steps_per_circle, overlap=0.15, connect=True, contour=True):
         """
         Creates geometry inside a polygon for a tool to cover
         the whole area.
@@ -1007,6 +1012,10 @@ class Geometry(object):
         # First line
         y = top - tooldia / 1.99999999
         while y > bot + tooldia / 1.999999999:
+            if self.app.abort_flag:
+                # graceful abort requested by the user
+                raise FlatCAMApp.GracefulException
+
             line = LineString([(left, y), (right, y)])
             lines.append(line)
             y -= tooldia * (1 - overlap)

+ 14 - 1
flatcamGUI/FlatCAMGUI.py

@@ -1253,7 +1253,15 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                     <tr height="20">
                         <td height="20"><strong>ALT+F10</strong></td>
                         <td>&nbsp;Toggle Full Screen</td>
+                    </tr>                 
+                    <tr height="20">
+                        <td height="20">&nbsp;</td>
+                        <td>&nbsp;</td>
                     </tr>
+                    <tr height="20">
+                        <td height="20"><strong>CTRL+ALT+X</strong></td>
+                        <td>&nbsp;Abort current task (gracefully)</td>
+                    </tr>                    
                     <tr height="20">
                         <td height="20">&nbsp;</td>
                         <td>&nbsp;</td>
@@ -2163,7 +2171,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
             key = event.key
 
         if self.app.call_source == 'app':
-            if modifiers == QtCore.Qt.ControlModifier:
+            if modifiers == QtCore.Qt.ControlModifier | QtCore.Qt.AltModifier:
+                if key == QtCore.Qt.Key_X:
+                    self.app.abort_all_tasks()
+                    return
+
+            elif modifiers == QtCore.Qt.ControlModifier:
                 if key == QtCore.Qt.Key_A:
                     self.app.on_selectall()
 

+ 1 - 1
flatcamGUI/GUIElements.py

@@ -1743,7 +1743,7 @@ class _BrowserTextEdit(QTextEdit):
 
     def clear(self):
         QTextEdit.clear(self)
-        text = "FlatCAM %s - Type help to get started\n\n" % self.version
+        text = "FlatCAM %s - Open Source Software - Type help to get started\n\n" % self.version
         text = html.escape(text)
         text = text.replace('\n', '<br/>')
         self.moveCursor(QTextCursor.End)

+ 64 - 14
flatcamTools/ToolNonCopperClear.py

@@ -1429,6 +1429,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
             geo_buff_list = []
             for poly in geo_n:
+                if self.app.abort_flag:
+                    # graceful abort requested by the user
+                    raise FlatCAMApp.GracefulException
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
             bounding_box = cascaded_union(geo_buff_list)
@@ -1444,6 +1447,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                 geo_buff_list = []
                 for poly in geo_n:
+                    if self.app.abort_flag:
+                        # graceful abort requested by the user
+                        raise FlatCAMApp.GracefulException
                     geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
                 bounding_box = cascaded_union(geo_buff_list)
@@ -1537,6 +1543,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     else:
                         try:
                             for geo_elem in isolated_geo:
+                                if self.app.abort_flag:
+                                    # graceful abort requested by the user
+                                    raise FlatCAMApp.GracefulException
+
                                 if isinstance(geo_elem, Polygon):
                                     for ring in self.poly2rings(geo_elem):
                                         new_geo = ring.intersection(bounding_box)
@@ -1627,6 +1637,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
             cp = None
             for tool in sorted_tools:
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
+                if self.app.abort_flag:
+                    # graceful abort requested by the user
+                    raise FlatCAMApp.GracefulException
+
                 app_obj.inform.emit(
                     '[success] %s %s%s %s' % (_('NCC Tool clearing with tool diameter = '),
                                               str(tool),
@@ -1661,6 +1675,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if len(area.geoms) > 0:
                         pol_nr = 0
                         for p in area.geoms:
+                            if self.app.abort_flag:
+                                # graceful abort requested by the user
+                                raise FlatCAMApp.GracefulException
                             if p is not None:
                                 try:
                                     if isinstance(p, Polygon):
@@ -1836,6 +1853,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     else:
                         try:
                             for geo_elem in isolated_geo:
+                                if self.app.abort_flag:
+                                    # graceful abort requested by the user
+                                    raise FlatCAMApp.GracefulException
+
                                 if isinstance(geo_elem, Polygon):
                                     for ring in self.poly2rings(geo_elem):
                                         new_geo = ring.intersection(bounding_box)
@@ -1858,21 +1879,24 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                         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)
+                            try:
+                                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)
+                            except Exception as e:
+                                pass
 
                         # a MultiLineString geometry element will show that the isolation is broken for this tool
                         for geo_e in new_geometry:
@@ -1919,6 +1943,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                     _("Could not get the extent of the area to be non copper cleared."))
                 return 'fail'
 
+            if self.app.abort_flag:
+                # graceful abort requested by the user
+                raise FlatCAMApp.GracefulException
+
             if type(empty) is Polygon:
                 empty = MultiPolygon([empty])
 
@@ -1929,6 +1957,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
             # Generate area for each tool
             while sorted_tools:
+                if self.app.abort_flag:
+                    # graceful abort requested by the user
+                    raise FlatCAMApp.GracefulException
+
                 tool = sorted_tools.pop(0)
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
 
@@ -1945,6 +1977,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                 # Area to clear
                 for poly in cleared_by_last_tool:
+                    if self.app.abort_flag:
+                        # graceful abort requested by the user
+                        raise FlatCAMApp.GracefulException
                     try:
                         area = area.difference(poly)
                     except Exception as e:
@@ -1973,6 +2008,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if len(area.geoms) > 0:
                         pol_nr = 0
                         for p in area.geoms:
+                            if self.app.abort_flag:
+                                # graceful abort requested by the user
+                                raise FlatCAMApp.GracefulException
+
                             if p is not None:
                                 if isinstance(p, Polygon):
                                     try:
@@ -2029,6 +2068,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                     old_disp_number = disp_number
                                     # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number))
 
+                        if self.app.abort_flag:
+                            # graceful abort requested by the user
+                            raise FlatCAMApp.GracefulException
+
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
                             # Overall cleared area
@@ -2039,10 +2082,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                             # here we store the poly's already processed in the original geometry by the current tool
                             # into cleared_by_last_tool list
-                            # this will be sustracted from the original geometry_to_be_cleared and make data for
+                            # this will be sutracted from the original geometry_to_be_cleared and make data for
                             # the next tool
                             buffer_value = tool_used / 2
                             for p in cleared_area:
+                                if self.app.abort_flag:
+                                    # graceful abort requested by the user
+                                    raise FlatCAMApp.GracefulException
+
                                 poly = p.buffer(buffer_value)
                                 cleared_by_last_tool.append(poly)
 
@@ -2091,6 +2138,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     app_obj.new_object("geometry", name, gen_clear_area_rest)
                 else:
                     app_obj.new_object("geometry", name, gen_clear_area)
+            except FlatCAMApp.GracefulException:
+                proc.done()
+                return
             except Exception as e:
                 proc.done()
                 traceback.print_stack()

+ 60 - 30
flatcamTools/ToolPaint.py

@@ -1254,32 +1254,38 @@ class ToolPaint(FlatCAMTool, Gerber):
                 pass
 
             def paint_p(polyg, tooldia):
-                if paint_method == "seed":
-                    # Type(cp) == FlatCAMRTreeStorage | None
-                    cpoly = self.clear_polygon2(polyg,
-                                                tooldia=tooldia,
-                                                steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                overlap=over,
-                                                contour=cont,
-                                                connect=conn)
-
-                elif paint_method == "lines":
-                    # Type(cp) == FlatCAMRTreeStorage | None
-                    cpoly = self.clear_polygon3(polyg,
-                                                tooldia=tooldia,
-                                                steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                overlap=over,
-                                                contour=cont,
-                                                connect=conn)
+                cpoly = None
+                try:
+                    if paint_method == "seed":
+                        # Type(cp) == FlatCAMRTreeStorage | None
+                        cpoly = self.clear_polygon2(polyg,
+                                                    tooldia=tooldia,
+                                                    steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                    overlap=over,
+                                                    contour=cont,
+                                                    connect=conn)
 
-                else:
-                    # Type(cp) == FlatCAMRTreeStorage | None
-                    cpoly = self.clear_polygon(polyg,
-                                               tooldia=tooldia,
-                                               steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                               overlap=over,
-                                               contour=cont,
-                                               connect=conn)
+                    elif paint_method == "lines":
+                        # Type(cp) == FlatCAMRTreeStorage | None
+                        cpoly = self.clear_polygon3(polyg,
+                                                    tooldia=tooldia,
+                                                    steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                    overlap=over,
+                                                    contour=cont,
+                                                    connect=conn)
+
+                    else:
+                        # Type(cp) == FlatCAMRTreeStorage | None
+                        cpoly = self.clear_polygon(polyg,
+                                                   tooldia=tooldia,
+                                                   steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                                                   overlap=over,
+                                                   contour=cont,
+                                                   connect=conn)
+                except FlatCAMApp.GracefulException:
+                    return "fail"
+                except Exception as e:
+                    log.debug("ToolPaint.paint_poly().gen_paintarea().paint_p() --> %s" % str(e))
 
                 if cpoly is not None:
                     geo_obj.solid_geometry += list(cpoly.get_objects())
@@ -1326,13 +1332,15 @@ class ToolPaint(FlatCAMTool, Gerber):
                                 total_geometry += list(x.get_objects())
                         else:
                             total_geometry = list(cp.get_objects())
+                except FlatCAMApp.GracefulException:
+                    return "fail"
                 except Exception as e:
                     log.debug("Could not Paint the polygons. %s" % str(e))
                     self.app.inform.emit('[ERROR] %s\n%s' %
                                          (_("Could not do Paint. Try a different combination of parameters. "
                                             "Or a different strategy of paint"),
                                           str(e)))
-                    return
+                    return "fail"
 
                 # add the solid_geometry to the current too in self.paint_tools (tools_storage)
                 # dictionary and then reset the temporary list that stored that solid_geometry
@@ -1391,6 +1399,9 @@ class ToolPaint(FlatCAMTool, Gerber):
         def job_thread(app_obj):
             try:
                 app_obj.new_object("geometry", name, gen_paintarea)
+            except FlatCAMApp.GracefulException:
+                proc.done()
+                return
             except Exception as e:
                 proc.done()
                 self.app.inform.emit('[ERROR_NOTCL] %s --> %s' %
@@ -1498,6 +1509,9 @@ class ToolPaint(FlatCAMTool, Gerber):
             :param geometry: Shapely type or list or list of list of such.
             :param reset: Clears the contents of self.flat_geometry.
             """
+            if self.app.abort_flag:
+                # graceful abort requested by the user
+                raise FlatCAMApp.GracefulException
 
             if geometry is None:
                 return
@@ -1611,13 +1625,15 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                         if cp is not None:
                             total_geometry += list(cp.get_objects())
+                    except FlatCAMApp.GracefulException:
+                        return "fail"
                     except Exception as e:
                         log.debug("Could not Paint the polygons. %s" % str(e))
                         self.app.inform.emit('[ERROR] %s\n%s' %
                                              (_("Could not do Paint All. Try a different combination of parameters. "
                                                 "Or a different Method of paint"),
                                               str(e)))
-                        return
+                        return "fail"
 
                     pol_nr += 1
                     disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 99]))
@@ -1741,14 +1757,15 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                         if cp is not None:
                             cleared_geo += list(cp.get_objects())
-
+                    except FlatCAMApp.GracefulException:
+                        return "fail"
                     except Exception as e:
                         log.debug("Could not Paint the polygons. %s" % str(e))
                         self.app.inform.emit('[ERROR] %s\n%s' %
                                              (_("Could not do Paint All. Try a different combination of parameters. "
                                                 "Or a different Method of paint"),
                                               str(e)))
-                        return
+                        return "fail"
 
                     pol_nr += 1
                     disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 99]))
@@ -1803,6 +1820,9 @@ class ToolPaint(FlatCAMTool, Gerber):
                     app_obj.new_object("geometry", name, gen_paintarea_rest_machining)
                 else:
                     app_obj.new_object("geometry", name, gen_paintarea)
+            except FlatCAMApp.GracefulException:
+                proc.done()
+                return
             except Exception as e:
                 proc.done()
                 traceback.print_stack()
@@ -1896,6 +1916,9 @@ class ToolPaint(FlatCAMTool, Gerber):
             :param geometry: Shapely type or list or list of list of such.
             :param reset: Clears the contents of self.flat_geometry.
             """
+            if self.app.abort_flag:
+                # graceful abort requested by the user
+                raise FlatCAMApp.GracefulException
 
             if geometry is None:
                 return
@@ -1991,6 +2014,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             continue
                         poly_buf = geo.buffer(-paint_margin)
 
+                        cp = None
                         if paint_method == "seed":
                             # Type(cp) == FlatCAMRTreeStorage | None
                             cp = self.clear_polygon2(poly_buf,
@@ -2020,6 +2044,8 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                         if cp is not None:
                             total_geometry += list(cp.get_objects())
+                    except FlatCAMApp.GracefulException:
+                        return "fail"
                     except Exception as e:
                         log.debug("Could not Paint the polygons. %s" % str(e))
                         self.app.inform.emit('[ERROR] %s' %
@@ -2149,7 +2175,8 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                         if cp is not None:
                             cleared_geo += list(cp.get_objects())
-
+                    except FlatCAMApp.GracefulException:
+                        return "fail"
                     except Exception as e:
                         log.debug("Could not Paint the polygons. %s" % str(e))
                         self.app.inform.emit('[ERROR] %s' %
@@ -2210,6 +2237,9 @@ class ToolPaint(FlatCAMTool, Gerber):
                     app_obj.new_object("geometry", name, gen_paintarea_rest_machining)
                 else:
                     app_obj.new_object("geometry", name, gen_paintarea)
+            except FlatCAMApp.GracefulException:
+                proc.done()
+                return
             except Exception as e:
                 proc.done()
                 traceback.print_stack()