Merge pull request #2418 from inkstitch/lexelby/convert-to-satin-join

produce only one satin from convert to satin
pull/2461/head
Lex Neva 2023-07-30 10:49:25 -04:00 zatwierdzone przez GitHub
commit 9c09017094
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 93 dodań i 33 usunięć

Wyświetl plik

@ -713,6 +713,9 @@ class SatinColumn(EmbroideryElement):
Returns two new SatinColumn instances: the part before and the part
after the split point. All parameters are copied over to the new
SatinColumn instances.
The returned SatinColumns will not be in the SVG document and will have
their transforms applied.
"""
cut_points = self._find_cut_points(split_point)
@ -825,6 +828,43 @@ class SatinColumn(EmbroideryElement):
return SatinColumn(node)
def merge(self, satin):
"""Merge this satin with another satin
This method expects that the provided satin continues on directly after
this one, as would be the case, for example, if the two satins were the
result of the split() method.
Returns a new SatinColumn instance that combines the rails and rungs of
this satin and the provided satin. A rung is added at the end of this
satin.
The returned SatinColumn will not be in the SVG document and will have
its transforms applied.
"""
rails = [self.flatten_subpath(rail) for rail in self.rails]
other_rails = [satin.flatten_subpath(rail) for rail in satin.rails]
if len(rails) != 2 or len(other_rails) != 2:
# weird non-satin things, give up and don't merge
return self
# remove first node of each other rail before merging (avoid duplicated nodes)
rails[0].extend(other_rails[0][1:])
rails[1].extend(other_rails[1][1:])
rungs = [self.flatten_subpath(rung) for rung in self.rungs]
other_rungs = [satin.flatten_subpath(rung) for rung in satin.rungs]
# add a rung in between the two satins and extend it just a litte to ensure it is crossing the rails
new_rung = shgeo.LineString([other_rails[0][0], other_rails[1][0]])
rungs.append(list(shaffinity.scale(new_rung, 1.2, 1.2).coords))
# add on the other satin's rungs
rungs.extend(other_rungs)
return self._csp_to_satin(point_lists_to_csp(rails + rungs))
@property
@cache
def center_line(self):

Wyświetl plik

@ -13,7 +13,7 @@ from numpy import diff, setdiff1d, sign
from shapely import geometry as shgeo
from .base import InkstitchExtension
from ..elements import Stroke
from ..elements import SatinColumn, Stroke
from ..i18n import _
from ..svg import PIXELS_PER_MM, get_correction_transform
from ..svg.tags import INKSTITCH_ATTRIBS
@ -51,22 +51,28 @@ class ConvertToSatin(InkstitchExtension):
path_style = self.path_style(element)
for path in element.paths:
path = self.remove_duplicate_points(path)
path = self.remove_duplicate_points(self.fix_loop(path))
if len(path) < 2:
# ignore paths with just one point -- they're not visible to the user anyway
continue
for satin in self.convert_path_to_satins(path, element.stroke_width, style_args, correction_transform, path_style):
parent.insert(index, satin)
index += 1
satins = list(self.convert_path_to_satins(path, element.stroke_width, style_args, path_style))
if satins:
joined_satin = satins[0]
for satin in satins[1:]:
joined_satin = joined_satin.merge(satin)
joined_satin.node.set('transform', correction_transform)
parent.insert(index, joined_satin.node)
parent.remove(element.node)
def convert_path_to_satins(self, path, stroke_width, style_args, correction_transform, path_style, depth=0):
def convert_path_to_satins(self, path, stroke_width, style_args, path_style, depth=0):
try:
rails, rungs = self.path_to_satin(path, stroke_width, style_args)
yield self.satin_to_svg_node(rails, rungs, correction_transform, path_style)
yield SatinColumn(self.satin_to_svg_node(rails, rungs, path_style))
except SelfIntersectionError:
# The path intersects itself. Split it in two and try doing the halves
# individually.
@ -76,27 +82,37 @@ class ConvertToSatin(InkstitchExtension):
# getting nowhere. Just give up on this section of the path.
return
half = int(len(path) / 2.0)
halves = [path[:half + 1], path[half:]]
halves = self.split_path(path)
for path in halves:
for satin in self.convert_path_to_satins(path, stroke_width, style_args, correction_transform, path_style, depth=depth + 1):
for satin in self.convert_path_to_satins(path, stroke_width, style_args, path_style, depth=depth + 1):
yield satin
def split_path(self, path):
half = len(path) // 2
halves = [path[:half], path[half:]]
start = Point.from_tuple(halves[0][-1])
end = Point.from_tuple(halves[1][0])
midpoint = (start + end) / 2
midpoint = midpoint.as_tuple()
halves[0].append(midpoint)
halves[1] = [midpoint] + halves[1]
return halves
def fix_loop(self, path):
if path[0] == path[-1]:
# Looping paths seem to confuse shapely's parallel_offset(). It loses track
# of where the start and endpoint is, even if the user explicitly breaks the
# path. I suspect this is because parallel_offset() uses buffer() under the
# hood.
#
# To work around this we'll introduce a tiny gap by nudging the starting point
# toward the next point slightly.
start = Point(*path[0])
next = Point(*path[1])
direction = (next - start).unit()
start += 0.01 * direction
path[0] = start.as_tuple()
if path[0] == path[-1] and len(path) > 1:
first = Point.from_tuple(path[0])
second = Point.from_tuple(path[1])
midpoint = (first + second) / 2
midpoint = midpoint.as_tuple()
return [midpoint] + path[1:] + [path[0], midpoint]
else:
return path
def remove_duplicate_points(self, path):
path = [[round(coord, 4) for coord in point] for point in path]
@ -304,10 +320,8 @@ class ConvertToSatin(InkstitchExtension):
# Rotate 90 degrees left to make a normal vector.
normal = tangent.rotate_left()
# Travel 75% of the stroke width left and right to make the rung's
# endpoints. This means the rung's length is 150% of the stroke
# width.
offset = normal * stroke_width * 0.75
# Extend the rungs by an offset value to make sure they will cross the rails
offset = normal * (stroke_width / 2) * 1.2
rung_start = rung_center + offset
rung_end = rung_center - offset
@ -319,7 +333,7 @@ class ConvertToSatin(InkstitchExtension):
color = element.get_style('stroke', '#000000')
return "stroke:%s;stroke-width:1px;fill:none" % (color)
def satin_to_svg_node(self, rails, rungs, correction_transform, path_style):
def satin_to_svg_node(self, rails, rungs, path_style):
d = ""
for path in chain(rails, rungs):
d += "M"
@ -330,7 +344,6 @@ class ConvertToSatin(InkstitchExtension):
return inkex.PathElement(attrib={
"id": self.uniqueId("path"),
"style": path_style,
"transform": correction_transform,
"d": d,
INKSTITCH_ATTRIBS['satin_column']: "true",
})

Wyświetl plik

@ -53,11 +53,18 @@ def get_node_transform(node):
def get_correction_transform(node, child=False):
"""Get a transform to apply to new siblings or children of this SVG node"""
"""Get a transform to apply to new siblings or children of this SVG node
# if we want to place our new nodes in the same group/layer as this node,
# then we'll need to factor in the effects of any transforms set on
# the parents of this node.
Arguments:
child (boolean) -- whether the new nodes we're going to add will be
children of node (child=True) or siblings of node
(child=False)
This allows us to add a new child node that has its path specified in
absolute coordinates. The correction transform will undo the effects of
the parent's and ancestors' transforms so that absolute coordinates
work properly.
"""
if child:
transform = get_node_transform(node)