summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/elements/fill_stitch.py3
-rw-r--r--lib/extensions/params.py5
-rw-r--r--lib/gui/simulator.py18
-rw-r--r--lib/stitches/auto_fill.py19
-rw-r--r--lib/stitches/fill.py3
-rw-r--r--lib/utils/threading.py21
6 files changed, 68 insertions, 1 deletions
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py
index 7de293cb..eef8341c 100644
--- a/lib/elements/fill_stitch.py
+++ b/lib/elements/fill_stitch.py
@@ -22,6 +22,7 @@ from ..svg.tags import INKSCAPE_LABEL
from ..utils import cache, version
from .element import EmbroideryElement, param
from .validation import ValidationError, ValidationWarning
+from ..utils.threading import ExitThread
class SmallShapeWarning(ValidationWarning):
@@ -571,6 +572,8 @@ class FillStitch(EmbroideryElement):
stitch_groups.extend(self.do_contour_fill(fill_shape, last_patch, start))
elif self.fill_method == 2:
stitch_groups.extend(self.do_guided_fill(fill_shape, last_patch, start, end))
+ except ExitThread:
+ raise
except Exception:
self.fatal_fill_error()
last_patch = stitch_groups[-1]
diff --git a/lib/extensions/params.py b/lib/extensions/params.py
index 306e5e56..1262ceb6 100644
--- a/lib/extensions/params.py
+++ b/lib/extensions/params.py
@@ -25,6 +25,7 @@ from ..i18n import _
from ..svg.tags import SVG_POLYLINE_TAG
from ..utils import get_resource_dir
from .base import InkstitchExtension
+from ..utils.threading import ExitThread, check_stop_flag
def grouper(iterable_obj, count, fillvalue=None):
@@ -509,9 +510,13 @@ class SettingsFrame(wx.Frame):
# for many params in embroider.py.
patches.extend(copy(node).embroider(None))
+
+ check_stop_flag()
except SystemExit:
wx.CallAfter(self._show_warning)
raise
+ except ExitThread:
+ raise
except Exception as e:
# Ignore errors. This can be things like incorrect paths for
# satins or division by zero caused by incorrect param values.
diff --git a/lib/gui/simulator.py b/lib/gui/simulator.py
index 1cc7066e..d9f51b48 100644
--- a/lib/gui/simulator.py
+++ b/lib/gui/simulator.py
@@ -10,6 +10,8 @@ from threading import Event, Thread
import wx
from wx.lib.intctrl import IntCtrl
+from lib.debug import debug
+from lib.utils.threading import ExitThread
from ..i18n import _
from ..stitch_plan import stitch_groups_to_stitch_plan, stitch_plan_from_file
from ..svg import PIXELS_PER_MM
@@ -749,6 +751,10 @@ class SimulatorPreview(Thread):
self.simulate_window = None
self.refresh_needed = Event()
+ # This is read by utils.threading.check_stop_flag() to abort stitch plan
+ # generation.
+ self.stop = Event()
+
# used when closing to avoid having the window reopen at the last second
self._disabled = False
@@ -770,17 +776,27 @@ class SimulatorPreview(Thread):
if not self.is_alive():
self.start()
+ self.stop.set()
self.refresh_needed.set()
def run(self):
while True:
self.refresh_needed.wait()
self.refresh_needed.clear()
- self.update_patches()
+ self.stop.clear()
+
+ try:
+ debug.log("update_patches")
+ self.update_patches()
+ except ExitThread:
+ debug.log("ExitThread caught")
+ self.stop.clear()
def update_patches(self):
try:
patches = self.parent.generate_patches(self.refresh_needed)
+ except ExitThread:
+ raise
except: # noqa: E722
# If something goes wrong when rendering patches, it's not great,
# but we don't really want the simulator thread to crash. Instead,
diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py
index 3ff5a24f..fde3433a 100644
--- a/lib/stitches/auto_fill.py
+++ b/lib/stitches/auto_fill.py
@@ -19,6 +19,7 @@ from ..svg import PIXELS_PER_MM
from ..utils.geometry import Point as InkstitchPoint, line_string_to_point_list, ensure_multi_line_string
from .fill import intersect_region_with_grating, stitch_row
from .running_stitch import running_stitch
+from ..utils.threading import check_stop_flag
class PathEdge(object):
@@ -153,6 +154,8 @@ def build_fill_stitch_graph(shape, segments, starting_point=None, ending_point=N
# mark this one as a grating segment.
graph.add_edge(segment[0], segment[-1], key="segment", underpath_edges=[], geometry=shgeo.LineString(segment))
+ check_stop_flag()
+
tag_nodes_with_outline_and_projection(graph, shape, graph.nodes())
add_edges_between_outline_nodes(graph, duplicate_every_other=True)
@@ -196,6 +199,8 @@ def tag_nodes_with_outline_and_projection(graph, shape, nodes):
graph.add_node(node, outline=outline_index, projection=outline_projection)
+ check_stop_flag()
+
def add_boundary_travel_nodes(graph, shape):
outlines = ensure_multi_line_string(shape.boundary).geoms
@@ -215,6 +220,8 @@ def add_boundary_travel_nodes(graph, shape):
subpoint = segment.interpolate(i)
graph.add_node((subpoint.x, subpoint.y), projection=outline.project(subpoint), outline=outline_index)
+ check_stop_flag()
+
graph.add_node((point.x, point.y), projection=outline.project(point), outline=outline_index)
prev = point
@@ -245,6 +252,8 @@ def add_edges_between_outline_nodes(graph, duplicate_every_other=False):
if i % 2 == 0:
graph.add_edge(node1, node2, key="extra", **data)
+ check_stop_flag()
+
def graph_is_valid(graph, shape, max_stitch_length):
# The graph may be empty if the shape is so small that it fits between the
@@ -382,6 +391,8 @@ def process_travel_edges(graph, fill_stitch_graph, shape, travel_edges):
graph.add_edge(*edge, weight=weight)
+ check_stop_flag()
+
# without this, we sometimes get exceptions like this:
# Exception AttributeError: "'NoneType' object has no attribute 'GEOSSTRtree_destroy'" in
# <bound method STRtree.__del__ of <shapely.strtree.STRtree instance at 0x0D2BFD50>> ignored
@@ -444,9 +455,13 @@ def build_travel_edges(shape, fill_angle):
diagonal_edges = ensure_multi_line_string(grating1.symmetric_difference(grating2))
+ check_stop_flag()
+
# without this, floating point inaccuracies prevent the intersection points from lining up perfectly.
vertical_edges = ensure_multi_line_string(snap(grating3.difference(grating1), diagonal_edges, 0.005))
+ check_stop_flag()
+
return endpoints, chain(diagonal_edges.geoms, vertical_edges.geoms)
@@ -540,6 +555,8 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None
real_end = nearest_node(outline_nodes, ending_point)
path.append(PathEdge((ending_node, real_end), key="outline"))
+ check_stop_flag()
+
return path
@@ -629,4 +646,6 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing,
else:
stitches.extend(travel(travel_graph, edge[0], edge[1], running_stitch_length, running_stitch_tolerance, skip_last))
+ check_stop_flag()
+
return stitches
diff --git a/lib/stitches/fill.py b/lib/stitches/fill.py
index 11c9259b..b3bb0bb7 100644
--- a/lib/stitches/fill.py
+++ b/lib/stitches/fill.py
@@ -11,6 +11,7 @@ from ..stitch_plan import Stitch
from ..svg import PIXELS_PER_MM
from ..utils import Point as InkstitchPoint
from ..utils import cache
+from ..utils.threading import check_stop_flag
def legacy_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, flip, staggers, skip_last):
@@ -169,6 +170,8 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non
else:
current_row_y += row_spacing
+ check_stop_flag()
+
return rows
diff --git a/lib/utils/threading.py b/lib/utils/threading.py
new file mode 100644
index 00000000..7bb90d1b
--- /dev/null
+++ b/lib/utils/threading.py
@@ -0,0 +1,21 @@
+import threading
+
+from ..exceptions import InkstitchException
+from ..debug import debug
+
+
+class ExitThread(InkstitchException):
+ """This exception is thrown in a thread to cause it to terminate.
+
+ Presumably we should only catch this at the thread's top level.
+ """
+ pass
+
+
+_default_stop_flag = threading.Event()
+
+
+def check_stop_flag():
+ if getattr(threading.current_thread(), 'stop', _default_stop_flag).is_set():
+ debug.log("exiting thread")
+ raise ExitThread()