summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLex Neva <lexelby@users.noreply.github.com>2023-07-30 10:49:25 -0400
committerGitHub <noreply@github.com>2023-07-30 10:49:25 -0400
commit9c090170941e00a6c6265e3a6999ca240fb73052 (patch)
tree748e9bdda3acbf145c8ddb2ba7595be1975f6e56
parent96df68c46f495de0f63d36b5130e7dfdc773198a (diff)
parent74e93834c094351a398dd46d01c40d197f8fe9af (diff)
Merge pull request #2418 from inkstitch/lexelby/convert-to-satin-join
produce only one satin from convert to satin
-rw-r--r--lib/elements/satin_column.py40
-rw-r--r--lib/extensions/convert_to_satin.py71
-rw-r--r--lib/svg/path.py15
3 files changed, 93 insertions, 33 deletions
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index 86c6d05a..4ea26575 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -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):
diff --git a/lib/extensions/convert_to_satin.py b/lib/extensions/convert_to_satin.py
index 7a36ce21..4bb3588e 100644
--- a/lib/extensions/convert_to_satin.py
+++ b/lib/extensions/convert_to_satin.py
@@ -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",
})
diff --git a/lib/svg/path.py b/lib/svg/path.py
index 6c2cbe35..878d2a7c 100644
--- a/lib/svg/path.py
+++ b/lib/svg/path.py
@@ -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)