| 
									
										
										
										
											2018-03-31 00:37:11 +00:00
										 |  |  | from itertools import chain, izip | 
					
						
							| 
									
										
										
										
											2018-05-02 01:21:07 +00:00
										 |  |  | from shapely import geometry as shgeo, ops as shops | 
					
						
							| 
									
										
										
										
											2018-03-31 00:37:11 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | from .element import param, EmbroideryElement, Patch | 
					
						
							| 
									
										
										
										
											2018-05-02 01:21:07 +00:00
										 |  |  | from ..i18n import _ | 
					
						
							|  |  |  | from ..utils import cache, Point | 
					
						
							| 
									
										
										
										
											2018-03-31 00:37:11 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SatinColumn(EmbroideryElement): | 
					
						
							|  |  |  |     element_name = _("Satin Column") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, *args, **kwargs): | 
					
						
							|  |  |  |         super(SatinColumn, self).__init__(*args, **kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('satin_column', _('Custom satin column'), type='toggle') | 
					
						
							|  |  |  |     def satin_column(self): | 
					
						
							|  |  |  |         return self.get_boolean_param("satin_column") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def color(self): | 
					
						
							|  |  |  |         return self.get_style("stroke") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('zigzag_spacing_mm', _('Zig-zag spacing (peak-to-peak)'), unit='mm', type='float', default=0.4) | 
					
						
							|  |  |  |     def zigzag_spacing(self): | 
					
						
							|  |  |  |         # peak-to-peak distance between zigzags | 
					
						
							|  |  |  |         return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('pull_compensation_mm', _('Pull compensation'), unit='mm', type='float') | 
					
						
							|  |  |  |     def pull_compensation(self): | 
					
						
							|  |  |  |         # In satin stitch, the stitches have a tendency to pull together and | 
					
						
							|  |  |  |         # narrow the entire column.  We can compensate for this by stitching | 
					
						
							|  |  |  |         # wider than we desire the column to end up. | 
					
						
							|  |  |  |         return self.get_float_param("pull_compensation_mm", 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('contour_underlay', _('Contour underlay'), type='toggle', group=_('Contour Underlay')) | 
					
						
							|  |  |  |     def contour_underlay(self): | 
					
						
							|  |  |  |         # "Contour underlay" is stitching just inside the rectangular shape | 
					
						
							|  |  |  |         # of the satin column; that is, up one side and down the other. | 
					
						
							|  |  |  |         return self.get_boolean_param("contour_underlay") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('contour_underlay_stitch_length_mm', _('Stitch length'), unit='mm', group=_('Contour Underlay'), type='float', default=1.5) | 
					
						
							|  |  |  |     def contour_underlay_stitch_length(self): | 
					
						
							|  |  |  |         return max(self.get_float_param("contour_underlay_stitch_length_mm", 1.5), 0.01) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('contour_underlay_inset_mm', _('Contour underlay inset amount'), unit='mm', group=_('Contour Underlay'), type='float', default=0.4) | 
					
						
							|  |  |  |     def contour_underlay_inset(self): | 
					
						
							|  |  |  |         # how far inside the edge of the column to stitch the underlay | 
					
						
							|  |  |  |         return self.get_float_param("contour_underlay_inset_mm", 0.4) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('center_walk_underlay', _('Center-walk underlay'), type='toggle', group=_('Center-Walk Underlay')) | 
					
						
							|  |  |  |     def center_walk_underlay(self): | 
					
						
							|  |  |  |         # "Center walk underlay" is stitching down and back in the centerline | 
					
						
							|  |  |  |         # between the two sides of the satin column. | 
					
						
							|  |  |  |         return self.get_boolean_param("center_walk_underlay") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('center_walk_underlay_stitch_length_mm', _('Stitch length'), unit='mm', group=_('Center-Walk Underlay'), type='float', default=1.5) | 
					
						
							|  |  |  |     def center_walk_underlay_stitch_length(self): | 
					
						
							|  |  |  |         return max(self.get_float_param("center_walk_underlay_stitch_length_mm", 1.5), 0.01) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('zigzag_underlay', _('Zig-zag underlay'), type='toggle', group=_('Zig-zag Underlay')) | 
					
						
							|  |  |  |     def zigzag_underlay(self): | 
					
						
							|  |  |  |         return self.get_boolean_param("zigzag_underlay") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('zigzag_underlay_spacing_mm', _('Zig-Zag spacing (peak-to-peak)'), unit='mm', group=_('Zig-zag Underlay'), type='float', default=3) | 
					
						
							|  |  |  |     def zigzag_underlay_spacing(self): | 
					
						
							|  |  |  |         return max(self.get_float_param("zigzag_underlay_spacing_mm", 3), 0.01) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @param('zigzag_underlay_inset_mm', _('Inset amount (default: half of contour underlay inset)'), unit='mm', group=_('Zig-zag Underlay'), type='float') | 
					
						
							|  |  |  |     def zigzag_underlay_inset(self): | 
					
						
							|  |  |  |         # how far in from the edge of the satin the points in the zigzags | 
					
						
							|  |  |  |         # should be | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Default to half of the contour underlay inset.  That is, if we're | 
					
						
							|  |  |  |         # doing both contour underlay and zigzag underlay, make sure the | 
					
						
							|  |  |  |         # points of the zigzag fall outside the contour underlay but inside | 
					
						
							|  |  |  |         # the edges of the satin column. | 
					
						
							|  |  |  |         return self.get_float_param("zigzag_underlay_inset_mm") or self.contour_underlay_inset / 2.0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @cache | 
					
						
							|  |  |  |     def csp(self): | 
					
						
							|  |  |  |         return self.parse_path() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     @cache | 
					
						
							|  |  |  |     def flattened_beziers(self): | 
					
						
							|  |  |  |         if len(self.csp) == 2: | 
					
						
							|  |  |  |             return self.simple_flatten_beziers() | 
					
						
							| 
									
										
										
										
											2018-06-10 20:01:37 +00:00
										 |  |  |         elif len(self.csp) < 2: | 
					
						
							|  |  |  |             self.fatal(_("satin column: %(id)s: at least two subpaths required (%(num)d found)") % dict(num=len(self.csp), id=self.node.get('id'))) | 
					
						
							| 
									
										
										
										
											2018-03-31 00:37:11 +00:00
										 |  |  |         else: | 
					
						
							|  |  |  |             return self.flatten_beziers_with_rungs() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def flatten_beziers_with_rungs(self): | 
					
						
							|  |  |  |         input_paths = [self.flatten([path]) for path in self.csp] | 
					
						
							|  |  |  |         input_paths = [shgeo.LineString(path[0]) for path in input_paths] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         paths = input_paths[:] | 
					
						
							|  |  |  |         paths.sort(key=lambda path: path.length, reverse=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Imagine a satin column as a curvy ladder. | 
					
						
							|  |  |  |         # The two long paths are the "rails" of the ladder.  The remainder are | 
					
						
							|  |  |  |         # the "rungs". | 
					
						
							|  |  |  |         rails = paths[:2] | 
					
						
							|  |  |  |         rungs = shgeo.MultiLineString(paths[2:]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # The rails should stay in the order they were in the original CSP. | 
					
						
							|  |  |  |         # (this lets the user control where the satin starts and ends) | 
					
						
							|  |  |  |         rails.sort(key=lambda rail: input_paths.index(rail)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         result = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for rail in rails: | 
					
						
							|  |  |  |             if not rail.is_simple: | 
					
						
							|  |  |  |                 self.fatal(_("One or more rails crosses itself, and this is not allowed.  Please split into multiple satin columns.")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # handle null intersections here? | 
					
						
							|  |  |  |             linestrings = shops.split(rail, rungs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             #print >> dbg, "rails and rungs", [str(rail) for rail in rails], [str(rung) for rung in rungs] | 
					
						
							|  |  |  |             if len(linestrings.geoms) < len(rungs.geoms) + 1: | 
					
						
							|  |  |  |                 self.fatal(_("satin column: One or more of the rungs doesn't intersect both rails.") + "  " + _("Each rail should intersect both rungs once.")) | 
					
						
							|  |  |  |             elif len(linestrings.geoms) > len(rungs.geoms) + 1: | 
					
						
							|  |  |  |                 self.fatal(_("satin column: One or more of the rungs intersects the rails more than once.") + "  " + _("Each rail should intersect both rungs once.")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             paths = [[Point(*coord) for coord in ls.coords] for ls in linestrings.geoms] | 
					
						
							|  |  |  |             result.append(paths) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return zip(*result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def simple_flatten_beziers(self): | 
					
						
							|  |  |  |         # Given a pair of paths made up of bezier segments, flatten | 
					
						
							|  |  |  |         # each individual bezier segment into line segments that approximate | 
					
						
							|  |  |  |         # the curves.  Retain the divisions between beziers -- we'll use those | 
					
						
							|  |  |  |         # later. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         paths = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for path in self.csp: | 
					
						
							|  |  |  |             # See the documentation in the parent class for parse_path() for a | 
					
						
							|  |  |  |             # description of the format of the CSP.  Each bezier is constructed | 
					
						
							|  |  |  |             # using two neighboring 3-tuples in the list. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             flattened_path = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # iterate over pairs of 3-tuples | 
					
						
							|  |  |  |             for prev, current in zip(path[:-1], path[1:]): | 
					
						
							|  |  |  |                 flattened_segment = self.flatten([[prev, current]]) | 
					
						
							|  |  |  |                 flattened_segment = [Point(x, y) for x, y in flattened_segment[0]] | 
					
						
							|  |  |  |                 flattened_path.append(flattened_segment) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             paths.append(flattened_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return zip(*paths) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def validate_satin_column(self): | 
					
						
							|  |  |  |         # The node should have exactly two paths with no fill.  Each | 
					
						
							|  |  |  |         # path should have the same number of points, meaning that they | 
					
						
							|  |  |  |         # will both be made up of the same number of bezier curves. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         node_id = self.node.get("id") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.get_style("fill") is not None: | 
					
						
							|  |  |  |             self.fatal(_("satin column: object %s has a fill (but should not)") % node_id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if len(self.csp) == 2: | 
					
						
							|  |  |  |             if len(self.csp[0]) != len(self.csp[1]): | 
					
						
							|  |  |  |                 self.fatal(_("satin column: object %(id)s has two paths with an unequal number of points (%(length1)d and %(length2)d)") % \ | 
					
						
							|  |  |  |                              dict(id=node_id, length1=len(self.csp[0]), length2=len(self.csp[1]))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def offset_points(self, pos1, pos2, offset_px): | 
					
						
							|  |  |  |         # Expand or contract two points about their midpoint.  This is | 
					
						
							|  |  |  |         # useful for pull compensation and insetting underlay. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         distance = (pos1 - pos2).length() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if distance < 0.0001: | 
					
						
							|  |  |  |             # if they're the same point, we don't know which direction | 
					
						
							|  |  |  |             # to offset in, so we have to just return the points | 
					
						
							|  |  |  |             return pos1, pos2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # don't contract beyond the midpoint, or we'll start expanding | 
					
						
							|  |  |  |         if offset_px < -distance / 2.0: | 
					
						
							|  |  |  |             offset_px = -distance / 2.0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pos1 = pos1 + (pos1 - pos2).unit() * offset_px | 
					
						
							|  |  |  |         pos2 = pos2 + (pos2 - pos1).unit() * offset_px | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return pos1, pos2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def walk(self, path, start_pos, start_index, distance): | 
					
						
							|  |  |  |         # Move <distance> pixels along <path>, which is a sequence of line | 
					
						
							|  |  |  |         # segments defined by points. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # <start_index> is the index of the line segment in <path> that | 
					
						
							|  |  |  |         # we're currently on.  <start_pos> is where along that line | 
					
						
							|  |  |  |         # segment we are.  Return a new position and index. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # print >> dbg, "walk", start_pos, start_index, distance | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pos = start_pos | 
					
						
							|  |  |  |         index = start_index | 
					
						
							|  |  |  |         last_index = len(path) - 1 | 
					
						
							|  |  |  |         distance_remaining = distance | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         while True: | 
					
						
							|  |  |  |             if index >= last_index: | 
					
						
							|  |  |  |                 return pos, index | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             segment_end = path[index + 1] | 
					
						
							|  |  |  |             segment = segment_end - pos | 
					
						
							|  |  |  |             segment_length = segment.length() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if segment_length > distance_remaining: | 
					
						
							|  |  |  |                 # our walk ends partway along this segment | 
					
						
							|  |  |  |                 return pos + segment.unit() * distance_remaining, index | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 # our walk goes past the end of this segment, so advance | 
					
						
							|  |  |  |                 # one point | 
					
						
							|  |  |  |                 index += 1 | 
					
						
							|  |  |  |                 distance_remaining -= segment_length | 
					
						
							|  |  |  |                 pos = segment_end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def walk_paths(self, spacing, offset): | 
					
						
							|  |  |  |         # Take a bezier segment from each path in turn, and plot out an | 
					
						
							|  |  |  |         # equal number of points on each bezier.  Return the points plotted. | 
					
						
							|  |  |  |         # The points will be contracted or expanded by offset using | 
					
						
							|  |  |  |         # offset_points(). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         points = [[], []] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def add_pair(pos1, pos2): | 
					
						
							|  |  |  |             pos1, pos2 = self.offset_points(pos1, pos2, offset) | 
					
						
							|  |  |  |             points[0].append(pos1) | 
					
						
							|  |  |  |             points[1].append(pos2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # We may not be able to fit an even number of zigzags in each pair of | 
					
						
							|  |  |  |         # beziers.  We'll store the remaining bit of the beziers after handling | 
					
						
							|  |  |  |         # each section. | 
					
						
							|  |  |  |         remainder_path1 = [] | 
					
						
							|  |  |  |         remainder_path2 = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for segment1, segment2 in self.flattened_beziers: | 
					
						
							|  |  |  |             subpath1 = remainder_path1 + segment1 | 
					
						
							|  |  |  |             subpath2 = remainder_path2 + segment2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             len1 = shgeo.LineString(subpath1).length | 
					
						
							|  |  |  |             len2 = shgeo.LineString(subpath2).length | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Base the number of stitches in each section on the _longest_ of | 
					
						
							|  |  |  |             # the two beziers. Otherwise, things could get too sparse when one | 
					
						
							|  |  |  |             # side is significantly longer (e.g. when going around a corner). | 
					
						
							|  |  |  |             # The risk here is that we poke a hole in the fabric if we try to | 
					
						
							|  |  |  |             # cram too many stitches on the short bezier.  The user will need | 
					
						
							|  |  |  |             # to avoid this through careful construction of paths. | 
					
						
							|  |  |  |             # | 
					
						
							|  |  |  |             # TODO: some commercial machine embroidery software compensates by | 
					
						
							|  |  |  |             # pulling in some of the "inner" stitches toward the center a bit. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # note, this rounds down using integer-division | 
					
						
							|  |  |  |             num_points = max(len1, len2) / spacing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             spacing1 = len1 / num_points | 
					
						
							|  |  |  |             spacing2 = len2 / num_points | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             pos1 = subpath1[0] | 
					
						
							|  |  |  |             index1 = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             pos2 = subpath2[0] | 
					
						
							|  |  |  |             index2 = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for i in xrange(int(num_points)): | 
					
						
							|  |  |  |                 add_pair(pos1, pos2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 pos1, index1 = self.walk(subpath1, pos1, index1, spacing1) | 
					
						
							|  |  |  |                 pos2, index2 = self.walk(subpath2, pos2, index2, spacing2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if index1 < len(subpath1) - 1: | 
					
						
							|  |  |  |                 remainder_path1 = [pos1] + subpath1[index1 + 1:] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 remainder_path1 = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if index2 < len(subpath2) - 1: | 
					
						
							|  |  |  |                 remainder_path2 = [pos2] + subpath2[index2 + 1:] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 remainder_path2 = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # We're off by one in the algorithm above, so we need one more | 
					
						
							|  |  |  |         # pair of points.  We also want to add points at the very end to | 
					
						
							|  |  |  |         # make sure we match the vectors on screen as best as possible. | 
					
						
							|  |  |  |         # Try to avoid doing both if they're going to stack up too | 
					
						
							|  |  |  |         # closely. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         end1 = remainder_path1[-1] | 
					
						
							|  |  |  |         end2 = remainder_path2[-1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (end1 - pos1).length() > 0.3 * spacing: | 
					
						
							|  |  |  |             add_pair(pos1, pos2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         add_pair(end1, end2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return points | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def do_contour_underlay(self): | 
					
						
							|  |  |  |         # "contour walk" underlay: do stitches up one side and down the | 
					
						
							|  |  |  |         # other. | 
					
						
							|  |  |  |         forward, back = self.walk_paths(self.contour_underlay_stitch_length, | 
					
						
							|  |  |  |                                         -self.contour_underlay_inset) | 
					
						
							|  |  |  |         return Patch(color=self.color, stitches=(forward + list(reversed(back)))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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.walk_paths(self.center_walk_underlay_stitch_length, | 
					
						
							|  |  |  |                                         -100000) | 
					
						
							|  |  |  |         return Patch(color=self.color, stitches=(forward + list(reversed(back)))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def do_zigzag_underlay(self): | 
					
						
							|  |  |  |         # zigzag underlay, usually done at a much lower density than the | 
					
						
							|  |  |  |         # satin itself.  It looks like this: | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # \/\/\/\/\/\/\/\/\/\/| | 
					
						
							|  |  |  |         # /\/\/\/\/\/\/\/\/\/\| | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # In combination with the "contour walk" underlay, this is the | 
					
						
							|  |  |  |         # "German underlay" described here: | 
					
						
							|  |  |  |         #   http://www.mrxstitch.com/underlay-what-lies-beneath-machine-embroidery/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         patch = Patch(color=self.color) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         sides = self.walk_paths(self.zigzag_underlay_spacing / 2.0, | 
					
						
							|  |  |  |                                 -self.zigzag_underlay_inset) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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])), | 
					
						
							|  |  |  |                  sides[1][1::2] + list(reversed(sides[1][::2]))] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # This fancy bit of iterable magic just repeatedly takes a point | 
					
						
							|  |  |  |         # from each side in turn. | 
					
						
							|  |  |  |         for point in chain.from_iterable(izip(*sides)): | 
					
						
							|  |  |  |             patch.add_stitch(point) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return patch | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def do_satin(self): | 
					
						
							|  |  |  |         # satin: do a zigzag pattern, alternating between the paths.  The | 
					
						
							|  |  |  |         # zigzag looks like this to make the satin stitches look perpendicular | 
					
						
							|  |  |  |         # to the column: | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # /|/|/|/|/|/|/|/| | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         patch = Patch(color=self.color) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         sides = self.walk_paths(self.zigzag_spacing, self.pull_compensation) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Like in zigzag_underlay(): take a point from each side in turn. | 
					
						
							|  |  |  |         for point in chain.from_iterable(izip(*sides)): | 
					
						
							|  |  |  |             patch.add_stitch(point) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return patch | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def to_patches(self, last_patch): | 
					
						
							|  |  |  |         # Stitch a variable-width satin column, zig-zagging between two paths. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # The algorithm will draw zigzags between each consecutive pair of | 
					
						
							|  |  |  |         # beziers.  The boundary points between beziers serve as "checkpoints", | 
					
						
							|  |  |  |         # allowing the user to control how the zigzags flow around corners. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # First, verify that we have valid paths. | 
					
						
							|  |  |  |         self.validate_satin_column() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         patches = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.center_walk_underlay: | 
					
						
							|  |  |  |             patches.append(self.do_center_walk()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.contour_underlay: | 
					
						
							|  |  |  |             patches.append(self.do_contour_underlay()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.zigzag_underlay: | 
					
						
							|  |  |  |             # zigzag underlay comes after contour walk underlay, so that the | 
					
						
							|  |  |  |             # zigzags sit on the contour walk underlay like rail ties on rails. | 
					
						
							|  |  |  |             patches.append(self.do_zigzag_underlay()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         patches.append(self.do_satin()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return patches |