From 2e62ba7926a19e2952148c6ced3b6ebf1a96d78a Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Sat, 6 May 2023 18:31:54 +0200 Subject: [PATCH] Avoid duplicated points in make_spiral (#2268) * avoid duplicated points in make_spiral * circular fill: add end_row_spacing, repeats and bean repeats * fix circular fill if original shape is a circle --- lib/elements/fill_stitch.py | 10 ++++-- lib/elements/stroke.py | 5 +-- lib/stitches/circular_fill.py | 68 ++++++++++++++++++++++++++++++----- lib/stitches/contour_fill.py | 6 +++- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py index 9e2eed458..a2d4855f1 100644 --- a/lib/elements/fill_stitch.py +++ b/lib/elements/fill_stitch.py @@ -285,6 +285,7 @@ class FillStitch(EmbroideryElement): sort_index=24, type='float', select_items=[('fill_method', 'auto_fill'), + ('fill_method', 'circular_fill'), ('fill_method', 'legacy_fill')], default=None) def end_row_spacing(self): @@ -378,7 +379,8 @@ class FillStitch(EmbroideryElement): tooltip=_('Defines how many times to run down and back along the path.'), type='int', default="1", - select_items=[('fill_method', 'meander_fill')], + select_items=[('fill_method', 'meander_fill'), + ('fill_method', 'circular_fill')], sort_index=33) def repeats(self): return max(1, self.get_int_param("repeats", 1)) @@ -391,7 +393,8 @@ class FillStitch(EmbroideryElement): 'A value of 2 would quintuple each stitch, etc.\n\n' 'A pattern with various repeats can be created with a list of values separated by a space.'), type='str', - select_items=[('fill_method', 'meander_fill')], + select_items=[('fill_method', 'meander_fill'), + ('fill_method', 'circular_fill')], default=0, sort_index=34) def bean_stitch_repeats(self): @@ -922,9 +925,12 @@ class FillStitch(EmbroideryElement): shape, self.angle, self.row_spacing, + self.end_row_spacing, self.staggers, self.running_stitch_length, self.running_stitch_tolerance, + self.bean_stitch_repeats, + self.repeats, self.skip_last, starting_point, ending_point, diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index ba525c2d6..7ec6b76cf 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -188,9 +188,10 @@ class Stroke(EmbroideryElement): @property @param('staggers', - _('Stagger lines this many times before repeating. For linear ripples only.'), + _('Stagger lines this many times before repeating'), tooltip=_('Length of the cycle by which successive stitch lines are staggered. ' - 'Fractional values are allowed and can have less visible diagonals than integer values.'), + 'Fractional values are allowed and can have less visible diagonals than integer values. ' + 'For linear ripples only.'), type='int', select_items=[('stroke_method', 'ripple_stitch')], default=1, diff --git a/lib/stitches/circular_fill.py b/lib/stitches/circular_fill.py index 91943b90c..233e326eb 100644 --- a/lib/stitches/circular_fill.py +++ b/lib/stitches/circular_fill.py @@ -1,4 +1,5 @@ from shapely import geometry as shgeo +from shapely.ops import substring from ..stitch_plan import Stitch from ..utils.geometry import reverse_line_string @@ -6,15 +7,18 @@ from .auto_fill import (build_fill_stitch_graph, build_travel_graph, collapse_sequential_outline_edges, fallback, find_stitch_path, graph_is_valid, travel) from .contour_fill import _make_fermat_spiral -from .running_stitch import running_stitch +from .running_stitch import bean_stitch, running_stitch def circular_fill(shape, angle, row_spacing, + end_row_spacing, num_staggers, running_stitch_length, running_stitch_tolerance, + bean_stitch_repeats, + repeats, skip_last, starting_point, ending_point, @@ -24,16 +28,27 @@ def circular_fill(shape, # get furthest distance of the target point to a shape border # so we know how many circles we will need - distance = shape.hausdorff_distance(target) + 1 + distance = shape.hausdorff_distance(target) radius = row_spacing center = shgeo.Point(target) + if radius > distance: + # if the shape is smaller than row_spacing, return a simple circle in the size of row_spacing + stitches = running_stitch([Stitch(*point) for point in center.buffer(radius).exterior.coords], + running_stitch_length, running_stitch_tolerance) + return _apply_bean_stitch_and_repeats(stitches, repeats, bean_stitch_repeats) + circles = [] # add a small inner circle to make sure that the spiral ends close to the center circles.append(shgeo.LineString(center.buffer(0.1).exterior.coords)) - while distance > radius: + # add twice the size of the (end_)row_spacing to make sure we go big enough + stopp_at_distance = distance + (end_row_spacing or row_spacing) * 2 + while radius < stopp_at_distance: circles.append(shgeo.LineString(center.buffer(radius).exterior.coords)) - radius += row_spacing + if end_row_spacing: + radius += row_spacing + (end_row_spacing - row_spacing) * (radius / distance) + else: + radius += row_spacing circles.reverse() # Use double spiral from contour fill (we don't want to get stuck in the middle of the spiral) @@ -41,6 +56,13 @@ def circular_fill(shape, double_spiral = shgeo.LineString(list(double_spiral)) intersection = double_spiral.intersection(shape) + if isinstance(intersection, shgeo.LineString): + # if we get a single linestrig (original shape is a circle), apply start and end commands and return path + path = list(intersection.coords) + path = _apply_start_end_commands(shape, path, starting_point, ending_point) + stitches = running_stitch([Stitch(*point) for point in path], running_stitch_length, running_stitch_tolerance) + return _apply_bean_stitch_and_repeats(stitches, repeats, bean_stitch_repeats) + segments = [] for line in intersection.geoms: if isinstance(line, shgeo.LineString): @@ -55,11 +77,41 @@ def circular_fill(shape, result = path_to_stitches(path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last) # use running stitch to adjust the stitch length - result = running_stitch(result, - running_stitch_length, - running_stitch_tolerance) + result = running_stitch(result, running_stitch_length, running_stitch_tolerance) + return _apply_bean_stitch_and_repeats(result, repeats, bean_stitch_repeats) - return result + +def _apply_bean_stitch_and_repeats(stitches, repeats, bean_stitch_repeats): + if any(bean_stitch_repeats): + stitches = bean_stitch(stitches, bean_stitch_repeats) + + if repeats: + for i in range(1, repeats): + if i % 2 == 1: + # reverse every other pass + stitches.extend(stitches[::-1]) + else: + stitches.extend(stitches) + + return stitches + + +def _apply_start_end_commands(shape, path, starting_point, ending_point): + if starting_point or ending_point: + outline = shape.boundary + if starting_point: + start = _get_start_end_sequence(outline, shgeo.Point(*starting_point), shgeo.Point(*path[0])) + path = list(start.coords) + path + if ending_point: + end = _get_start_end_sequence(outline, shgeo.Point(*path[-1]), shgeo.Point(*ending_point)) + path.extend(list(end.coords)) + return path + + +def _get_start_end_sequence(outline, start, end): + start_dist = outline.project(start) + end_dist = outline.project(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): diff --git a/lib/stitches/contour_fill.py b/lib/stitches/contour_fill.py index f5f2a3ee5..2ea61afb8 100644 --- a/lib/stitches/contour_fill.py +++ b/lib/stitches/contour_fill.py @@ -574,6 +574,10 @@ def _make_spiral(rings, stitch_length, starting_point): check_stop_flag() spiral_part = _interpolate_linear_rings(ring1, ring2, stitch_length, starting_point) - path.extend(spiral_part.coords) + # skip last to avoid duplicated points + path.extend(spiral_part.coords[:-1]) + + # at the end add last point + path.append(spiral_part.coords[-1]) return path