kopia lustrzana https://github.com/inkstitch/inkstitch
satin pattern and split stitch
rodzic
bf064b7169
commit
1adfa87a68
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
Ładowanie…
Reference in New Issue