From bb9f7b7d821bc91f15e8c179482081ab5a7d5472 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 29 May 2023 15:26:55 -0400 Subject: avoid travel stitches outside shape --- lib/stitches/auto_fill.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 03930ddd..a9c6914a 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -17,6 +17,7 @@ from shapely.strtree import STRtree from ..debug import debug from ..stitch_plan import Stitch from ..svg import PIXELS_PER_MM +from ..utils.clamp_path import clamp_path_to_polygon from ..utils.geometry import Point as InkstitchPoint, line_string_to_point_list, ensure_multi_line_string from .fill import intersect_region_with_grating, stitch_row from .running_stitch import running_stitch @@ -77,7 +78,7 @@ def auto_fill(shape, travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath) path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) - result = path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, + result = path_to_stitches(shape, path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length, running_stitch_length, running_stitch_tolerance, staggers, skip_last) @@ -618,20 +619,21 @@ def collapse_sequential_outline_edges(path, graph): if not start_of_run: start_of_run = edge[0] - if start_of_run: + if start_of_run and start_of_run != edge[1]: # if we were still in a run, close it off new_path.append(PathEdge((start_of_run, edge[1]), "collapsed")) return new_path -def travel(travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last): +def travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last): """Create stitches to get from one point on an outline of the shape to another.""" start, end = edge path = networkx.shortest_path(travel_graph, start, end, weight='weight') - path = [Stitch(*p) for p in path] - stitches = running_stitch(path, running_stitch_length, running_stitch_tolerance) + path = clamp_path_to_polygon(path, shape) + points = running_stitch(path, running_stitch_length, running_stitch_tolerance) + stitches = [Stitch(point) for point in points] for stitch in stitches: stitch.add_tag('auto_fill_travel') @@ -653,8 +655,8 @@ def travel(travel_graph, edge, running_stitch_length, running_stitch_tolerance, @debug.time -def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length, running_stitch_length, running_stitch_tolerance, - staggers, skip_last): +def path_to_stitches(shape, path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length, running_stitch_length, + running_stitch_tolerance, staggers, skip_last): path = collapse_sequential_outline_edges(path, fill_stitch_graph) stitches = [] @@ -668,7 +670,7 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, stitch_row(stitches, edge[0], edge[1], angle, row_spacing, max_stitch_length, staggers, skip_last) travel_graph.remove_edges_from(fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', [])) else: - stitches.extend(travel(travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last)) + stitches.extend(travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last)) check_stop_flag() -- cgit v1.2.3 From acc2b1a7fbf519cbe4aaed2383211d5ca76a0996 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 29 May 2023 15:59:00 -0400 Subject: slightly less edge avoidance --- lib/stitches/auto_fill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index a9c6914a..cecf65d1 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -351,9 +351,9 @@ def get_segments(graph): def process_travel_edges(graph, fill_stitch_graph, shape, travel_edges): """Weight the interior edges and pre-calculate intersection with fill stitch rows.""" - # Set the weight equal to 5x the edge length, to encourage travel() + # Set the weight equal to 3x the edge length, to encourage travel() # to avoid them. - weight_edges_by_length(graph, 5) + weight_edges_by_length(graph, 3) segments = get_segments(fill_stitch_graph) -- cgit v1.2.3 From 81e84db8a2ab7893cc5984e1da7843e248734d15 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 29 May 2023 21:01:55 -0400 Subject: avoid losing start and end of path when clamping --- lib/utils/clamp_path.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/utils/clamp_path.py b/lib/utils/clamp_path.py index e5ef78d8..f1fc7765 100644 --- a/lib/utils/clamp_path.py +++ b/lib/utils/clamp_path.py @@ -1,6 +1,6 @@ from shapely.geometry import LineString, Point as ShapelyPoint, MultiPolygon from shapely.prepared import prep -from .geometry import Point, ensure_multi_line_string +from .geometry import Point, ensure_geometry_collection def path_to_segments(path): @@ -69,20 +69,25 @@ def clamp_path_to_polygon(path, polygon): Description: https://gis.stackexchange.com/questions/428848/clamp-linestring-to-polygon """ - path = LineString(path) + start = path[0] + end = path[-1] # This splits the path at the points where it intersects with the polygon # border and returns the pieces in the same order as the original path. - split_path = ensure_multi_line_string(path.difference(polygon.boundary)) + split_path = ensure_geometry_collection(LineString(path).difference(polygon.boundary)) - # contains() checks can fail without this. + # Add the start and end points to avoid losing part of the path if the + # start or end coincides with the polygon boundary + split_path = [ShapelyPoint(start), *split_path.geoms, ShapelyPoint(end)] + + # contains() checks can fail without the buffer. buffered_polygon = prep(polygon.buffer(1e-9)) last_segment_inside = None was_inside = False result = [] - for segment in split_path.geoms: + for segment in split_path: if buffered_polygon.contains(segment): if not was_inside: if last_segment_inside is not None: -- cgit v1.2.3 From 12565051880a2527af1eccaa8c876b9287934dea Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 29 May 2023 21:02:21 -0400 Subject: smooth underpaths --- lib/stitches/auto_fill.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index cecf65d1..5e886fae 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -21,6 +21,7 @@ from ..utils.clamp_path import clamp_path_to_polygon from ..utils.geometry import Point as InkstitchPoint, line_string_to_point_list, ensure_multi_line_string from .fill import intersect_region_with_grating, stitch_row from .running_stitch import running_stitch +from ..utils.smoothing import smooth_path from ..utils.threading import check_stop_flag @@ -80,7 +81,7 @@ def auto_fill(shape, path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) result = path_to_stitches(shape, path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length, running_stitch_length, running_stitch_tolerance, - staggers, skip_last) + staggers, skip_last, underpath) return result @@ -626,12 +627,15 @@ def collapse_sequential_outline_edges(path, graph): return new_path -def travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last): +def travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last, underpath): """Create stitches to get from one point on an outline of the shape to another.""" start, end = edge path = networkx.shortest_path(travel_graph, start, end, weight='weight') + if underpath and path != (start, end): + path = smooth_path(path, 2) path = clamp_path_to_polygon(path, shape) + points = running_stitch(path, running_stitch_length, running_stitch_tolerance) stitches = [Stitch(point) for point in points] @@ -656,7 +660,7 @@ def travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tole @debug.time def path_to_stitches(shape, path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length, running_stitch_length, - running_stitch_tolerance, staggers, skip_last): + running_stitch_tolerance, staggers, skip_last, underpath): path = collapse_sequential_outline_edges(path, fill_stitch_graph) stitches = [] @@ -670,7 +674,7 @@ def path_to_stitches(shape, path, travel_graph, fill_stitch_graph, angle, row_sp stitch_row(stitches, edge[0], edge[1], angle, row_spacing, max_stitch_length, staggers, skip_last) travel_graph.remove_edges_from(fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', [])) else: - stitches.extend(travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last)) + stitches.extend(travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last, underpath)) check_stop_flag() -- cgit v1.2.3 From ec68c17a05aa4a3d75fcab069a3b065f473574af Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 29 May 2023 21:45:31 -0400 Subject: remove debug timing in frequently-run functions --- lib/stitches/running_stitch.py | 2 -- lib/utils/smoothing.py | 10 +++------- 2 files changed, 3 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/stitches/running_stitch.py b/lib/stitches/running_stitch.py index 50e3be5f..29e2547d 100644 --- a/lib/stitches/running_stitch.py +++ b/lib/stitches/running_stitch.py @@ -11,7 +11,6 @@ from copy import copy import numpy as np from shapely import geometry as shgeo -from ..debug import debug from ..utils import prng from ..utils.geometry import Point from ..utils.threading import check_stop_flag @@ -248,7 +247,6 @@ def path_to_curves(points: typing.List[Point], min_len: float): return curves -@debug.time def running_stitch(points, stitch_length, tolerance): # Turn a continuous path into a running stitch. stitches = [points[0]] diff --git a/lib/utils/smoothing.py b/lib/utils/smoothing.py index 1bb250c5..2c210e37 100644 --- a/lib/utils/smoothing.py +++ b/lib/utils/smoothing.py @@ -3,7 +3,6 @@ from scipy.interpolate import splprep, splev from .geometry import Point, coordinate_list_to_point_list from ..stitches.running_stitch import running_stitch -from ..debug import debug def _remove_duplicate_coordinates(coords_array): @@ -23,7 +22,6 @@ def _remove_duplicate_coordinates(coords_array): return coords_array[keepers] -@debug.time def smooth_path(path, smoothness=1.0): """Smooth a path of coordinates. @@ -70,8 +68,7 @@ def smooth_path(path, smoothness=1.0): # .T transposes the array (for some reason splprep expects # [[x1, x2, ...], [y1, y2, ...]] - with debug.time_this("splprep"): - tck, fp, ier, msg = splprep(coords.T, s=s, k=3, nest=-1, full_output=1) + tck, fp, ier, msg = splprep(coords.T, s=s, k=3, nest=-1, full_output=1) if ier > 0: debug.log(f"error {ier} smoothing path: {msg}") return path @@ -79,8 +76,7 @@ def smooth_path(path, smoothness=1.0): # Evaluate the spline curve at many points along its length to produce the # smoothed point list. 2 * num_points seems to be a good number, but it # does produce a lot of points. - with debug.time_this("splev"): - smoothed_x_values, smoothed_y_values = splev(np.linspace(0, 1, int(num_points * 2)), tck[0]) - coords = np.array([smoothed_x_values, smoothed_y_values]).T + smoothed_x_values, smoothed_y_values = splev(np.linspace(0, 1, int(num_points * 2)), tck[0]) + coords = np.array([smoothed_x_values, smoothed_y_values]).T return [Point(x, y) for x, y in coords] -- cgit v1.2.3 From f9438ac9ff5886f25e4ad18271769ef77de806d0 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 29 May 2023 21:55:17 -0400 Subject: return early for paths already inside --- lib/utils/clamp_path.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/utils/clamp_path.py b/lib/utils/clamp_path.py index f1fc7765..f9b8991a 100644 --- a/lib/utils/clamp_path.py +++ b/lib/utils/clamp_path.py @@ -66,6 +66,8 @@ def find_border(polygon, point): def clamp_path_to_polygon(path, polygon): """Constrain a path to a Polygon. + The path is expected to have at least some part inside the Polygon. + Description: https://gis.stackexchange.com/questions/428848/clamp-linestring-to-polygon """ @@ -76,6 +78,10 @@ def clamp_path_to_polygon(path, polygon): # border and returns the pieces in the same order as the original path. split_path = ensure_geometry_collection(LineString(path).difference(polygon.boundary)) + if len(split_path.geoms) == 1: + # The path never intersects with the polygon, so it's entirely inside. + return path + # Add the start and end points to avoid losing part of the path if the # start or end coincides with the polygon boundary split_path = [ShapelyPoint(start), *split_path.geoms, ShapelyPoint(end)] -- cgit v1.2.3 From 6c5920f48e621cbd5ed36c1b46b73ab1d147500d Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 29 May 2023 22:00:09 -0400 Subject: default more likely to avoid stitching outside shape --- lib/elements/fill_stitch.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py index 80e68247..b93d7ff5 100644 --- a/lib/elements/fill_stitch.py +++ b/lib/elements/fill_stitch.py @@ -364,12 +364,11 @@ class FillStitch(EmbroideryElement): @property @param('running_stitch_tolerance_mm', _('Running stitch tolerance'), - tooltip=_('All stitches must be within this distance of the path. ' + - 'A lower tolerance means stitches will be closer together. ' + - 'A higher tolerance means sharp corners may be rounded.'), + tooltip=_('Determines how hard Ink/Stitch tries to avoid stitching outside the shape.' + + 'Lower numbers are less likely to stitch outside the shape but require more stitches.'), unit='mm', type='float', - default=0.2, + default=0.1, sort_index=32) def running_stitch_tolerance(self): return max(self.get_float_param("running_stitch_tolerance_mm", 0.2), 0.01) -- cgit v1.2.3 From 799b9a9131185bae3cb392bd0603e903498074dc Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 31 May 2023 20:49:20 -0400 Subject: ensure Points --- lib/stitches/auto_fill.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 5e886fae..50eea18e 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -634,6 +634,8 @@ def travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tole path = networkx.shortest_path(travel_graph, start, end, weight='weight') if underpath and path != (start, end): path = smooth_path(path, 2) + else: + path = [InkstitchPoint.from_tuple(point) for point in path] path = clamp_path_to_polygon(path, shape) points = running_stitch(path, running_stitch_length, running_stitch_tolerance) -- cgit v1.2.3 From 72a0a7384fb61daa2cc5ce4082cf1ff222831710 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Sat, 1 Jul 2023 08:29:07 +0200 Subject: fix guided and circular fill --- lib/stitches/circular_fill.py | 6 +++--- lib/stitches/guided_fill.py | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/stitches/circular_fill.py b/lib/stitches/circular_fill.py index e63f5607..351f4cf4 100644 --- a/lib/stitches/circular_fill.py +++ b/lib/stitches/circular_fill.py @@ -78,7 +78,7 @@ def circular_fill(shape, travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath) path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) - result = path_to_stitches(path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last) + result = path_to_stitches(shape, path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last, underpath) result = _apply_bean_stitch_and_repeats(result, repeats, bean_stitch_repeats) return result @@ -117,7 +117,7 @@ def _get_start_end_sequence(outline, start, end): return substring(outline, start_dist, end_dist) -def path_to_stitches(path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last): +def path_to_stitches(shape, path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last, underpath): path = collapse_sequential_outline_edges(path, fill_stitch_graph) stitches = [] @@ -144,6 +144,6 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, running_stitch_lengt travel_graph.remove_edges_from(fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', [])) else: - stitches.extend(travel(travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last)) + stitches.extend(travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last, underpath)) return stitches diff --git a/lib/stitches/guided_fill.py b/lib/stitches/guided_fill.py index 7b80c021..e4793838 100644 --- a/lib/stitches/guided_fill.py +++ b/lib/stitches/guided_fill.py @@ -45,7 +45,9 @@ def guided_fill(shape, travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath) path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) - result = path_to_stitches(path, travel_graph, fill_stitch_graph, max_stitch_length, running_stitch_length, running_stitch_tolerance, skip_last) + result = path_to_stitches(shape, path, travel_graph, fill_stitch_graph, + max_stitch_length, running_stitch_length, running_stitch_tolerance, skip_last, + underpath) return result @@ -59,7 +61,9 @@ def fallback(shape, guideline, row_spacing, max_stitch_length, running_stitch_le num_staggers, skip_last, starting_point, ending_point, underpath) -def path_to_stitches(path, travel_graph, fill_stitch_graph, stitch_length, running_stitch_length, running_stitch_tolerance, skip_last): +def path_to_stitches(shape, path, travel_graph, fill_stitch_graph, + stitch_length, running_stitch_length, running_stitch_tolerance, skip_last, + underpath): path = collapse_sequential_outline_edges(path, fill_stitch_graph) stitches = [] @@ -89,7 +93,7 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, stitch_length, runni travel_graph.remove_edges_from(fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', [])) else: - stitches.extend(travel(travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last)) + stitches.extend(travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tolerance, skip_last, underpath)) return stitches -- cgit v1.2.3