diff options
| author | Lex Neva <lexelby@users.noreply.github.com> | 2023-07-03 19:55:19 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-03 19:55:19 -0400 |
| commit | 1301b02c0d8c462810e55c39970d77535fbd1adc (patch) | |
| tree | 33962fa581ccc47cac98967948552193bbbf1471 /lib | |
| parent | f8df02ff9eccb740c193c0af9707d5b9cc41dac0 (diff) | |
| parent | 72a0a7384fb61daa2cc5ce4082cf1ff222831710 (diff) | |
Merge pull request #2346 from inkstitch/lexelby/fill-clamp-and-smooth
auto-fill clamp and smooth
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/elements/fill_stitch.py | 7 | ||||
| -rw-r--r-- | lib/stitches/auto_fill.py | 30 | ||||
| -rw-r--r-- | lib/stitches/circular_fill.py | 6 | ||||
| -rw-r--r-- | lib/stitches/guided_fill.py | 10 | ||||
| -rw-r--r-- | lib/stitches/running_stitch.py | 2 | ||||
| -rw-r--r-- | lib/utils/clamp_path.py | 21 | ||||
| -rw-r--r-- | lib/utils/smoothing.py | 10 |
7 files changed, 51 insertions, 35 deletions
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) diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 03930ddd..50eea18e 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -17,9 +17,11 @@ 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 +from ..utils.smoothing import smooth_path from ..utils.threading import check_stop_flag @@ -77,9 +79,9 @@ 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) + staggers, skip_last, underpath) return result @@ -350,9 +352,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) @@ -618,20 +620,26 @@ 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, 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') - path = [Stitch(*p) for p in path] - stitches = running_stitch(path, running_stitch_length, running_stitch_tolerance) + 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) + stitches = [Stitch(point) for point in points] for stitch in stitches: stitch.add_tag('auto_fill_travel') @@ -653,8 +661,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, underpath): path = collapse_sequential_outline_edges(path, fill_stitch_graph) stitches = [] @@ -668,7 +676,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, underpath)) check_stop_flag() 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 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/clamp_path.py b/lib/utils/clamp_path.py index e5ef78d8..f9b8991a 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): @@ -66,23 +66,34 @@ 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 """ - 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)) + + 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)] - # contains() checks can fail without this. + # 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: 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] |
