summaryrefslogtreecommitdiff
path: root/lib/elements/satin_column.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/elements/satin_column.py')
-rw-r--r--lib/elements/satin_column.py96
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]