diff options
Diffstat (limited to 'lib/elements')
| -rw-r--r-- | lib/elements/element.py | 55 | ||||
| -rw-r--r-- | lib/elements/fill_stitch.py | 70 | ||||
| -rw-r--r-- | lib/elements/satin_column.py | 54 |
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): |
