Split satins at stitch level (#3336)

pull/3344/head
Kaalleen 2024-12-14 16:49:10 +01:00 zatwierdzone przez GitHub
rodzic da5ab4fa78
commit 1abd305132
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
1 zmienionych plików z 156 dodań i 154 usunięć

Wyświetl plik

@ -877,7 +877,7 @@ class SatinColumn(EmbroideryElement):
ends. Finds corresponding point on the other rail (taking into account
the rungs) and breaks the rails at these points.
split_point can also be a noramlized projection of a distance along the
split_point can also be a normalized projection of a distance along the
satin, in the range 0.0 to 1.0.
Returns two new SatinColumn instances: the part before and the part
@ -1099,12 +1099,12 @@ class SatinColumn(EmbroideryElement):
@property
@cache
def offset_center_line(self):
stitches = self._get_center_line_stitches()
stitches = self._get_center_line_stitches(self.running_stitch_position)
linestring = shgeo.LineString(stitches)
return linestring
def _get_center_line_stitches(self):
inset_prop = -np.array([self.running_stitch_position, 100-self.running_stitch_position]) / 100
def _get_center_line_stitches(self, position):
inset_prop = -np.array([position, 100-position]) / 100
# Do it like contour underlay, but inset all the way to the center.
pairs = self.plot_points_on_rails(self.running_stitch_tolerance, (0, 0), inset_prop)
@ -1247,74 +1247,58 @@ class SatinColumn(EmbroideryElement):
return pairs
def do_start_path(self, satins, start_point):
def _connect_stitch_group_with_point(self, first_stitch_group, start_point, connect_to_satin_end=False):
start_stitch_group = StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay"),
stitches=[Stitch(*start_point)]
)
connector = self.offset_center_line
split_line = shgeo.LineString(self.find_cut_points(start_point))
start = connector.project(nearest_points(split_line, connector)[1])
if self.end_point is None:
end = 0
elif satins[0] is None:
if self._center_walk_is_odd():
end = 0
else:
end = connector.length
elif satins[1] is None:
if self._center_walk_is_odd():
end = connector.length
else:
end = 0
if connect_to_satin_end and not self._center_walk_is_odd():
end = connector.length
else:
if not self._center_walk_is_odd():
end = 0
else:
split_line = shgeo.LineString(self.find_cut_points(self.end_point))
end = connector.project(nearest_points(split_line, connector)[1])
split_line = shgeo.LineString(self.find_cut_points(first_stitch_group.stitches[0]))
end = connector.project(nearest_points(split_line, connector)[1])
start_path = substring(connector, start, end)
stitches = [Stitch(*coord) for coord in start_path.coords]
stitch_group = StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay"),
stitches=stitches
)
stitch_group = self.connect_and_add(start_stitch_group, stitch_group)
stitch_group.add_tags(("satin_column", "satin_column_underlay"))
return stitch_group
def do_end_point_connection(self):
if self._center_walk_is_odd():
return StitchGroup()
center_line = self.offset_center_line.reverse()
stitches = [Stitch(*coord) for coord in center_line.coords]
stitch_group = StitchGroup(
def do_end_path(self):
return StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay"),
stitches=stitches
tags=("satin_column",),
stitches=[Point(*self.end_point)]
)
return stitch_group
def _do_underlay_stitch_groups(self, i, satin, stitch_group):
def _do_underlay_stitch_groups(self):
stitch_groups = []
if self.center_walk_underlay:
center_walk = satin.do_center_walk()
stitch_group = self.connect_and_add(stitch_group, center_walk)
stitch_groups.extend(self.do_center_walk())
# if they just went one stitch back, it's not really necessary to add all the underlays
if i == 0 or satin.center_line.length > self.zigzag_spacing:
if self.contour_underlay:
contour = satin.do_contour_underlay()
stitch_group = self.connect_and_add(stitch_group, contour)
if self.contour_underlay:
stitch_groups.extend(self.do_contour_underlay())
if self.zigzag_underlay:
# zigzag underlay comes after contour walk underlay, so that the
# zigzags sit on the contour walk underlay like rail ties on rails.
zigzag = satin.do_zigzag_underlay()
stitch_group = self.connect_and_add(stitch_group, zigzag)
return stitch_group
if self.zigzag_underlay:
stitch_groups.extend(self.do_zigzag_underlay())
return stitch_groups
def _to_stitch_group(self, linestring, tags, reverse=False):
if reverse:
linestring = linestring.reverse()
return StitchGroup(
color=self.color,
tags=tags,
stitches=[Stitch(*coord) for coord in linestring.coords]
)
def do_contour_underlay(self):
# "contour walk" underlay: do stitches up one side and down the
@ -1341,6 +1325,19 @@ class SatinColumn(EmbroideryElement):
else:
second_side.reverse()
if self.end_point:
stitch_groups = []
tags = ("satin_column", "satin_column_underlay", "satin_contour_underlay")
first_linestring = shgeo.LineString(first_side)
first_start, first_end = self._split_linestring_at_end_point(first_linestring)
second_linestring = shgeo.LineString(second_side)
second_end, second_start = self._split_linestring_at_end_point(second_linestring)
stitch_groups.append(self._to_stitch_group(first_start, tags))
stitch_groups.append(self._to_stitch_group(second_end, tags))
stitch_groups.append(self._to_stitch_group(second_start, tags))
stitch_groups.append(self._to_stitch_group(first_end, tags))
return stitch_groups
stitch_group = StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay", "satin_contour_underlay"),
@ -1349,28 +1346,39 @@ class SatinColumn(EmbroideryElement):
self.add_running_stitches(first_side[-1], second_side[0], stitch_group)
stitch_group.stitches += second_side
return stitch_group
return [stitch_group]
def do_center_walk(self):
# Center walk underlay is just a running stitch down and back on the
# center line between the bezier curves.
repeats = self.center_walk_underlay_repeats
stitches = self._get_center_line_stitches()
stitch_groups = []
stitches = self._get_center_line_stitches(self.center_walk_underlay_position)
if self.end_point:
tags = ("satin_column", "satin_column_underlay", "satin_center_walk")
stitches = shgeo.LineString(stitches)
start, end = self._split_linestring_at_end_point(stitches)
if self._center_walk_is_odd():
end, start = start, end
stitch_groups.append(self._to_stitch_group(start, tags))
stitch_groups.append(self._to_stitch_group(end, tags, True))
else:
stitch_group = StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay", "satin_center_walk"),
stitches=stitches
)
stitch_groups.append(stitch_group)
repeated_stitches = []
for i in range(repeats - 1):
if i % 2 == 0:
repeated_stitches.extend(reversed(stitches))
else:
repeated_stitches.extend(stitches)
stitches.extend(repeated_stitches)
return StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay", "satin_center_walk"),
stitches=stitches
)
for stitch_group in stitch_groups:
stitch_count = len(stitch_group.stitches)
for i in range(repeats - 1):
if i % 2 == 0:
stitch_group.stitches += reversed(stitch_group.stitches[:stitch_count])
else:
stitch_group.stitches += stitch_group.stitches[:stitch_count]
return stitch_groups
def do_zigzag_underlay(self):
# zigzag underlay, usually done at a much lower density than the
@ -1383,7 +1391,7 @@ class SatinColumn(EmbroideryElement):
# "German underlay" described here:
# http://www.mrxstitch.com/underlay-what-lies-beneath-machine-embroidery/
stitch_group = StitchGroup(color=self.color)
stitch_groups = []
pairs = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0,
-self.zigzag_underlay_inset_px,
@ -1392,35 +1400,72 @@ class SatinColumn(EmbroideryElement):
if self._center_walk_is_odd():
pairs = list(reversed(pairs))
# This organizes the points in each side in the order that they'll be
# visited.
# take a points, from each side in turn, then go backed over the other points
points = [p[i % 2] for i, p in enumerate(pairs)] + list(reversed([p[i % 2] for i, p in enumerate(pairs, 1)]))
# This organizes the points in each side in the order that they'll be visited.
# take a point, from each side in turn, then go backed over the other points
point_groups = [p[i % 2] for i, p in enumerate(pairs)], list(reversed([p[i % 2] for i, p in enumerate(pairs, 1)]))
start_groups = []
end_groups = []
for points in point_groups:
if not self.end_point:
stitch_groups.append(self._generate_zigzag_stitch_group(points))
continue
zigzag_line = shgeo.LineString(points)
start, end = self._split_linestring_at_end_point(zigzag_line)
start_groups.append(self._generate_zigzag_stitch_group([Stitch(*point) for point in start.coords]))
end_groups.append(self._generate_zigzag_stitch_group([Stitch(*point) for point in end.coords]))
if start_groups:
stitch_groups.append(self.connect_and_add(start_groups[0], end_groups[-1]))
stitch_groups.append(self.connect_and_add(start_groups[-1], end_groups[0]))
return stitch_groups
def _generate_zigzag_stitch_group(self, points):
max_len = self.zigzag_underlay_max_stitch_length
last_point = None
stitch_group = StitchGroup(color=self.color)
for point in points:
if last_point and max_len:
if last_point.distance(point) > max_len:
split_points = running_stitch.split_segment_even_dist(last_point, point, max_len)
for p in split_points:
stitch_group.add_stitch(p)
stitch_group.add_stitch(p, ("split_stitch",))
last_point = point
stitch_group.add_stitch(point)
stitch_group.add_stitch(point, ("edge",))
stitch_group.add_tags(("satin_column", "satin_column_underlay", "satin_zigzag_underlay"))
return stitch_group
def _do_top_layer_stitch_group(self, satin):
def _do_top_layer_stitch_group(self):
if self.satin_method == 'e_stitch':
stitch_group = satin.do_e_stitch()
stitch_group = self.do_e_stitch()
elif self.satin_method == 's_stitch':
stitch_group = satin.do_s_stitch()
stitch_group = self.do_s_stitch()
elif self.satin_method == 'zigzag':
stitch_group = satin.do_zigzag()
stitch_group = self.do_zigzag()
else:
stitch_group = satin.do_satin()
return stitch_group
stitch_group = self.do_satin()
if self.end_point:
return self._split_top_layer(stitch_group)
return [stitch_group]
def _split_linestring_at_end_point(self, linestring):
split_line = shgeo.LineString(self.find_cut_points(self.end_point))
split_point = nearest_points(linestring, split_line)[0]
project = linestring.project(split_point)
start = substring(linestring, 0, project)
end = substring(linestring, project, linestring.length)
return start, end
def _split_top_layer(self, stitch_group):
top_layer = shgeo.LineString(stitch_group.stitches)
start, end = self._split_linestring_at_end_point(top_layer)
stitch_group2 = deepcopy(stitch_group)
stitch_group2.stitches = [Stitch(*point) for point in end.reverse().coords]
stitch_group1 = stitch_group
stitch_group1.stitches = [Stitch(*point) for point in start.coords]
top_layer_stitch_groups = [stitch_group1, stitch_group2]
return top_layer_stitch_groups
def do_satin(self):
# satin: do a zigzag pattern, alternating between the paths. The
@ -1750,54 +1795,14 @@ class SatinColumn(EmbroideryElement):
point = point.target_point
return point
def _split_satin(self):
if self.end_point is not None:
satins = self.split(self.end_point)
if self.reverse_rails == 'automatic':
self._adapt_automatic_rail_swapping(satins)
if satins[0] is None:
if not self._center_walk_is_odd():
satins[1] = satins[1].reverse()
if self.swap_rails:
satins[1] = satins[1].flip()
satins = [None, satins[1]]
elif satins[1] is not None:
if self._center_walk_is_odd():
satins[0] = satins[0].reverse()
else:
satins[1] = satins[1].reverse()
if self.swap_rails:
satins[0] = satins[0].flip()
else:
if self._center_walk_is_odd():
satins[0] = satins[0].reverse()
satins = [satins[0], None]
else:
satins = [self, None]
return satins
def _adapt_automatic_rail_swapping(self, satins): # noqa: C901
reverse_rails = self._get_rails_to_reverse()
if reverse_rails == (False, False):
if satins[0] is not None:
satins[0].set_param('reverse_rails', 'none')
if satins[1] is not None:
satins[1].set_param('reverse_rails', 'none')
elif reverse_rails == (True, False):
if satins[0] is not None:
satins[0].set_param('reverse_rails', 'first')
if satins[1] is not None:
satins[1].set_param('reverse_rails', 'first')
elif reverse_rails == (False, True):
if satins[0] is not None:
satins[0].set_param('reverse_rails', 'second')
if satins[1] is not None:
satins[1].set_param('reverse_rails', 'second')
elif reverse_rails == (True, True):
if satins[0] is not None:
satins[0].set_param('reverse_rails', 'both')
if satins[1] is not None:
satins[1].set_param('reverse_rails', 'both')
def _sort_stitch_groups(self, stitch_groups):
if self.end_point:
ordered_stitch_groups = []
ordered_stitch_groups.extend(stitch_groups[::2])
ordered_stitch_groups.append(self._connect_stitch_group_with_point(stitch_groups[1], ordered_stitch_groups[-1].stitches[-1], True))
ordered_stitch_groups.extend(stitch_groups[1::2])
return ordered_stitch_groups
return stitch_groups
def to_stitch_groups(self, last_stitch_group=None):
# Stitch a variable-width satin column, zig-zagging between two paths.
@ -1805,45 +1810,42 @@ class SatinColumn(EmbroideryElement):
# beziers. The boundary points between beziers serve as "checkpoints",
# allowing the user to control how the zigzags flow around corners.
satins = self._split_satin()
stitch_groups = []
start_point = self.start_point
if start_point is None and last_stitch_group is not None and self.start_at_nearest_point:
start_point = nearest_points(shgeo.Point(*last_stitch_group.stitches[-1]), self.center_line)[1]
start_point = Point(*list(start_point.coords[0]))
# underlays
stitch_groups.extend(self._do_underlay_stitch_groups())
# top layer
stitch_groups.extend(self._do_top_layer_stitch_group())
# order stitch groups
stitch_groups = self._sort_stitch_groups(stitch_groups)
# start and end
if start_point is not None:
stitch_groups = [self._connect_stitch_group_with_point(stitch_groups[0], start_point)] + stitch_groups
if self.end_point:
stitch_groups.append(self.do_end_path())
# assemble stitch groups
stitch_group = StitchGroup(
color=self.color,
force_lock_stitches=self.force_lock_stitches,
lock_stitches=self.lock_stitches
)
start_point = self.start_point
if start_point is None and last_stitch_group is not None and self.start_at_nearest_point:
start_point = nearest_points(shgeo.Point(*last_stitch_group.stitches[-1]), self.center_line)[1]
start_point = Point(*list(start_point.coords[0]))
if start_point is not None:
start_path = self.do_start_path(satins, start_point)
stitch_group = self.connect_and_add(stitch_group, start_path)
for i, satin in enumerate(satins):
if satin is None:
continue
if i > 0 and None not in satins:
end_point_connection = satin.do_end_point_connection()
stitch_group = self.connect_and_add(stitch_group, end_point_connection)
stitch_group = self._do_underlay_stitch_groups(i, satin, stitch_group)
final_stitch_group = self._do_top_layer_stitch_group(satin)
stitch_group = self.connect_and_add(stitch_group, final_stitch_group)
for satin_layer in stitch_groups:
if satin_layer and satin_layer.stitches:
stitch_group = self.connect_and_add(stitch_group, satin_layer)
if not stitch_group.stitches:
return []
if self.end_point:
ending_point_stitch_group = StitchGroup(
color=self.color,
tags=("satin_column"),
stitches=[Point(*self.end_point)]
)
stitch_group = self.connect_and_add(stitch_group, ending_point_stitch_group)
return [stitch_group]