satin pattern and split stitch

pull/1254/head
Kaalleen 2021-06-22 20:04:39 +02:00
rodzic bf064b7169
commit 1adfa87a68
6 zmienionych plików z 161 dodań i 4 usunięć

Wyświetl plik

@ -12,7 +12,8 @@ from shapely import geometry as shgeo
from shapely.ops import nearest_points
from ..i18n import _
from ..svg import line_strings_to_csp, point_lists_to_csp
from ..svg import (PIXELS_PER_MM, apply_transforms, line_strings_to_csp,
point_lists_to_csp)
from ..utils import Point, cache, collapse_duplicate_point, cut
from .element import EmbroideryElement, Patch, param
from .validation import ValidationError, ValidationWarning
@ -74,12 +75,31 @@ class SatinColumn(EmbroideryElement):
def satin_column(self):
return self.get_boolean_param("satin_column")
# I18N: Split stitch divides a satin column into equal with parts if the maximum stitch length is exceeded
@property
@param('split_stitch',
_('Split stitch'),
tooltip=_('Sets additional stitches if the satin column exceeds the maximum stitch length.'),
type='boolean',
default='false')
def split_stitch(self):
return self.get_boolean_param("split_stitch")
# I18N: "E" stitch is so named because it looks like the letter E.
@property
@param('e_stitch', _('"E" stitch'), type='boolean', default='false')
def e_stitch(self):
return self.get_boolean_param("e_stitch")
@property
@param('max_stitch_length_mm',
_('Maximum stitch length'),
tooltip=_('Maximum stitch length for split stitches.'),
type='float', unit="mm",
default=12.1)
def max_stitch_length(self):
return max(self.get_float_param("max_stitch_length_mm", 12.4), 0.1 * PIXELS_PER_MM)
@property
def color(self):
return self.get_style("stroke")
@ -556,6 +576,20 @@ class SatinColumn(EmbroideryElement):
return SatinColumn(node)
def get_patterns(self):
xpath = ".//*[@inkstitch:pattern='%(id)s']" % dict(id=self.node.get('id'))
patterns = self.node.getroottree().getroot().xpath(xpath)
line_strings = []
for pattern in patterns:
d = pattern.get_path()
path = paths.Path(d).to_superpath()
path = apply_transforms(path, pattern)
path = self.flatten(path)
lines = [shgeo.LineString(p) for p in path]
for line in lines:
line_strings.append(line)
return shgeo.MultiLineString(line_strings)
@property
@cache
def center_line(self):
@ -787,6 +821,63 @@ class SatinColumn(EmbroideryElement):
return patch
def do_pattern_satin(self, patterns):
# elements with the attribute 'inkstitch:pattern' set to this elements id will cause extra stitches to be added
patch = Patch(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
for i, (left, right) in enumerate(zip(*sides)):
patch.add_stitch(left)
for point in self._get_pattern_points(left, right, patterns):
patch.add_stitch(point)
patch.add_stitch(right)
if not i+1 >= len(sides[0]):
for point in self._get_pattern_points(right, sides[0][i+1], patterns):
patch.add_stitch(point)
return patch
def do_split_stitch(self):
# stitches exceeding the maximum stitch length will be divided into equal parts through additional stitches
patch = Patch(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
for i, (left, right) in enumerate(zip(*sides)):
patch.add_stitch(left)
points, count = self._get_split_points(left, right)
for point in points:
patch.add_stitch(point)
patch.add_stitch(right)
# it is possible that the way back has a different length from the first
# but it looks ugly if the points differ too much
# so let's make sure they have at least the same amount of divisions
if not i+1 >= len(sides[0]):
points, count = self._get_split_points(right, sides[0][i+1], count)
for point in points:
patch.add_stitch(point)
return patch
def _get_pattern_points(self, left, right, patterns):
points = []
for pattern in patterns:
intersection = shgeo.LineString([left, right]).intersection(pattern)
if isinstance(intersection, shgeo.Point):
points.append(Point(intersection.x, intersection.y))
if isinstance(intersection, shgeo.MultiPoint):
for point in intersection:
points.append(Point(point.x, point.y))
# sort points after their distance to left
points.sort(key=lambda point: point.distance(left))
return points
def _get_split_points(self, left, right, count=None):
points = []
distance = left.distance(right)
split_count = count or int(distance / self.max_stitch_length)
for i in range(split_count):
line = shgeo.LineString((left, right))
split_point = line.interpolate((i+1)/split_count, normalized=True)
points.append(Point(split_point.x, split_point.y))
return [points, split_count]
def to_patches(self, last_patch):
# Stitch a variable-width satin column, zig-zagging between two paths.
@ -807,8 +898,13 @@ class SatinColumn(EmbroideryElement):
# zigzags sit on the contour walk underlay like rail ties on rails.
patch += self.do_zigzag_underlay()
patterns = self.get_patterns()
if self.e_stitch:
patch += self.do_e_stitch()
elif self.split_stitch:
patch += self.do_split_stitch()
elif self.get_patterns():
patch += self.do_pattern_satin(patterns)
else:
patch += self.do_satin()

Wyświetl plik

@ -5,6 +5,7 @@
from lib.extensions.troubleshoot import Troubleshoot
from .apply_satin_pattern import ApplySatinPattern
from .auto_satin import AutoSatin
from .break_apart import BreakApart
from .cleanup import Cleanup
@ -45,6 +46,7 @@ __all__ = extensions = [StitchPlanPreview,
GlobalCommands,
ConvertToSatin,
CutSatin,
ApplySatinPattern,
AutoSatin,
Lettering,
LetteringGenerateJson,

Wyświetl plik

@ -0,0 +1,39 @@
# Authors: see git history
#
# Copyright (c) 2021 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import inkex
from ..i18n import _
from ..svg.tags import INKSTITCH_ATTRIBS
from .base import InkstitchExtension
from ..elements import SatinColumn
class ApplySatinPattern(InkstitchExtension):
# Add inkstitch:pattern attribute to selected patterns. The patterns will be projected on a satin column, which must be in the selection too
def effect(self):
if not self.get_elements():
return
if not self.svg.selected or not any(isinstance(item, SatinColumn) for item in self.elements) or len(self.svg.selected) < 2:
inkex.errormsg(_("Please select at least one satin column and a pattern."))
return
if sum(isinstance(item, SatinColumn) for item in self.elements) > 1:
inkex.errormsg(_("Please select only one satin column."))
return
satin_id = self.get_satin_column().node.get('id', None)
patterns = self.get_patterns()
for pattern in patterns:
pattern.node.set(INKSTITCH_ATTRIBS['pattern'], satin_id)
def get_satin_column(self):
return list(filter(lambda satin: isinstance(satin, SatinColumn), self.elements))[0]
def get_patterns(self):
return list(filter(lambda satin: not isinstance(satin, SatinColumn), self.elements))

Wyświetl plik

@ -18,7 +18,8 @@ from ..elements.clone import is_clone
from ..i18n import _
from ..svg import generate_unique_id
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG)
INKSTITCH_ATTRIBS, NOT_EMBROIDERABLE_TAGS,
SVG_DEFS_TAG, SVG_GROUP_TAG)
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
@ -170,9 +171,9 @@ class InkstitchExtension(inkex.Effect):
if selected:
if node.tag == SVG_GROUP_TAG:
pass
elif getattr(node, "get_path", None):
elif (node.tag in EMBROIDERABLE_TAGS or is_clone(node)) and not node.get(INKSTITCH_ATTRIBS['pattern']):
nodes.append(node)
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
elif troubleshoot and node.tag in NOT_EMBROIDERABLE_TAGS:
nodes.append(node)
return nodes

Wyświetl plik

@ -86,6 +86,8 @@ inkstitch_attribs = [
'zigzag_underlay_inset_mm',
'zigzag_underlay_spacing_mm',
'e_stitch',
'pattern',
'split_stitch',
'pull_compensation_mm',
'stroke_first',
# Legacy

Wyświetl plik

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>{% trans %}Apply Satin Pattern{% endtrans %}</name>
<id>org.inkstitch.apply_satin_pattern.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">apply_satin_pattern</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch">
<submenu name="{% trans %}Satin Tools{% endtrans %}" />
</submenu>
</effects-menu>
</effect>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>