From 9ae97154d689b188c796e5d11820e026ed1f9326 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 12 Dec 2018 20:26:22 -0500 Subject: add option to skip last stitch in fill rows --- lib/stitches/auto_fill.py | 24 ++++++++++++++---------- lib/stitches/fill.py | 15 ++++++++------- 2 files changed, 22 insertions(+), 17 deletions(-) (limited to 'lib/stitches') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 28c79eff..0b5782e5 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -1,13 +1,14 @@ +from collections import deque +from itertools import groupby, izip import sys -import shapely + import networkx -from itertools import groupby, izip -from collections import deque +import shapely -from .fill import intersect_region_with_grating, row_num, stitch_row -from .running_stitch import running_stitch from ..i18n import _ from ..utils.geometry import Point as InkstitchPoint, cut +from .fill import intersect_region_with_grating, row_num, stitch_row +from .running_stitch import running_stitch class MaxQueueLengthExceeded(Exception): @@ -39,7 +40,7 @@ class PathEdge(object): return self.key == self.SEGMENT_KEY -def auto_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, running_stitch_length, staggers, starting_point, ending_point=None): +def auto_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, running_stitch_length, staggers, skip_last, starting_point, ending_point=None): stitches = [] rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing) @@ -48,7 +49,7 @@ def auto_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, run graph = build_graph(shape, segments, angle, row_spacing) path = find_stitch_path(graph, segments, starting_point, ending_point) - stitches.extend(path_to_stitches(graph, path, shape, angle, row_spacing, max_stitch_length, running_stitch_length, staggers)) + stitches.extend(path_to_stitches(graph, path, shape, angle, row_spacing, max_stitch_length, running_stitch_length, staggers, skip_last)) return stitches @@ -513,7 +514,10 @@ def connect_points(shape, start, end, running_stitch_length, row_spacing): # 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) + stitches = running_stitch(path, running_stitch_length) + + # The row of stitches already stitched the first point, so skip it. + return stitches[1:] def trim_end(path): @@ -521,14 +525,14 @@ def trim_end(path): path.pop() -def path_to_stitches(graph, path, shape, angle, row_spacing, max_stitch_length, running_stitch_length, staggers): +def path_to_stitches(graph, path, shape, angle, row_spacing, max_stitch_length, running_stitch_length, staggers, skip_last): path = collapse_sequential_outline_edges(graph, path) stitches = [] for edge in path: if edge.is_segment(): - stitch_row(stitches, edge[0], edge[1], angle, row_spacing, max_stitch_length, staggers) + stitch_row(stitches, edge[0], edge[1], angle, row_spacing, max_stitch_length, staggers, skip_last) else: stitches.extend(connect_points(shape, edge[0], edge[1], running_stitch_length, row_spacing)) diff --git a/lib/stitches/fill.py b/lib/stitches/fill.py index af0a8403..e00d66de 100644 --- a/lib/stitches/fill.py +++ b/lib/stitches/fill.py @@ -1,15 +1,16 @@ -import shapely import math +import shapely + from ..svg import PIXELS_PER_MM from ..utils import cache, Point as InkstitchPoint -def legacy_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, flip, staggers): +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) + return [section_to_stitches(group, angle, row_spacing, max_stitch_length, staggers, skip_last) for group in groups_of_segments] @@ -37,7 +38,7 @@ def adjust_stagger(stitch, angle, row_spacing, max_stitch_length, staggers): return stitch - offset * east(angle) -def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, staggers): +def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, staggers, skip_last=False): # We want our stitches to look like this: # # ---*-----------*----------- @@ -81,7 +82,7 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge stitches.append(beg + offset * row_direction) offset += max_stitch_length - if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM: + if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM and not skip_last: stitches.append(end) @@ -164,7 +165,7 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non return rows -def section_to_stitches(group_of_segments, angle, row_spacing, max_stitch_length, staggers): +def section_to_stitches(group_of_segments, angle, row_spacing, max_stitch_length, staggers, skip_last): stitches = [] swap = False @@ -174,7 +175,7 @@ def section_to_stitches(group_of_segments, angle, row_spacing, max_stitch_length if (swap): (beg, end) = (end, beg) - stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, staggers) + stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, staggers, skip_last) swap = not swap -- cgit v1.2.3 From 8f3c922011a5c39a6154863160ea8354a502ed42 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Dec 2018 20:10:50 -0500 Subject: fix style --- lib/stitches/auto_fill.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/stitches') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 0b5782e5..4a0fbed7 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -40,7 +40,16 @@ class PathEdge(object): return self.key == self.SEGMENT_KEY -def auto_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, running_stitch_length, staggers, skip_last, starting_point, ending_point=None): +def auto_fill(shape, + angle, + row_spacing, + end_row_spacing, + max_stitch_length, + running_stitch_length, + staggers, + skip_last, + starting_point, + ending_point=None): stitches = [] rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing) -- cgit v1.2.3 From 04ed93cb25198d216ea7adef66a1f4a1be854b58 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Fri, 4 Jan 2019 20:08:11 -0500 Subject: ability to stitch a single-point running stitch path --- lib/stitches/running_stitch.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/stitches') diff --git a/lib/stitches/running_stitch.py b/lib/stitches/running_stitch.py index 5f8ed21e..fa8c50ba 100644 --- a/lib/stitches/running_stitch.py +++ b/lib/stitches/running_stitch.py @@ -23,7 +23,7 @@ def running_stitch(points, stitch_length): segment_start = points[0] last_segment_direction = None - # This tracks the distance we've travelled along the current segment so + # This tracks the distance we've traveled along the current segment so # far. Each time we make a stitch, we add the stitch_length to this # value. If we fall off the end of the current segment, we carry over # the remainder to the next segment. @@ -62,8 +62,12 @@ def running_stitch(points, stitch_length): last_segment_direction = segment_direction distance -= segment_length + # stitch a single point if the path has a length of zero + if not output: + output.append(segment_start) + # stitch the last point unless we're already almost there - if (segment_start - output[-1]).length() > 0.1: + if (segment_start - output[-1]).length() > 0.1 or len(output) == 0: output.append(segment_start) return output -- cgit v1.2.3 From 6a8edfc1cdc19e90b85c4023ef8611f506317e2d Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Fri, 4 Jan 2019 20:22:10 -0500 Subject: fix crash if fill start and end points are very close --- lib/stitches/auto_fill.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/stitches') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 4a0fbed7..c7e65473 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -327,14 +327,16 @@ def get_outline_nodes(graph, outline_index=0): def find_initial_path(graph, starting_point, ending_point=None): starting_node = nearest_node_on_outline(graph, starting_point) - if ending_point is None: + if ending_point is not None: + ending_node = nearest_node_on_outline(graph, ending_point) + + if ending_point is None or starting_node is ending_node: # If they didn't give an ending point, pick either neighboring node # along the outline -- doesn't matter which. We do this because # the algorithm requires we start with _some_ path. neighbors = [n for n, keys in graph.adj[starting_node].iteritems() if 'outline' in keys] return [PathEdge((starting_node, neighbors[0]), "initial")] else: - ending_node = nearest_node_on_outline(graph, ending_point) outline_nodes = get_outline_nodes(graph) # Multiply the outline_nodes list by 2 (duplicate it) because -- cgit v1.2.3 From fdd9a74fd6fc6e0aacc6766e186c4ffb2fec8e7a Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 26 Jan 2019 19:48:25 -0500 Subject: fix argument bug in auto-satin --- lib/stitches/auto_satin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/stitches') diff --git a/lib/stitches/auto_satin.py b/lib/stitches/auto_satin.py index 7bc3e67c..e204a445 100644 --- a/lib/stitches/auto_satin.py +++ b/lib/stitches/auto_satin.py @@ -242,7 +242,7 @@ class RunningStitch(object): @cache def reversed(self): - return RunningStitch(shgeo.LineString(reversed(self.path.coords)), self.style) + return RunningStitch(shgeo.LineString(reversed(self.path.coords)), self.original_element) def is_sequential(self, other): if not isinstance(other, RunningStitch): -- cgit v1.2.3 From be7d0af82da95d64261351b6281153db1944c0b4 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Fri, 15 Feb 2019 20:51:10 -0500 Subject: improve error message when trying to autofill tiny shapes --- lib/stitches/auto_fill.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'lib/stitches') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index c7e65473..1c0be0a0 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -5,13 +5,18 @@ import sys import networkx import shapely +from ..exceptions import InkstitchException from ..i18n import _ from ..utils.geometry import Point as InkstitchPoint, cut from .fill import intersect_region_with_grating, row_num, stitch_row from .running_stitch import running_stitch -class MaxQueueLengthExceeded(Exception): +class MaxQueueLengthExceeded(InkstitchException): + pass + + +class InvalidPath(InkstitchException): pass @@ -55,7 +60,7 @@ def auto_fill(shape, rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing) segments = [segment for row in rows_of_segments for segment in row] - graph = build_graph(shape, segments, angle, row_spacing) + graph = build_graph(shape, segments, angle, row_spacing, max_stitch_length) path = find_stitch_path(graph, segments, starting_point, ending_point) stitches.extend(path_to_stitches(graph, path, shape, angle, row_spacing, max_stitch_length, running_stitch_length, staggers, skip_last)) @@ -90,7 +95,7 @@ def project(shape, coords, outline_index): return outline.project(shapely.geometry.Point(*coords)) -def build_graph(shape, segments, angle, row_spacing): +def build_graph(shape, segments, angle, row_spacing, max_stitch_length): """build a graph representation of the grating segments This function builds a specialized graph (as in graph theory) that will @@ -173,8 +178,12 @@ def build_graph(shape, segments, angle, row_spacing): if i % 2 == edge_set: graph.add_edge(node1, node2, key="extra") - if not networkx.is_eulerian(graph): - raise Exception(_("Unable to autofill. This most often happens because your shape is made up of multiple sections that aren't connected.")) + if networkx.is_empty(graph) or not networkx.is_eulerian(graph): + if shape.area < max_stitch_length ** 2: + raise InvalidPath(_("This shape is so small that it cannot be filled with rows of stitches. " + "It would probably look best as a satin column or running stitch.")) + else: + raise InvalidPath(_("Cannot parse shape. This most often happens because your shape is made up of multiple sections that aren't connected.")) return graph -- cgit v1.2.3 From fa3236372bcee28f4aaa78da47b68c5d7f32cca4 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 16 Feb 2019 16:46:16 -0500 Subject: fix style --- lib/stitches/auto_fill.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'lib/stitches') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 1c0be0a0..0f07b795 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -178,14 +178,19 @@ def build_graph(shape, segments, angle, row_spacing, max_stitch_length): if i % 2 == edge_set: graph.add_edge(node1, node2, key="extra") + check_graph(graph, shape, max_stitch_length) + + return graph + + +def check_graph(graph, shape, max_stitch_length): if networkx.is_empty(graph) or not networkx.is_eulerian(graph): if shape.area < max_stitch_length ** 2: raise InvalidPath(_("This shape is so small that it cannot be filled with rows of stitches. " "It would probably look best as a satin column or running stitch.")) else: - raise InvalidPath(_("Cannot parse shape. This most often happens because your shape is made up of multiple sections that aren't connected.")) - - return graph + raise InvalidPath(_("Cannot parse shape. " + "This most often happens because your shape is made up of multiple sections that aren't connected.")) def node_list_to_edge_list(node_list): -- cgit v1.2.3