summaryrefslogtreecommitdiff
path: root/lib/elements
diff options
context:
space:
mode:
Diffstat (limited to 'lib/elements')
-rw-r--r--lib/elements/element.py55
-rw-r--r--lib/elements/fill_stitch.py70
-rw-r--r--lib/elements/satin_column.py54
3 files changed, 106 insertions, 73 deletions
diff --git a/lib/elements/element.py b/lib/elements/element.py
index 43cbc8a2..963653af 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -3,6 +3,7 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
+from contextlib import contextmanager
from copy import deepcopy
import inkex
@@ -11,6 +12,7 @@ from inkex import bezier
from ..commands import find_commands
from ..debug import debug
+from ..exceptions import InkstitchException, format_uncaught_exception
from ..i18n import _
from ..marker import get_marker_elements_cache_key_data
from ..patterns import apply_patterns, get_patterns_cache_key_data
@@ -546,23 +548,25 @@ class EmbroideryElement(object):
def embroider(self, last_stitch_group):
debug.log(f"starting {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
- if last_stitch_group:
- previous_stitch = last_stitch_group.stitches[-1]
- else:
- previous_stitch = None
- stitch_groups = self._load_cached_stitch_groups(previous_stitch)
- if not stitch_groups:
- self.validate()
+ with self.handle_unexpected_exceptions():
+ if last_stitch_group:
+ previous_stitch = last_stitch_group.stitches[-1]
+ else:
+ previous_stitch = None
+ stitch_groups = self._load_cached_stitch_groups(previous_stitch)
+
+ if not stitch_groups:
+ self.validate()
- stitch_groups = self.to_stitch_groups(last_stitch_group)
- apply_patterns(stitch_groups, self.node)
+ stitch_groups = self.to_stitch_groups(last_stitch_group)
+ apply_patterns(stitch_groups, self.node)
- if stitch_groups:
- stitch_groups[-1].trim_after = self.has_command("trim") or self.trim_after
- stitch_groups[-1].stop_after = self.has_command("stop") or self.stop_after
+ if stitch_groups:
+ stitch_groups[-1].trim_after = self.has_command("trim") or self.trim_after
+ stitch_groups[-1].stop_after = self.has_command("stop") or self.stop_after
- self._save_cached_stitch_groups(stitch_groups, previous_stitch)
+ self._save_cached_stitch_groups(stitch_groups, previous_stitch)
debug.log(f"ending {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
return stitch_groups
@@ -575,14 +579,27 @@ class EmbroideryElement(object):
else:
name = id
- # L10N used when showing an error message to the user such as
- # "Failed on PathLabel (path1234): Satin column: One or more of the rungs doesn't intersect both rails."
- error_msg = "%s %s: %s" % (_("Failed on "), name, message)
+ error_msg = f"{name}: {message}"
if point_to_troubleshoot:
error_msg += "\n\n%s" % _("Please run Extensions > Ink/Stitch > Troubleshoot > Troubleshoot objects. "
- "This will indicate the errorneus position.")
- inkex.errormsg(error_msg)
- sys.exit(1)
+ "This will show you the exact location of the problem.")
+
+ raise InkstitchException(error_msg)
+
+ @contextmanager
+ def handle_unexpected_exceptions(self):
+ try:
+ # This runs the code in the `with` body so that we can catch
+ # exceptions.
+ yield
+ except (InkstitchException, SystemExit, KeyboardInterrupt):
+ raise
+ except Exception:
+ if hasattr(sys, 'gettrace') and sys.gettrace():
+ # if we're debugging, let the exception bubble up
+ raise
+
+ raise InkstitchException(format_uncaught_exception())
def validation_errors(self):
"""Return a list of errors with this Element.
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py
index b93d7ff5..c7c3c640 100644
--- a/lib/elements/fill_stitch.py
+++ b/lib/elements/fill_stitch.py
@@ -6,8 +6,6 @@
import logging
import math
import re
-import sys
-import traceback
import numpy as np
from inkex import Transform
@@ -25,9 +23,8 @@ from ..stitches.meander_fill import meander_fill
from ..svg import PIXELS_PER_MM, get_node_transform
from ..svg.clip import get_clip_path
from ..svg.tags import INKSCAPE_LABEL
-from ..utils import cache, version
+from ..utils import cache
from ..utils.param import ParamOption
-from ..utils.threading import ExitThread
from .element import EmbroideryElement, param
from .validation import ValidationError, ValidationWarning
@@ -706,30 +703,25 @@ class FillStitch(EmbroideryElement):
for shape in self.shape.geoms:
start = self.get_starting_point(previous_stitch_group)
- try:
- if self.fill_underlay:
- underlay_shapes = self.underlay_shape(shape)
- for underlay_shape in underlay_shapes.geoms:
- underlay_stitch_groups, start = self.do_underlay(underlay_shape, start)
- stitch_groups.extend(underlay_stitch_groups)
-
- fill_shapes = self.fill_shape(shape)
- for i, fill_shape in enumerate(fill_shapes.geoms):
- if self.fill_method == 'contour_fill':
- stitch_groups.extend(self.do_contour_fill(fill_shape, previous_stitch_group, start))
- elif self.fill_method == 'guided_fill':
- stitch_groups.extend(self.do_guided_fill(fill_shape, previous_stitch_group, start, end))
- elif self.fill_method == 'meander_fill':
- stitch_groups.extend(self.do_meander_fill(fill_shape, shape, i, start, end))
- elif self.fill_method == 'circular_fill':
- stitch_groups.extend(self.do_circular_fill(fill_shape, previous_stitch_group, start, end))
- else:
- # auto_fill
- stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
- except ExitThread:
- raise
- except Exception:
- self.fatal_fill_error()
+ if self.fill_underlay:
+ underlay_shapes = self.underlay_shape(shape)
+ for underlay_shape in underlay_shapes.geoms:
+ underlay_stitch_groups, start = self.do_underlay(underlay_shape, start)
+ stitch_groups.extend(underlay_stitch_groups)
+
+ fill_shapes = self.fill_shape(shape)
+ for i, fill_shape in enumerate(fill_shapes.geoms):
+ if self.fill_method == 'contour_fill':
+ stitch_groups.extend(self.do_contour_fill(fill_shape, previous_stitch_group, start))
+ elif self.fill_method == 'guided_fill':
+ stitch_groups.extend(self.do_guided_fill(fill_shape, previous_stitch_group, start, end))
+ elif self.fill_method == 'meander_fill':
+ stitch_groups.extend(self.do_meander_fill(fill_shape, shape, i, start, end))
+ elif self.fill_method == 'circular_fill':
+ stitch_groups.extend(self.do_circular_fill(fill_shape, previous_stitch_group, start, end))
+ else:
+ # auto_fill
+ stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
previous_stitch_group = stitch_groups[-1]
return stitch_groups
@@ -885,28 +877,6 @@ class FillStitch(EmbroideryElement):
else:
return guide_lines['stroke'][0]
- def fatal_fill_error(self):
- if hasattr(sys, 'gettrace') and sys.gettrace():
- # if we're debugging, let the exception bubble up
- raise
-
- # for an uncaught exception, give a little more info so that they can create a bug report
- message = ""
- message += _("Error during autofill! This means it is a bug in Ink/Stitch.")
- message += "\n\n"
- # L10N this message is followed by a URL: https://github.com/inkstitch/inkstitch/issues/new
- message += _("If you'd like to help please\n"
- "- copy the entire error message below\n"
- "- save your SVG file and\n"
- "- create a new issue at")
- message += " https://github.com/inkstitch/inkstitch/issues/new\n\n"
- message += _("Include the error description and also (if possible) the svg file.")
- message += '\n\n\n'
- message += version.get_inkstitch_version() + '\n'
- message += traceback.format_exc()
-
- self.fatal(message)
-
def do_circular_fill(self, shape, last_patch, starting_point, ending_point):
# get target position
command = self.get_command('ripple_target')
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index 25f50e13..a63ca403 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -280,7 +280,7 @@ class SatinColumn(EmbroideryElement):
def reverse_rails(self):
return self.get_param('reverse_rails', 'automatic')
- def _get_rails_to_reverse(self, rails):
+ def _get_rails_to_reverse(self):
choice = self.reverse_rails
if choice == 'first':
@@ -290,6 +290,7 @@ class SatinColumn(EmbroideryElement):
elif choice == 'both':
return True, True
elif choice == 'automatic':
+ rails = [shgeo.LineString(self.flatten_subpath(rail)) for rail in self.rails]
if len(rails) == 2:
# Sample ten points along the rails. Compare the distance
# between corresponding points on both rails with and without
@@ -314,7 +315,7 @@ class SatinColumn(EmbroideryElement):
# reverse the second rail
return False, True
- return None
+ return False, False
@property
@param(
@@ -508,7 +509,7 @@ class SatinColumn(EmbroideryElement):
"""The rails, as LineStrings."""
paths = [shgeo.LineString(self.flatten_subpath(rail)) for rail in self.rails]
- rails_to_reverse = self._get_rails_to_reverse(paths)
+ rails_to_reverse = self._get_rails_to_reverse()
if paths and rails_to_reverse is not None:
for i, reverse in enumerate(rails_to_reverse):
if reverse:
@@ -537,14 +538,19 @@ class SatinColumn(EmbroideryElement):
else:
return [subpath for i, subpath in enumerate(self.csp) if i not in self.rail_indices]
+ @cache
def _synthesize_rungs(self):
rung_endpoints = []
# check for unequal length of rails
equal_length = len(self.rails[0]) == len(self.rails[1])
- for rail in self.rails:
+ rails_to_reverse = self._get_rails_to_reverse()
+ for i, rail in enumerate(self.rails):
points = self.strip_control_points(rail)
+ if rails_to_reverse[i]:
+ points = points[::-1]
+
if len(points) > 2 or not equal_length:
# Don't bother putting rungs at the start and end.
points = points[1:-1]
@@ -744,6 +750,9 @@ class SatinColumn(EmbroideryElement):
Returns two new SatinColumn instances: the part before and the part
after the split point. All parameters are copied over to the new
SatinColumn instances.
+
+ The returned SatinColumns will not be in the SVG document and will have
+ their transforms applied.
"""
cut_points = self._find_cut_points(split_point)
@@ -856,6 +865,43 @@ class SatinColumn(EmbroideryElement):
return SatinColumn(node)
+ def merge(self, satin):
+ """Merge this satin with another satin
+
+ This method expects that the provided satin continues on directly after
+ this one, as would be the case, for example, if the two satins were the
+ result of the split() method.
+
+ Returns a new SatinColumn instance that combines the rails and rungs of
+ this satin and the provided satin. A rung is added at the end of this
+ satin.
+
+ The returned SatinColumn will not be in the SVG document and will have
+ its transforms applied.
+ """
+ rails = [self.flatten_subpath(rail) for rail in self.rails]
+ other_rails = [satin.flatten_subpath(rail) for rail in satin.rails]
+
+ if len(rails) != 2 or len(other_rails) != 2:
+ # weird non-satin things, give up and don't merge
+ return self
+
+ # remove first node of each other rail before merging (avoid duplicated nodes)
+ rails[0].extend(other_rails[0][1:])
+ rails[1].extend(other_rails[1][1:])
+
+ rungs = [self.flatten_subpath(rung) for rung in self.rungs]
+ other_rungs = [satin.flatten_subpath(rung) for rung in satin.rungs]
+
+ # add a rung in between the two satins and extend it just a litte to ensure it is crossing the rails
+ new_rung = shgeo.LineString([other_rails[0][0], other_rails[1][0]])
+ rungs.append(list(shaffinity.scale(new_rung, 1.2, 1.2).coords))
+
+ # add on the other satin's rungs
+ rungs.extend(other_rungs)
+
+ return self._csp_to_satin(point_lists_to_csp(rails + rungs))
+
@property
@cache
def center_line(self):