kopia lustrzana https://github.com/inkstitch/inkstitch
				
				
				
			
		
			
				
	
	
		
			255 wiersze
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			255 wiersze
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
| # Authors: see git history
 | |
| #
 | |
| # Copyright (c) 2010 Authors
 | |
| # Licensed under the GNU GPL version 3.0 or later.  See the file LICENSE for details.
 | |
| 
 | |
| import math
 | |
| 
 | |
| import shapely
 | |
| 
 | |
| from ..stitch_plan import Stitch
 | |
| from ..svg import PIXELS_PER_MM
 | |
| from ..utils import Point as InkstitchPoint
 | |
| from ..utils import cache
 | |
| from ..stitch_plan import Stitch
 | |
| 
 | |
| 
 | |
| def legacy_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, flip, staggers, skip_last):
 | |
|     rows_of_segments = intersect_region_with_grating(
 | |
|         shape, angle, row_spacing, end_row_spacing, flip)
 | |
|     groups_of_segments = pull_runs(rows_of_segments, shape, row_spacing)
 | |
| 
 | |
|     return [section_to_stitches(group, angle, row_spacing, max_stitch_length, staggers, skip_last)
 | |
|             for group in groups_of_segments]
 | |
| 
 | |
| 
 | |
| @cache
 | |
| def east(angle):
 | |
|     # "east" is the name of the direction that is to the right along a row
 | |
|     return InkstitchPoint(1, 0).rotate(-angle)
 | |
| 
 | |
| 
 | |
| @cache
 | |
| def north(angle):
 | |
|     return east(angle).rotate(math.pi / 2)
 | |
| 
 | |
| 
 | |
| def row_num(point, angle, row_spacing):
 | |
|     return round((point * north(angle)) / row_spacing)
 | |
| 
 | |
| 
 | |
| def adjust_stagger(stitch, angle, row_spacing, max_stitch_length, staggers):
 | |
|     this_row_num = row_num(stitch, angle, row_spacing)
 | |
|     row_stagger = this_row_num % staggers
 | |
|     stagger_offset = (float(row_stagger) / staggers) * max_stitch_length
 | |
|     offset = ((stitch * east(angle)) - stagger_offset) % max_stitch_length
 | |
| 
 | |
|     return stitch - offset * east(angle)
 | |
| 
 | |
| 
 | |
| def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, staggers, skip_last=False):
 | |
|     # We want our stitches to look like this:
 | |
|     #
 | |
|     # ---*-----------*-----------
 | |
|     # ------*-----------*--------
 | |
|     # ---------*-----------*-----
 | |
|     # ------------*-----------*--
 | |
|     # ---*-----------*-----------
 | |
|     #
 | |
|     # Each successive row of stitches will be staggered, with
 | |
|     # num_staggers rows before the pattern repeats.  A value of
 | |
|     # 4 gives a nice fill while hiding the needle holes.  The
 | |
|     # first row is offset 0%, the second 25%, the third 50%, and
 | |
|     # the fourth 75%.
 | |
|     #
 | |
|     # Actually, instead of just starting at an offset of 0, we
 | |
|     # can calculate a row's offset relative to the origin.  This
 | |
|     # way if we have two abutting fill regions, they'll perfectly
 | |
|     # tile with each other.  That's important because we often get
 | |
|     # abutting fill regions from pull_runs().
 | |
| 
 | |
|     beg = Stitch(*beg, tags=('fill_row_start',))
 | |
|     end = Stitch(*end, tags=('fill_row_end',))
 | |
| 
 | |
|     row_direction = (end - beg).unit()
 | |
|     segment_length = (end - beg).length()
 | |
| 
 | |
|     stitches.append(beg)
 | |
| 
 | |
|     first_stitch = adjust_stagger(
 | |
|         beg, angle, row_spacing, max_stitch_length, staggers)
 | |
| 
 | |
|     # we might have chosen our first stitch just outside this row, so move back in
 | |
|     if (first_stitch - beg) * row_direction < 0:
 | |
|         first_stitch += row_direction * max_stitch_length
 | |
| 
 | |
|     offset = (first_stitch - beg).length()
 | |
| 
 | |
|     while offset < segment_length:
 | |
|         stitches.append(
 | |
|             Stitch(beg + offset * row_direction, tags=('fill_row')))
 | |
|         offset += max_stitch_length
 | |
| 
 | |
|     if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM and not skip_last:
 | |
|         stitches.append(end)
 | |
| 
 | |
| 
 | |
| def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=None, flip=False):
 | |
|     # the max line length I'll need to intersect the whole shape is the diagonal
 | |
|     (minx, miny, maxx, maxy) = shape.bounds
 | |
|     upper_left = InkstitchPoint(minx, miny)
 | |
|     lower_right = InkstitchPoint(maxx, maxy)
 | |
|     length = (upper_left - lower_right).length()
 | |
|     half_length = length / 2.0
 | |
| 
 | |
|     # Now get a unit vector rotated to the requested angle.  I use -angle
 | |
|     # because shapely rotates clockwise, but my geometry textbooks taught
 | |
|     # me to consider angles as counter-clockwise from the X axis.
 | |
|     direction = InkstitchPoint(1, 0).rotate(-angle)
 | |
| 
 | |
|     # and get a normal vector
 | |
|     normal = direction.rotate(math.pi / 2)
 | |
| 
 | |
|     # I'll start from the center, move in the normal direction some amount,
 | |
|     # and then walk left and right half_length in each direction to create
 | |
|     # a line segment in the grating.
 | |
|     center = InkstitchPoint((minx + maxx) / 2.0, (miny + maxy) / 2.0)
 | |
| 
 | |
|     # I need to figure out how far I need to go along the normal to get to
 | |
|     # the edge of the shape.  To do that, I'll rotate the bounding box
 | |
|     # angle degrees clockwise and ask for the new bounding box.  The max
 | |
|     # and min y tell me how far to go.
 | |
| 
 | |
|     _, start, _, end = shapely.affinity.rotate(
 | |
|         shape, angle, origin='center', use_radians=True).bounds
 | |
| 
 | |
|     # convert start and end to be relative to center (simplifies things later)
 | |
|     start -= center.y
 | |
|     end -= center.y
 | |
| 
 | |
|     height = abs(end - start)
 | |
| 
 | |
|     # print >> dbg, "grating:", start, end, height, row_spacing, end_row_spacing
 | |
| 
 | |
|     # offset start slightly so that rows are always an even multiple of
 | |
|     # row_spacing_px from the origin.  This makes it so that abutting
 | |
|     # fill regions at the same angle and spacing always line up nicely.
 | |
|     start -= (start + normal * center) % row_spacing
 | |
| 
 | |
|     current_row_y = start
 | |
| 
 | |
|     while current_row_y < end:
 | |
|         p0 = center + normal * current_row_y + direction * half_length
 | |
|         p1 = center + normal * current_row_y - direction * half_length
 | |
|         endpoints = [p0.as_tuple(), p1.as_tuple()]
 | |
|         grating_line = shapely.geometry.LineString(endpoints)
 | |
| 
 | |
|         res = grating_line.intersection(shape)
 | |
| 
 | |
|         if (isinstance(res, shapely.geometry.MultiLineString) or isinstance(res, shapely.geometry.GeometryCollection)):
 | |
|             runs = [line_string.coords for line_string in res.geoms if isinstance(line_string, shapely.geometry.LineString)]
 | |
|         else:
 | |
|             if res.is_empty or len(res.coords) == 1:
 | |
|                 # ignore if we intersected at a single point or no points
 | |
|                 runs = []
 | |
|             else:
 | |
|                 runs = [res.coords]
 | |
| 
 | |
|         if runs:
 | |
|             runs.sort(key=lambda seg: (
 | |
|                 InkstitchPoint(*seg[0]) - upper_left).length())
 | |
| 
 | |
|             if flip:
 | |
|                 runs.reverse()
 | |
|                 runs = [tuple(reversed(run)) for run in runs]
 | |
| 
 | |
|             yield from runs
 | |
| 
 | |
|         if end_row_spacing:
 | |
|             current_row_y += row_spacing + \
 | |
|                 (end_row_spacing - row_spacing) * \
 | |
|                 ((current_row_y - start) / height)
 | |
|         else:
 | |
|             current_row_y += row_spacing
 | |
| 
 | |
| 
 | |
| def section_to_stitches(group_of_segments, angle, row_spacing, max_stitch_length, staggers, skip_last):
 | |
|     stitches = []
 | |
|     swap = False
 | |
| 
 | |
|     for segment in group_of_segments:
 | |
|         (beg, end) = segment
 | |
| 
 | |
|         if (swap):
 | |
|             (beg, end) = (end, beg)
 | |
| 
 | |
|         stitch_row(stitches, beg, end, angle, row_spacing,
 | |
|                    max_stitch_length, staggers, skip_last)
 | |
| 
 | |
|         swap = not swap
 | |
| 
 | |
|     return stitches
 | |
| 
 | |
| 
 | |
| def make_quadrilateral(segment1, segment2):
 | |
|     return shapely.geometry.Polygon((segment1[0], segment1[1], segment2[1], segment2[0], segment1[0]))
 | |
| 
 | |
| 
 | |
| def is_same_run(segment1, segment2, shape, row_spacing):
 | |
|     line1 = shapely.geometry.LineString(segment1)
 | |
|     line2 = shapely.geometry.LineString(segment2)
 | |
| 
 | |
|     if line1.distance(line2) > row_spacing * 1.1:
 | |
|         return False
 | |
| 
 | |
|     quad = make_quadrilateral(segment1, segment2)
 | |
|     quad_area = quad.area
 | |
|     intersection_area = shape.intersection(quad).area
 | |
| 
 | |
|     return (intersection_area / quad_area) >= 0.9
 | |
| 
 | |
| 
 | |
| def pull_runs(rows, shape, row_spacing):
 | |
|     # Given a list of rows, each containing a set of line segments,
 | |
|     # break the area up into contiguous patches of line segments.
 | |
|     #
 | |
|     # This is done by repeatedly pulling off the first line segment in
 | |
|     # each row and calling that a shape.  We have to be careful to make
 | |
|     # sure that the line segments are part of the same shape.  Consider
 | |
|     # the letter "H", with an embroidery angle of 45 degrees.  When
 | |
|     # we get to the bottom of the lower left leg, the next row will jump
 | |
|     # over to midway up the lower right leg.  We want to stop there and
 | |
|     # start a new patch.
 | |
| 
 | |
|     # for row in rows:
 | |
|     #    print >> sys.stderr, len(row)
 | |
| 
 | |
|     # print >>sys.stderr, "\n".join(str(len(row)) for row in rows)
 | |
| 
 | |
|     runs = []
 | |
|     count = 0
 | |
|     while (len(rows) > 0):
 | |
|         run = []
 | |
|         prev = None
 | |
| 
 | |
|         for row_num in range(len(rows)):
 | |
|             row = rows[row_num]
 | |
|             first, rest = row[0], row[1:]
 | |
| 
 | |
|             # TODO: only accept actually adjacent rows here
 | |
|             if prev is not None and not is_same_run(prev, first, shape, row_spacing):
 | |
|                 break
 | |
| 
 | |
|             run.append(first)
 | |
|             prev = first
 | |
| 
 | |
|             rows[row_num] = rest
 | |
| 
 | |
|         # print >> sys.stderr, len(run)
 | |
|         runs.append(run)
 | |
|         rows = [r for r in rows if len(r) > 0]
 | |
| 
 | |
|         count += 1
 | |
| 
 | |
|     return runs
 |