diff options
Diffstat (limited to 'lib/elements/satin_column.py')
| -rw-r--r-- | lib/elements/satin_column.py | 96 |
1 files changed, 78 insertions, 18 deletions
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index cf31c2af..b944bee5 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -6,17 +6,18 @@ from copy import deepcopy from itertools import chain -from inkex import paths from shapely import affinity as shaffinity from shapely import geometry as shgeo from shapely.ops import nearest_points -from .element import EmbroideryElement, param -from .validation import ValidationError, ValidationWarning +from inkex import paths + from ..i18n import _ from ..stitch_plan import StitchGroup from ..svg import line_strings_to_csp, point_lists_to_csp from ..utils import Point, cache, collapse_duplicate_point, cut +from .element import EmbroideryElement, param +from .validation import ValidationError, ValidationWarning class SatinHasFillError(ValidationError): @@ -51,6 +52,15 @@ class UnequalPointsError(ValidationError): ] +class NotStitchableError(ValidationError): + name = _("Not stitchable satin column") + description = _("A satin column consists out of two rails and one or more rungs. This satin column may have a different setup.") + steps_to_solve = [ + _('Make sure your satin column is not a combination of multiple satin columns.'), + _('Go to our website and read how a satin column should look like https://inkstitch.org/docs/stitches/satin-column/'), + ] + + rung_message = _("Each rung should intersect both rails once.") @@ -156,6 +166,11 @@ class SatinColumn(EmbroideryElement): return max(self.get_float_param("center_walk_underlay_stitch_length_mm", 1.5), 0.01) @property + @param('center_walk_underlay_repeats', _('Repeats'), group=_('Center-Walk Underlay'), type='int', default=2, sort_index=2) + def center_walk_underlay_repeats(self): + return max(self.get_int_param("center_walk_underlay_repeats", 2), 1) + + @property @param('zigzag_underlay', _('Zig-zag underlay'), type='toggle', group=_('Zig-zag Underlay')) def zigzag_underlay(self): return self.get_boolean_param("zigzag_underlay") @@ -190,15 +205,23 @@ class SatinColumn(EmbroideryElement): return self.get_float_param("zigzag_underlay_inset_mm") or self.contour_underlay_inset / 2.0 @property + @param('zigzag_underlay_max_stitch_length_mm', + _('Maximum stitch length'), + tooltip=_('Split stitch if distance of maximum stitch length is exceeded'), + unit='mm', + group=_('Zig-zag Underlay'), + type='float', + default="") + def zigzag_underlay_max_stitch_length(self): + return self.get_float_param("zigzag_underlay_max_stitch_length_mm") or None + + @property @cache def shape(self): # This isn't used for satins at all, but other parts of the code # may need to know the general shape of a satin column. - flattened = self.flatten(self.parse_path()) - line_strings = [shgeo.LineString(path) for path in flattened] - - return shgeo.MultiLineString(line_strings) + return shgeo.MultiLineString(self.flattened_rails).convex_hull @property @cache @@ -411,6 +434,12 @@ class SatinColumn(EmbroideryElement): if not intersection.is_empty and not isinstance(intersection, shgeo.Point): yield TooManyIntersectionsError(rung.interpolate(0.5, normalized=True)) + if not self.to_stitch_groups(): + yield NotStitchableError(self.shape.centroid) + + def _center_walk_is_odd(self): + return self.center_walk_underlay_repeats % 2 == 1 + def reverse(self): """Return a new SatinColumn like this one but in the opposite direction. @@ -715,24 +744,34 @@ class SatinColumn(EmbroideryElement): def do_contour_underlay(self): # "contour walk" underlay: do stitches up one side and down the # other. - forward, back = self.plot_points_on_rails(self.contour_underlay_stitch_length, - -self.contour_underlay_inset) + forward, back = self.plot_points_on_rails(self.contour_underlay_stitch_length, -self.contour_underlay_inset) + stitches = (forward + list(reversed(back))) + if self._center_walk_is_odd(): + stitches = (list(reversed(back)) + forward) + return StitchGroup( color=self.color, tags=("satin_column", "satin_column_underlay", "satin_contour_underlay"), - stitches=(forward + list(reversed(back)))) + stitches=stitches) def do_center_walk(self): # Center walk underlay is just a running stitch down and back on the # center line between the bezier curves. # Do it like contour underlay, but inset all the way to the center. - forward, back = self.plot_points_on_rails(self.center_walk_underlay_stitch_length, - -100000) + forward, back = self.plot_points_on_rails(self.center_walk_underlay_stitch_length, -100000) + + stitches = [] + for i in range(self.center_walk_underlay_repeats): + if i % 2 == 0: + stitches += forward + else: + stitches += list(reversed(back)) + return StitchGroup( color=self.color, tags=("satin_column", "satin_column_underlay", "satin_center_walk"), - stitches=(forward + list(reversed(back)))) + stitches=stitches) def do_zigzag_underlay(self): # zigzag underlay, usually done at a much lower density than the @@ -750,6 +789,9 @@ class SatinColumn(EmbroideryElement): sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, -self.zigzag_underlay_inset) + if self._center_walk_is_odd(): + sides = [list(reversed(sides[0])), list(reversed(sides[1]))] + # This organizes the points in each side in the order that they'll be # visited. sides = [sides[0][::2] + list(reversed(sides[0][1::2])), @@ -757,7 +799,14 @@ class SatinColumn(EmbroideryElement): # This fancy bit of iterable magic just repeatedly takes a point # from each side in turn. + last_point = None for point in chain.from_iterable(zip(*sides)): + if last_point and self.zigzag_underlay_max_stitch_length: + if last_point.distance(point) > self.zigzag_underlay_max_stitch_length: + points, count = self._get_split_points(last_point, point, self.zigzag_underlay_max_stitch_length) + for point in points: + patch.add_stitch(point) + last_point = point patch.add_stitch(point) patch.add_tags(("satin_column", "satin_column_underlay", "satin_zigzag_underlay")) @@ -783,6 +832,9 @@ class SatinColumn(EmbroideryElement): for point in chain.from_iterable(zip(*sides)): patch.add_stitch(point) + if self._center_walk_is_odd(): + patch.stitches = list(reversed(patch.stitches)) + patch.add_tags(("satin_column", "satin_column_edge")) return patch @@ -805,6 +857,9 @@ class SatinColumn(EmbroideryElement): patch.add_stitch(right) patch.add_stitch(left) + if self._center_walk_is_odd(): + patch.stitches = list(reversed(patch.stitches)) + patch.add_tags(("satin_column", "e_stitch")) return patch @@ -815,7 +870,7 @@ class SatinColumn(EmbroideryElement): for i, (left, right) in enumerate(zip(*sides)): patch.add_stitch(left) patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) - points, count = self._get_split_points(left, right) + points, count = self._get_split_points(left, right, self.max_stitch_length) for point in points: patch.add_stitch(point) patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch")) @@ -825,23 +880,25 @@ class SatinColumn(EmbroideryElement): # but it looks ugly if the points differ too much # so let's make sure they have at least the same amount of divisions if not i+1 >= len(sides[0]): - points, count = self._get_split_points(right, sides[0][i+1], count) + points, count = self._get_split_points(right, sides[0][i+1], self.max_stitch_length, count) for point in points: patch.add_stitch(point) patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch")) + if self._center_walk_is_odd(): + patch.stitches = list(reversed(patch.stitches)) return patch - def _get_split_points(self, left, right, count=None): + def _get_split_points(self, left, right, max_stitch_length, count=None): points = [] distance = left.distance(right) - split_count = count or int(-(-distance // self.max_stitch_length)) + split_count = count or int(-(-distance // max_stitch_length)) for i in range(split_count): line = shgeo.LineString((left, right)) split_point = line.interpolate((i+1)/split_count, normalized=True) points.append(Point(split_point.x, split_point.y)) return [points, split_count] - def to_stitch_groups(self, last_patch): + def to_stitch_groups(self, last_patch=None): # Stitch a variable-width satin column, zig-zagging between two paths. # The algorithm will draw zigzags between each consecutive pair of @@ -866,4 +923,7 @@ class SatinColumn(EmbroideryElement): else: patch += self.do_satin() + if not patch.stitches: + return [] + return [patch] |
