summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLex Neva <lexelby@users.noreply.github.com>2023-07-03 19:55:19 -0400
committerGitHub <noreply@github.com>2023-07-03 19:55:19 -0400
commit1301b02c0d8c462810e55c39970d77535fbd1adc (patch)
tree33962fa581ccc47cac98967948552193bbbf1471 /lib
parentf8df02ff9eccb740c193c0af9707d5b9cc41dac0 (diff)
parent72a0a7384fb61daa2cc5ce4082cf1ff222831710 (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.py7
-rw-r--r--lib/stitches/auto_fill.py30
-rw-r--r--lib/stitches/circular_fill.py6
-rw-r--r--lib/stitches/guided_fill.py10
-rw-r--r--lib/stitches/running_stitch.py2
-rw-r--r--lib/utils/clamp_path.py21
-rw-r--r--lib/utils/smoothing.py10
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]