summaryrefslogtreecommitdiff
path: root/lib/stitches
diff options
context:
space:
mode:
authorLex Neva <github.com@lexneva.name>2018-08-09 15:16:55 -0400
committerLex Neva <github.com@lexneva.name>2018-08-09 15:17:33 -0400
commit80d8257e8029f785620fcaf5b944aff597d51edb (patch)
tree15a52703206d6fe44aee44c8a14a2d8ec3e45e8d /lib/stitches
parent49d1092f1cb9e09bebaa4684d466238a2c000751 (diff)
avoid cutting corners in auto-fill running stitch
Diffstat (limited to 'lib/stitches')
-rw-r--r--lib/stitches/auto_fill.py113
1 files changed, 71 insertions, 42 deletions
diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py
index 6326ced2..46490513 100644
--- a/lib/stitches/auto_fill.py
+++ b/lib/stitches/auto_fill.py
@@ -6,9 +6,10 @@ from itertools import groupby, izip
from collections import deque
from .fill import intersect_region_with_grating, row_num, stitch_row
+from .running_stitch import running_stitch
from ..i18n import _
from ..svg import PIXELS_PER_MM
-from ..utils.geometry import Point as InkstitchPoint
+from ..utils.geometry import Point as InkstitchPoint, cut
class MaxQueueLengthExceeded(Exception):
@@ -437,58 +438,86 @@ def collapse_sequential_outline_edges(graph, path):
return new_path
-def outline_distance(outline, p1, p2):
- # how far around the outline (and in what direction) do I need to go
- # to get from p1 to p2?
-
- p1_projection = outline.project(shapely.geometry.Point(p1))
- p2_projection = outline.project(shapely.geometry.Point(p2))
-
- distance = p2_projection - p1_projection
-
- if abs(distance) > outline.length / 2.0:
- # if we'd have to go more than halfway around, it's faster to go
- # the other way
- if distance < 0:
- return distance + outline.length
- elif distance > 0:
- return distance - outline.length
- else:
- # this ought not happen, but just for completeness, return 0 if
- # p1 and p0 are the same point
- return 0
- else:
- return distance
+def connect_points(shape, start, end, running_stitch_length, row_spacing):
+ """Create stitches to get from one point on an outline of the shape to another.
+ An outline is essentially a loop (a path of points that ends where it starts).
+ Given point A and B on that loop, we want to take the shortest path from one
+ to the other. Due to the way our path-finding algorithm above works, it may
+ have had to take the long way around the shape to get from A to B, but we'd
+ rather ignore that and just get there the short way.
+ """
-def connect_points(shape, start, end, running_stitch_length):
+ # We may be on the outer boundary or on on of the hole boundaries.
outline_index = which_outline(shape, start)
outline = shape.boundary[outline_index]
- pos = outline.project(shapely.geometry.Point(start))
- distance = outline_distance(outline, start, end)
- num_stitches = abs(int(distance / running_stitch_length))
-
- direction = math.copysign(1.0, distance)
- one_stitch = running_stitch_length * direction
-
- stitches = [InkstitchPoint(*outline.interpolate(pos).coords[0])]
-
- for i in xrange(num_stitches):
- pos = (pos + one_stitch) % outline.length
-
- stitches.append(InkstitchPoint(*outline.interpolate(pos).coords[0]))
+ # First, figure out the start and end position along the outline. The
+ # projection gives us the distance travelled down the outline to get to
+ # that point.
+ start = shapely.geometry.Point(start)
+ start_projection = outline.project(start)
+ end = shapely.geometry.Point(end)
+ end_projection = outline.project(end)
+
+ # If the points are pretty close, just jump there. There's a slight
+ # risk that we're going to sew outside the shape here. The way to
+ # avoid that is to use running_stitch() even for these really short
+ # connections, but that would be really slow for all of the
+ # connections from one row to the next.
+ #
+ # This seems to do a good job of avoiding going outside the shape in
+ # most cases. 1.5 is chosen as approximately the length of the
+ # stitch connecting two rows if the side of the shape is at a 45
+ # degree angle to the rows of stitches (sqrt(2)).
+ if abs(end_projection - start_projection) < row_spacing * 1.5:
+ return [InkstitchPoint(end.x, end.y)]
+
+ # The outline path has a "natural" starting point. Think of this as
+ # 0 or 12 on an analog clock.
+
+ # Cut the outline into two paths at the starting point. The first
+ # section will go from 12 o'clock to the starting point. The second
+ # section will go from the starting point all the way around and end
+ # up at 12 again.
+ result = cut(outline, start_projection)
+
+ # result will be None if our starting point happens to already be at
+ # 12 o'clock.
+ if result is not None and result[1] is not None:
+ before, after = result
+
+ # Make a new outline, starting from the starting point. This is
+ # like rotating the clock so that now our starting point is
+ # at 12 o'clock.
+ outline = shapely.geometry.LineString(list(after.coords) + list(before.coords))
+
+ # Now figure out where our ending point is on the newly-rotated clock.
+ end_projection = outline.project(end)
+
+ # Cut the new path at the ending point. before and after now represent
+ # two ways to get from the starting point to the ending point. One
+ # will most likely be longer than the other.
+ before, after = cut(outline, end_projection)
+
+ if before.length <= after.length:
+ points = list(before.coords)
+ else:
+ # after goes from the ending point to the starting point, so reverse
+ # it to get from start to end.
+ points = list(reversed(after.coords))
- end = InkstitchPoint(*end)
- if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM:
- stitches.append(end)
+ # Now do running stitch along the path we've found. running_stitch() will
+ # avoid cutting sharp corners.
+ path = [InkstitchPoint(*p) for p in points]
+ return running_stitch(path, running_stitch_length)
- return stitches
def trim_end(path):
while path and path[-1].is_outline():
path.pop()
+
def path_to_stitches(graph, path, shape, angle, row_spacing, max_stitch_length, running_stitch_length, staggers):
path = collapse_sequential_outline_edges(graph, path)
@@ -498,6 +527,6 @@ def path_to_stitches(graph, path, shape, angle, row_spacing, max_stitch_length,
if edge.is_segment():
stitch_row(stitches, edge[0], edge[1], angle, row_spacing, max_stitch_length, staggers)
else:
- stitches.extend(connect_points(shape, edge[0], edge[1], running_stitch_length))
+ stitches.extend(connect_points(shape, edge[0], edge[1], running_stitch_length, row_spacing))
return stitches