- 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
This commit is contained in:
Marius Stanciu 2019-09-07 09:16:32 +03:00 committed by Marius
parent 9da1defe3e
commit 5d854a6f1b
8 changed files with 195 additions and 56 deletions

View File

@ -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>"
"<B> Main Contributors:</B><BR>"
"<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

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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)
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)
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)
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)
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()