From 1adfa87a68be6bcc92d9521b97ab59dc022ab3be Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Tue, 22 Jun 2021 20:04:39 +0200 Subject: satin pattern and split stitch --- lib/elements/satin_column.py | 98 ++++++++++++++++++++++++++++++++++- lib/extensions/__init__.py | 2 + lib/extensions/apply_satin_pattern.py | 39 ++++++++++++++ lib/extensions/base.py | 7 +-- lib/svg/tags.py | 2 + 5 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 lib/extensions/apply_satin_pattern.py (limited to 'lib') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 778fc88a..3f5f05e5 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -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() diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 25f835c3..70df7c37 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -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, diff --git a/lib/extensions/apply_satin_pattern.py b/lib/extensions/apply_satin_pattern.py new file mode 100644 index 00000000..9da81075 --- /dev/null +++ b/lib/extensions/apply_satin_pattern.py @@ -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)) diff --git a/lib/extensions/base.py b/lib/extensions/base.py index 70ca4701..f23ec5e2 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -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 diff --git a/lib/svg/tags.py b/lib/svg/tags.py index 5c1d892a..c8e9b67e 100644 --- a/lib/svg/tags.py +++ b/lib/svg/tags.py @@ -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 -- cgit v1.2.3 From d6df8084f4a0fe8c8e174ea230d158512bd8f094 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Thu, 24 Jun 2021 22:25:13 +0200 Subject: add start markers, add troubleshoot pattern warning and fix wxpython language issue --- lib/elements/pattern.py | 32 +++++++++++++++++++++++++ lib/elements/utils.py | 8 +++++-- lib/extensions/apply_satin_pattern.py | 44 +++++++++++++++++++++++++++++++++-- lib/extensions/base.py | 2 +- lib/extensions/lettering.py | 4 +++- lib/extensions/params.py | 4 +++- 6 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 lib/elements/pattern.py (limited to 'lib') diff --git a/lib/elements/pattern.py b/lib/elements/pattern.py new file mode 100644 index 00000000..98f29456 --- /dev/null +++ b/lib/elements/pattern.py @@ -0,0 +1,32 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +import inkex + +from ..i18n import _ +from .element import EmbroideryElement +from .validation import ObjectTypeWarning + + +class PatternWarning(ObjectTypeWarning): + name = _("Pattern Element") + description = _("This element will only be stitched out as a pattern within the specified object.") + steps_to_solve = [ + _("If you want to remove the pattern configuration for a pattern object follow these steps:"), + _("* Select pattern element(s)"), + _('* Run Extensions > Ink/Stitch > Troubleshoot > Remove embroidery settings...'), + _('* Make sure "Remove params" is enables'), + _('* Click "Apply"') + ] + + +class PatternObject(EmbroideryElement): + + def validation_warnings(self): + repr_point = next(inkex.Path(self.parse_path()).end_points) + yield PatternWarning(repr_point) + + def to_patches(self, last_patch): + return [] diff --git a/lib/elements/utils.py b/lib/elements/utils.py index aceab485..03ea48d4 100644 --- a/lib/elements/utils.py +++ b/lib/elements/utils.py @@ -4,14 +4,15 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. from ..commands import is_command -from ..svg.tags import (EMBROIDERABLE_TAGS, SVG_IMAGE_TAG, SVG_PATH_TAG, - SVG_POLYLINE_TAG, SVG_TEXT_TAG) +from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_IMAGE_TAG, + SVG_PATH_TAG, SVG_POLYLINE_TAG, SVG_TEXT_TAG) from .auto_fill import AutoFill from .clone import Clone, is_clone from .element import EmbroideryElement from .empty_d_object import EmptyDObject from .fill import Fill from .image import ImageObject +from .pattern import PatternObject from .polyline import Polyline from .satin_column import SatinColumn from .stroke import Stroke @@ -28,6 +29,9 @@ def node_to_elements(node): # noqa: C901 elif node.tag == SVG_PATH_TAG and not node.get('d', ''): return [EmptyDObject(node)] + elif node.get(INKSTITCH_ATTRIBS['pattern']): + return [PatternObject(node)] + elif node.tag in EMBROIDERABLE_TAGS: element = EmbroideryElement(node) diff --git a/lib/extensions/apply_satin_pattern.py b/lib/extensions/apply_satin_pattern.py index 9da81075..47eb4d83 100644 --- a/lib/extensions/apply_satin_pattern.py +++ b/lib/extensions/apply_satin_pattern.py @@ -4,11 +4,12 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. import inkex +from lxml import etree +from ..elements import SatinColumn from ..i18n import _ -from ..svg.tags import INKSTITCH_ATTRIBS +from ..svg.tags import INKSTITCH_ATTRIBS, SVG_DEFS_TAG from .base import InkstitchExtension -from ..elements import SatinColumn class ApplySatinPattern(InkstitchExtension): @@ -31,9 +32,48 @@ class ApplySatinPattern(InkstitchExtension): for pattern in patterns: pattern.node.set(INKSTITCH_ATTRIBS['pattern'], satin_id) + self.set_marker(pattern.node) 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)) + + def set_marker(self, node): + document = node.getroottree().getroot() + xpath = ".//marker[@id='inkstitch-pattern-marker']" + pattern_marker = document.xpath(xpath) + if not pattern_marker: + # get or create def element + defs = document.find(SVG_DEFS_TAG) + if defs is None: + defs = etree.SubElement(document, SVG_DEFS_TAG) + + # insert marker + marker = """ + + + + + """ # noqa: E501 + defs.append(etree.fromstring(marker)) + + # attach marker to node + style = node.get('style', '').split(";") + import sys + print(style, file=sys.stderr) + style = [i for i in style if not i.startswith('marker-start')] + style.append('marker-start:url(#inkstitch-pattern-marker)') + node.set('style', ";".join(style)) diff --git a/lib/extensions/base.py b/lib/extensions/base.py index f23ec5e2..00d4a00d 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -173,7 +173,7 @@ class InkstitchExtension(inkex.Effect): pass 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: + elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.get(INKSTITCH_ATTRIBS['pattern'])): nodes.append(node) return nodes diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py index e55365c6..cf627fe5 100644 --- a/lib/extensions/lettering.py +++ b/lib/extensions/lettering.py @@ -31,9 +31,11 @@ class LetteringFrame(wx.Frame): def __init__(self, *args, **kwargs): # This is necessary because of https://github.com/inkstitch/inkstitch/issues/1186 - if sys.platform.startswith('win'): + if sys.platform.startswith('win32'): import locale locale.setlocale(locale.LC_ALL, "C") + lc = wx.Locale() + lc.Init(wx.LANGUAGE_DEFAULT) # begin wxGlade: MyFrame.__init__ self.group = kwargs.pop('group') diff --git a/lib/extensions/params.py b/lib/extensions/params.py index 40494ec7..7775aed1 100644 --- a/lib/extensions/params.py +++ b/lib/extensions/params.py @@ -331,9 +331,11 @@ class ParamsTab(ScrolledPanel): class SettingsFrame(wx.Frame): def __init__(self, *args, **kwargs): # This is necessary because of https://github.com/inkstitch/inkstitch/issues/1186 - if sys.platform.startswith('win'): + if sys.platform.startswith('win32'): import locale locale.setlocale(locale.LC_ALL, "C") + lc = wx.Locale() + lc.Init(wx.LANGUAGE_DEFAULT) # begin wxGlade: MyFrame.__init__ self.tabs_factory = kwargs.pop('tabs_factory', []) -- cgit v1.2.3 From c602c4c517cab40dfc2dc7dbc5c29c037cccafae Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Sun, 27 Jun 2021 22:29:57 +0200 Subject: group patterns --- lib/commands.py | 11 ++++- lib/elements/satin_column.py | 10 +++-- lib/elements/utils.py | 4 +- lib/extensions/__init__.py | 2 - lib/extensions/apply_satin_pattern.py | 79 ----------------------------------- lib/extensions/base.py | 9 ++-- 6 files changed, 26 insertions(+), 89 deletions(-) delete mode 100644 lib/extensions/apply_satin_pattern.py (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index f2ab8c3e..ea6d3509 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -46,10 +46,12 @@ COMMANDS = { # L10N command attached to an object "satin_cut_point": N_("Satin cut point (use with Cut Satin Column)"), - # L10N command that affects a layer "ignore_layer": N_("Ignore layer (do not stitch any objects in this layer)"), + # L10N command that affects a group + "pattern_group": N_("Strokes in this group will be interpretet as a pattern"), + # L10N command that affects entire document "origin": N_("Origin for exported embroidery files"), @@ -58,6 +60,7 @@ COMMANDS = { } OBJECT_COMMANDS = ["fill_start", "fill_end", "satin_start", "satin_end", "stop", "trim", "ignore_object", "satin_cut_point"] +GROUP_COMMANDS = ["pattern_group"] LAYER_COMMANDS = ["ignore_layer"] GLOBAL_COMMANDS = ["origin", "stop_position"] @@ -184,6 +187,12 @@ def find_commands(node): return commands +def group_commands(node, command): + xpath = "./ancestor::svg:g/svg:use[@xlink:href='#inkstitch_%(command)s']" % dict(id=node.get('id'), command=command) + group_command = node.xpath(xpath, namespaces=inkex.NSS) + return group_command + + def layer_commands(layer, command): """Find standalone (unconnected) command symbols in this layer.""" diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 3f5f05e5..43948e42 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -6,7 +6,7 @@ from copy import deepcopy from itertools import chain -from inkex import paths +from inkex import paths, NSS from shapely import affinity as shaffinity from shapely import geometry as shgeo from shapely.ops import nearest_points @@ -14,6 +14,7 @@ from shapely.ops import nearest_points from ..i18n import _ from ..svg import (PIXELS_PER_MM, apply_transforms, line_strings_to_csp, point_lists_to_csp) +from ..svg.tags import EMBROIDERABLE_TAGS from ..utils import Point, cache, collapse_duplicate_point, cut from .element import EmbroideryElement, Patch, param from .validation import ValidationError, ValidationWarning @@ -577,10 +578,13 @@ 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) + xpath = "./ancestor::svg:g[svg:use[@xlink:href='#inkstitch_pattern_group']]//*[not(@inkstitch:satin_column='true')]" + patterns = self.node.xpath(xpath, namespaces=NSS) line_strings = [] for pattern in patterns: + # TODO: exclude fills in case we will want to use them with the pattern too + if pattern.tag not in EMBROIDERABLE_TAGS: + continue d = pattern.get_path() path = paths.Path(d).to_superpath() path = apply_transforms(path, pattern) diff --git a/lib/elements/utils.py b/lib/elements/utils.py index 03ea48d4..cbac3d40 100644 --- a/lib/elements/utils.py +++ b/lib/elements/utils.py @@ -17,6 +17,7 @@ from .polyline import Polyline from .satin_column import SatinColumn from .stroke import Stroke from .text import TextObject +from ..commands import group_commands def node_to_elements(node): # noqa: C901 @@ -29,7 +30,8 @@ def node_to_elements(node): # noqa: C901 elif node.tag == SVG_PATH_TAG and not node.get('d', ''): return [EmptyDObject(node)] - elif node.get(INKSTITCH_ATTRIBS['pattern']): + # TODO: exclude fills + elif group_commands(node, 'pattern_group') and not node.get(INKSTITCH_ATTRIBS['satin_column']): return [PatternObject(node)] elif node.tag in EMBROIDERABLE_TAGS: diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 70df7c37..25f835c3 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -5,7 +5,6 @@ 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 @@ -46,7 +45,6 @@ __all__ = extensions = [StitchPlanPreview, GlobalCommands, ConvertToSatin, CutSatin, - ApplySatinPattern, AutoSatin, Lettering, LetteringGenerateJson, diff --git a/lib/extensions/apply_satin_pattern.py b/lib/extensions/apply_satin_pattern.py deleted file mode 100644 index 47eb4d83..00000000 --- a/lib/extensions/apply_satin_pattern.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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 lxml import etree - -from ..elements import SatinColumn -from ..i18n import _ -from ..svg.tags import INKSTITCH_ATTRIBS, SVG_DEFS_TAG -from .base import InkstitchExtension - - -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) - self.set_marker(pattern.node) - - 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)) - - def set_marker(self, node): - document = node.getroottree().getroot() - xpath = ".//marker[@id='inkstitch-pattern-marker']" - pattern_marker = document.xpath(xpath) - if not pattern_marker: - # get or create def element - defs = document.find(SVG_DEFS_TAG) - if defs is None: - defs = etree.SubElement(document, SVG_DEFS_TAG) - - # insert marker - marker = """ - - - - - """ # noqa: E501 - defs.append(etree.fromstring(marker)) - - # attach marker to node - style = node.get('style', '').split(";") - import sys - print(style, file=sys.stderr) - style = [i for i in style if not i.startswith('marker-start')] - style.append('marker-start:url(#inkstitch-pattern-marker)') - node.set('style', ";".join(style)) diff --git a/lib/extensions/base.py b/lib/extensions/base.py index 00d4a00d..ce5f8b1d 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -12,7 +12,7 @@ import inkex from lxml import etree from stringcase import snakecase -from ..commands import is_command, layer_commands +from ..commands import is_command, layer_commands, group_commands from ..elements import EmbroideryElement, nodes_to_elements from ..elements.clone import is_clone from ..i18n import _ @@ -171,9 +171,12 @@ class InkstitchExtension(inkex.Effect): if selected: if node.tag == SVG_GROUP_TAG: pass - elif (node.tag in EMBROIDERABLE_TAGS or is_clone(node)) and not node.get(INKSTITCH_ATTRIBS['pattern']): + elif ((node.tag in EMBROIDERABLE_TAGS or is_clone(node)) and not + (len(list(group_commands(node, 'pattern_group'))) and not node.get(INKSTITCH_ATTRIBS['satin_column']))): nodes.append(node) - elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.get(INKSTITCH_ATTRIBS['pattern'])): + # add images, text and patterns for the troubleshoot extension + elif (troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or + (len(list(group_commands(node, 'pattern_group'))) and not node.get(INKSTITCH_ATTRIBS['satin_column'])))): nodes.append(node) return nodes -- cgit v1.2.3 From 2f54ff2a436f2774bfdc730b6e95c43f18ed81ac Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Sun, 27 Jun 2021 22:47:43 +0200 Subject: group command extension --- lib/elements/satin_column.py | 2 +- lib/elements/utils.py | 3 +-- lib/extensions/__init__.py | 2 ++ lib/extensions/base.py | 2 +- lib/extensions/group_commands.py | 41 ++++++++++++++++++++++++++++++++++++++++ lib/inx/extensions.py | 9 +++++++-- 6 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 lib/extensions/group_commands.py (limited to 'lib') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 43948e42..65e523d4 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -6,7 +6,7 @@ from copy import deepcopy from itertools import chain -from inkex import paths, NSS +from inkex import NSS, paths from shapely import affinity as shaffinity from shapely import geometry as shgeo from shapely.ops import nearest_points diff --git a/lib/elements/utils.py b/lib/elements/utils.py index cbac3d40..78dace6a 100644 --- a/lib/elements/utils.py +++ b/lib/elements/utils.py @@ -3,7 +3,7 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -from ..commands import is_command +from ..commands import group_commands, is_command from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_IMAGE_TAG, SVG_PATH_TAG, SVG_POLYLINE_TAG, SVG_TEXT_TAG) from .auto_fill import AutoFill @@ -17,7 +17,6 @@ from .polyline import Polyline from .satin_column import SatinColumn from .stroke import Stroke from .text import TextObject -from ..commands import group_commands def node_to_elements(node): # noqa: C901 diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 25f835c3..a6d25d2c 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -14,6 +14,7 @@ from .duplicate_params import DuplicateParams from .embroider_settings import EmbroiderSettings from .flip import Flip from .global_commands import GlobalCommands +from .group_commands import GroupCommands from .import_threadlist import ImportThreadlist from .input import Input from .install import Install @@ -41,6 +42,7 @@ __all__ = extensions = [StitchPlanPreview, Zip, Flip, ObjectCommands, + GroupCommands, LayerCommands, GlobalCommands, ConvertToSatin, diff --git a/lib/extensions/base.py b/lib/extensions/base.py index ce5f8b1d..353e433c 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -12,7 +12,7 @@ import inkex from lxml import etree from stringcase import snakecase -from ..commands import is_command, layer_commands, group_commands +from ..commands import group_commands, is_command, layer_commands from ..elements import EmbroideryElement, nodes_to_elements from ..elements.clone import is_clone from ..i18n import _ diff --git a/lib/extensions/group_commands.py b/lib/extensions/group_commands.py new file mode 100644 index 00000000..af1f9fb1 --- /dev/null +++ b/lib/extensions/group_commands.py @@ -0,0 +1,41 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +import inkex +from lxml import etree + +from ..commands import GROUP_COMMANDS, ensure_symbol, get_command_description +from ..i18n import _ +from ..svg import get_correction_transform +from ..svg.tags import INKSCAPE_LABEL, SVG_USE_TAG, XLINK_HREF +from .commands import CommandsExtension + + +class GroupCommands(CommandsExtension): + COMMANDS = GROUP_COMMANDS + + def effect(self): + commands = [command for command in self.COMMANDS if getattr(self.options, command)] + + if not commands: + inkex.errormsg(_("Please choose one or more commands to add.")) + return + + correction_transform = get_correction_transform(self.svg.get_current_layer(), child=True) + + for i, command in enumerate(commands): + ensure_symbol(self.document, command) + + etree.SubElement(self.svg.get_current_layer(), SVG_USE_TAG, + { + "id": self.uniqueId("use"), + INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command), + XLINK_HREF: "#inkstitch_%s" % command, + "height": "100%", + "width": "100%", + "x": str(i * 20), + "y": "-10", + "transform": correction_transform + }) diff --git a/lib/inx/extensions.py b/lib/inx/extensions.py index 9a197c5d..8ca0addc 100755 --- a/lib/inx/extensions.py +++ b/lib/inx/extensions.py @@ -5,8 +5,8 @@ import pyembroidery -from ..commands import (COMMANDS, GLOBAL_COMMANDS, LAYER_COMMANDS, - OBJECT_COMMANDS) +from ..commands import (COMMANDS, GLOBAL_COMMANDS, GROUP_COMMANDS, + LAYER_COMMANDS, OBJECT_COMMANDS) from ..extensions import Input, Output, extensions from ..threads import ThreadCatalog from .outputs import pyembroidery_output_formats @@ -24,6 +24,10 @@ def global_commands(): return [(command, COMMANDS[command]) for command in GLOBAL_COMMANDS] +def group_commands(): + return [(command, COMMANDS[command]) for command in GROUP_COMMANDS] + + def object_commands(): return [(command, COMMANDS[command]) for command in OBJECT_COMMANDS] @@ -51,6 +55,7 @@ def generate_extension_inx_files(): write_inx_file(name, template.render(formats=pyembroidery_output_formats(), debug_formats=pyembroidery_debug_formats(), threadcatalog=threadcatalog(), + group_commands=group_commands(), layer_commands=layer_commands(), object_commands=object_commands(), global_commands=global_commands())) -- cgit v1.2.3 From ecacb9829e9c2b7050486707211f9d176aafdf75 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Mon, 28 Jun 2021 20:05:50 +0200 Subject: pattern markers --- lib/commands.py | 7 ----- lib/elements/pattern.py | 4 +++ lib/elements/satin_column.py | 7 +++-- lib/elements/utils.py | 11 ++++--- lib/extensions/__init__.py | 4 +-- lib/extensions/apply_pattern.py | 62 ++++++++++++++++++++++++++++++++++++++++ lib/extensions/base.py | 12 ++++---- lib/extensions/group_commands.py | 41 -------------------------- lib/inx/extensions.py | 9 ++---- 9 files changed, 85 insertions(+), 72 deletions(-) create mode 100644 lib/extensions/apply_pattern.py delete mode 100644 lib/extensions/group_commands.py (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index ea6d3509..cb2a74d5 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -60,7 +60,6 @@ COMMANDS = { } OBJECT_COMMANDS = ["fill_start", "fill_end", "satin_start", "satin_end", "stop", "trim", "ignore_object", "satin_cut_point"] -GROUP_COMMANDS = ["pattern_group"] LAYER_COMMANDS = ["ignore_layer"] GLOBAL_COMMANDS = ["origin", "stop_position"] @@ -187,12 +186,6 @@ def find_commands(node): return commands -def group_commands(node, command): - xpath = "./ancestor::svg:g/svg:use[@xlink:href='#inkstitch_%(command)s']" % dict(id=node.get('id'), command=command) - group_command = node.xpath(xpath, namespaces=inkex.NSS) - return group_command - - def layer_commands(layer, command): """Find standalone (unconnected) command symbols in this layer.""" diff --git a/lib/elements/pattern.py b/lib/elements/pattern.py index 98f29456..c66ffbdc 100644 --- a/lib/elements/pattern.py +++ b/lib/elements/pattern.py @@ -30,3 +30,7 @@ class PatternObject(EmbroideryElement): def to_patches(self, last_patch): return [] + + +def is_pattern(node): + return "marker-start:url(#inkstitch-pattern-marker)" in node.get('style', '') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 65e523d4..77cb7d22 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -578,11 +578,14 @@ class SatinColumn(EmbroideryElement): return SatinColumn(node) def get_patterns(self): - xpath = "./ancestor::svg:g[svg:use[@xlink:href='#inkstitch_pattern_group']]//*[not(@inkstitch:satin_column='true')]" + # TODO: which one is better?!? + # All child groups of pattern + # xpath = "./ancestor::svg:g//*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" + # Only direct siblings of pattern + xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" patterns = self.node.xpath(xpath, namespaces=NSS) line_strings = [] for pattern in patterns: - # TODO: exclude fills in case we will want to use them with the pattern too if pattern.tag not in EMBROIDERABLE_TAGS: continue d = pattern.get_path() diff --git a/lib/elements/utils.py b/lib/elements/utils.py index 78dace6a..cd87cec8 100644 --- a/lib/elements/utils.py +++ b/lib/elements/utils.py @@ -3,16 +3,16 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -from ..commands import group_commands, is_command -from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_IMAGE_TAG, - SVG_PATH_TAG, SVG_POLYLINE_TAG, SVG_TEXT_TAG) +from ..commands import is_command +from ..svg.tags import (EMBROIDERABLE_TAGS, SVG_IMAGE_TAG, SVG_PATH_TAG, + SVG_POLYLINE_TAG, SVG_TEXT_TAG) from .auto_fill import AutoFill from .clone import Clone, is_clone from .element import EmbroideryElement from .empty_d_object import EmptyDObject from .fill import Fill from .image import ImageObject -from .pattern import PatternObject +from .pattern import PatternObject, is_pattern from .polyline import Polyline from .satin_column import SatinColumn from .stroke import Stroke @@ -29,8 +29,7 @@ def node_to_elements(node): # noqa: C901 elif node.tag == SVG_PATH_TAG and not node.get('d', ''): return [EmptyDObject(node)] - # TODO: exclude fills - elif group_commands(node, 'pattern_group') and not node.get(INKSTITCH_ATTRIBS['satin_column']): + elif is_pattern(node): return [PatternObject(node)] elif node.tag in EMBROIDERABLE_TAGS: diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index a6d25d2c..3bd2fef6 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -5,6 +5,7 @@ from lib.extensions.troubleshoot import Troubleshoot +from .apply_pattern import ApplyPattern from .auto_satin import AutoSatin from .break_apart import BreakApart from .cleanup import Cleanup @@ -14,7 +15,6 @@ from .duplicate_params import DuplicateParams from .embroider_settings import EmbroiderSettings from .flip import Flip from .global_commands import GlobalCommands -from .group_commands import GroupCommands from .import_threadlist import ImportThreadlist from .input import Input from .install import Install @@ -41,8 +41,8 @@ __all__ = extensions = [StitchPlanPreview, Output, Zip, Flip, + ApplyPattern, ObjectCommands, - GroupCommands, LayerCommands, GlobalCommands, ConvertToSatin, diff --git a/lib/extensions/apply_pattern.py b/lib/extensions/apply_pattern.py new file mode 100644 index 00000000..ad881604 --- /dev/null +++ b/lib/extensions/apply_pattern.py @@ -0,0 +1,62 @@ +# 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 lxml import etree + +from ..i18n import _ +from ..svg.tags import SVG_DEFS_TAG +from .base import InkstitchExtension + + +class ApplyPattern(InkstitchExtension): + # This extension will mark selected + + def effect(self): + if not self.get_elements(): + return + + if not self.svg.selected: + inkex.errormsg(_("Please select at least one object to be marked as a pattern.")) + return + + for pattern in self.svg.selected.values(): + self.set_marker(pattern) + + def set_marker(self, node): + xpath = ".//marker[@id='inkstitch-pattern-marker']" + pattern_marker = self.document.xpath(xpath) + + if not pattern_marker: + # get or create def element + defs = self.document.find(SVG_DEFS_TAG) + if defs is None: + defs = etree.SubElement(self.document, SVG_DEFS_TAG) + + # insert marker + marker = """ + + + + + """ # noqa: E501 + defs.append(etree.fromstring(marker)) + + # attach marker to node + style = node.get('style', '').split(";") + style = [i for i in style if not i.startswith('marker-start')] + style.append('marker-start:url(#inkstitch-pattern-marker)') + node.set('style', ";".join(style)) diff --git a/lib/extensions/base.py b/lib/extensions/base.py index 353e433c..1c10cd4a 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -12,14 +12,14 @@ import inkex from lxml import etree from stringcase import snakecase -from ..commands import group_commands, is_command, layer_commands +from ..commands import is_command, layer_commands from ..elements import EmbroideryElement, nodes_to_elements from ..elements.clone import is_clone +from ..elements.pattern import is_pattern from ..i18n import _ from ..svg import generate_unique_id from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE, - INKSTITCH_ATTRIBS, NOT_EMBROIDERABLE_TAGS, - SVG_DEFS_TAG, SVG_GROUP_TAG) + NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG) SVG_METADATA_TAG = inkex.addNS("metadata", "svg") @@ -171,12 +171,10 @@ class InkstitchExtension(inkex.Effect): if selected: if node.tag == SVG_GROUP_TAG: pass - elif ((node.tag in EMBROIDERABLE_TAGS or is_clone(node)) and not - (len(list(group_commands(node, 'pattern_group'))) and not node.get(INKSTITCH_ATTRIBS['satin_column']))): + elif (node.tag in EMBROIDERABLE_TAGS or is_clone(node)) and not is_pattern(node): nodes.append(node) # add images, text and patterns for the troubleshoot extension - elif (troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or - (len(list(group_commands(node, 'pattern_group'))) and not node.get(INKSTITCH_ATTRIBS['satin_column'])))): + elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or is_pattern(node)): nodes.append(node) return nodes diff --git a/lib/extensions/group_commands.py b/lib/extensions/group_commands.py deleted file mode 100644 index af1f9fb1..00000000 --- a/lib/extensions/group_commands.py +++ /dev/null @@ -1,41 +0,0 @@ -# Authors: see git history -# -# Copyright (c) 2010 Authors -# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. - -import inkex -from lxml import etree - -from ..commands import GROUP_COMMANDS, ensure_symbol, get_command_description -from ..i18n import _ -from ..svg import get_correction_transform -from ..svg.tags import INKSCAPE_LABEL, SVG_USE_TAG, XLINK_HREF -from .commands import CommandsExtension - - -class GroupCommands(CommandsExtension): - COMMANDS = GROUP_COMMANDS - - def effect(self): - commands = [command for command in self.COMMANDS if getattr(self.options, command)] - - if not commands: - inkex.errormsg(_("Please choose one or more commands to add.")) - return - - correction_transform = get_correction_transform(self.svg.get_current_layer(), child=True) - - for i, command in enumerate(commands): - ensure_symbol(self.document, command) - - etree.SubElement(self.svg.get_current_layer(), SVG_USE_TAG, - { - "id": self.uniqueId("use"), - INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command), - XLINK_HREF: "#inkstitch_%s" % command, - "height": "100%", - "width": "100%", - "x": str(i * 20), - "y": "-10", - "transform": correction_transform - }) diff --git a/lib/inx/extensions.py b/lib/inx/extensions.py index 8ca0addc..9a197c5d 100755 --- a/lib/inx/extensions.py +++ b/lib/inx/extensions.py @@ -5,8 +5,8 @@ import pyembroidery -from ..commands import (COMMANDS, GLOBAL_COMMANDS, GROUP_COMMANDS, - LAYER_COMMANDS, OBJECT_COMMANDS) +from ..commands import (COMMANDS, GLOBAL_COMMANDS, LAYER_COMMANDS, + OBJECT_COMMANDS) from ..extensions import Input, Output, extensions from ..threads import ThreadCatalog from .outputs import pyembroidery_output_formats @@ -24,10 +24,6 @@ def global_commands(): return [(command, COMMANDS[command]) for command in GLOBAL_COMMANDS] -def group_commands(): - return [(command, COMMANDS[command]) for command in GROUP_COMMANDS] - - def object_commands(): return [(command, COMMANDS[command]) for command in OBJECT_COMMANDS] @@ -55,7 +51,6 @@ def generate_extension_inx_files(): write_inx_file(name, template.render(formats=pyembroidery_output_formats(), debug_formats=pyembroidery_debug_formats(), threadcatalog=threadcatalog(), - group_commands=group_commands(), layer_commands=layer_commands(), object_commands=object_commands(), global_commands=global_commands())) -- cgit v1.2.3 From b6ce6ccd814fdbaa754c60ca69901dd1c855d8b6 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Mon, 28 Jun 2021 20:21:44 +0200 Subject: undo previous changes (group command stuff) --- lib/commands.py | 3 --- lib/svg/tags.py | 1 - 2 files changed, 4 deletions(-) (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index cb2a74d5..265e7e23 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -49,9 +49,6 @@ COMMANDS = { # L10N command that affects a layer "ignore_layer": N_("Ignore layer (do not stitch any objects in this layer)"), - # L10N command that affects a group - "pattern_group": N_("Strokes in this group will be interpretet as a pattern"), - # L10N command that affects entire document "origin": N_("Origin for exported embroidery files"), diff --git a/lib/svg/tags.py b/lib/svg/tags.py index c8e9b67e..cacf8b78 100644 --- a/lib/svg/tags.py +++ b/lib/svg/tags.py @@ -86,7 +86,6 @@ inkstitch_attribs = [ 'zigzag_underlay_inset_mm', 'zigzag_underlay_spacing_mm', 'e_stitch', - 'pattern', 'split_stitch', 'pull_compensation_mm', 'stroke_first', -- cgit v1.2.3 From aa24b3a3a215cc536bdac94d8ace667f692ffc45 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Tue, 29 Jun 2021 19:17:12 +0200 Subject: everywhere patterns --- lib/elements/element.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ lib/elements/satin_column.py | 35 -------------------------------- 2 files changed, 48 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/elements/element.py b/lib/elements/element.py index 9b894d89..aa0c4795 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -9,6 +9,7 @@ from copy import deepcopy import inkex import tinycss2 from inkex import bezier +from shapely import geometry as shgeo from ..commands import find_commands from ..i18n import _ @@ -330,6 +331,52 @@ class EmbroideryElement(object): else: return None + @cache + def get_patterns(self): + xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" + patterns = self.node.xpath(xpath, namespaces=inkex.NSS) + line_strings = [] + for pattern in patterns: + if pattern.tag not in EMBROIDERABLE_TAGS: + continue + d = pattern.get_path() + path = inkex.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) + + def _apply_patterns(self, patches): + patterns = self.get_patterns() + if not patterns: + return patches + + patch_points = [] + for patch in patches: + for i, stitch in enumerate(patch.stitches): + patch_points.append(stitch) + if i == len(patch.stitches) - 1: + continue + intersection_points = self._get_pattern_points(stitch, patch.stitches[i+1], patterns) + for point in intersection_points: + patch_points.append(point) + patch.stitches = patch_points + + def _get_pattern_points(self, first, second, patterns): + points = [] + for pattern in patterns: + intersection = shgeo.LineString([first, second]).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(first)) + return points + def strip_control_points(self, subpath): return [point for control_before, point, control_after in subpath] @@ -362,6 +409,7 @@ class EmbroideryElement(object): self.validate() patches = self.to_patches(last_patch) + self._apply_patterns(patches) for patch in patches: patch.tie_modus = self.ties diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 77cb7d22..00a8500a 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -578,10 +578,6 @@ class SatinColumn(EmbroideryElement): return SatinColumn(node) def get_patterns(self): - # TODO: which one is better?!? - # All child groups of pattern - # xpath = "./ancestor::svg:g//*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" - # Only direct siblings of pattern xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" patterns = self.node.xpath(xpath, namespaces=NSS) line_strings = [] @@ -828,20 +824,6 @@ 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) @@ -859,22 +841,8 @@ class SatinColumn(EmbroideryElement): 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) @@ -905,13 +873,10 @@ 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() -- cgit v1.2.3 From 20176008f761d0bdcc1a2ab4f863229ed0c32e52 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Tue, 29 Jun 2021 20:42:16 +0200 Subject: update split pattern --- lib/elements/satin_column.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 00a8500a..dd7df7d1 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -76,16 +76,6 @@ 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') @@ -96,10 +86,12 @@ class SatinColumn(EmbroideryElement): @param('max_stitch_length_mm', _('Maximum stitch length'), tooltip=_('Maximum stitch length for split stitches.'), - type='float', unit="mm", - default=12.1) + type='float', unit="mm") def max_stitch_length(self): - return max(self.get_float_param("max_stitch_length_mm", 12.4), 0.1 * PIXELS_PER_MM) + max_stitch_length = self.get_float_param("max_stitch_length_mm") or None + if max_stitch_length: + max_stitch_length *= PIXELS_PER_MM + return self.get_float_param("max_stitch_length_mm") or None @property def color(self): @@ -793,6 +785,9 @@ class SatinColumn(EmbroideryElement): # print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation + if self.max_stitch_length: + return self.do_split_stitch() + patch = Patch(color=self.color) sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation) @@ -846,7 +841,7 @@ class SatinColumn(EmbroideryElement): def _get_split_points(self, left, right, count=None): points = [] distance = left.distance(right) - split_count = count or int(distance / self.max_stitch_length) + 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) @@ -875,8 +870,6 @@ class SatinColumn(EmbroideryElement): if self.e_stitch: patch += self.do_e_stitch() - elif self.split_stitch: - patch += self.do_split_stitch() else: patch += self.do_satin() -- cgit v1.2.3 From 9604e411d4e50b011e4ae41bdcfdf95c45640684 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Tue, 29 Jun 2021 20:52:46 +0200 Subject: remove get_pattern from satin_column --- lib/elements/satin_column.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index dd7df7d1..a3cc527f 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -6,15 +6,13 @@ from copy import deepcopy from itertools import chain -from inkex import NSS, paths +from inkex import paths from shapely import affinity as shaffinity from shapely import geometry as shgeo from shapely.ops import nearest_points from ..i18n import _ -from ..svg import (PIXELS_PER_MM, apply_transforms, line_strings_to_csp, - point_lists_to_csp) -from ..svg.tags import EMBROIDERABLE_TAGS +from ..svg import PIXELS_PER_MM, 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 @@ -569,22 +567,6 @@ class SatinColumn(EmbroideryElement): return SatinColumn(node) - def get_patterns(self): - xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" - patterns = self.node.xpath(xpath, namespaces=NSS) - line_strings = [] - for pattern in patterns: - if pattern.tag not in EMBROIDERABLE_TAGS: - continue - 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): -- cgit v1.2.3 From a152e1edea19ae16f8226032f9cd2ded004b168c Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Tue, 29 Jun 2021 21:00:45 +0200 Subject: only check embroiderable tags for pattern marker --- lib/elements/pattern.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/elements/pattern.py b/lib/elements/pattern.py index c66ffbdc..fa54b7ba 100644 --- a/lib/elements/pattern.py +++ b/lib/elements/pattern.py @@ -6,6 +6,7 @@ import inkex from ..i18n import _ +from ..svg.tags import EMBROIDERABLE_TAGS from .element import EmbroideryElement from .validation import ObjectTypeWarning @@ -33,4 +34,6 @@ class PatternObject(EmbroideryElement): def is_pattern(node): + if node.tag not in EMBROIDERABLE_TAGS: + return False return "marker-start:url(#inkstitch-pattern-marker)" in node.get('style', '') -- cgit v1.2.3 From 52d9ee6a6d97b2ea752f5fdd3080a160a3574f82 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Wed, 30 Jun 2021 14:05:13 +0200 Subject: structuring --- lib/elements/element.py | 50 ++------------------------ lib/elements/pattern.py | 7 ---- lib/elements/utils.py | 3 +- lib/extensions/__init__.py | 4 +-- lib/extensions/apply_pattern.py | 62 -------------------------------- lib/extensions/base.py | 2 +- lib/extensions/selection_to_pattern.py | 61 +++++++++++++++++++++++++++++++ lib/patterns.py | 66 ++++++++++++++++++++++++++++++++++ 8 files changed, 134 insertions(+), 121 deletions(-) delete mode 100644 lib/extensions/apply_pattern.py create mode 100644 lib/extensions/selection_to_pattern.py create mode 100644 lib/patterns.py (limited to 'lib') diff --git a/lib/elements/element.py b/lib/elements/element.py index aa0c4795..dc466fbf 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -9,10 +9,10 @@ from copy import deepcopy import inkex import tinycss2 from inkex import bezier -from shapely import geometry as shgeo from ..commands import find_commands from ..i18n import _ +from ..patterns import apply_patterns from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length, get_node_transform) from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS, @@ -331,52 +331,6 @@ class EmbroideryElement(object): else: return None - @cache - def get_patterns(self): - xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" - patterns = self.node.xpath(xpath, namespaces=inkex.NSS) - line_strings = [] - for pattern in patterns: - if pattern.tag not in EMBROIDERABLE_TAGS: - continue - d = pattern.get_path() - path = inkex.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) - - def _apply_patterns(self, patches): - patterns = self.get_patterns() - if not patterns: - return patches - - patch_points = [] - for patch in patches: - for i, stitch in enumerate(patch.stitches): - patch_points.append(stitch) - if i == len(patch.stitches) - 1: - continue - intersection_points = self._get_pattern_points(stitch, patch.stitches[i+1], patterns) - for point in intersection_points: - patch_points.append(point) - patch.stitches = patch_points - - def _get_pattern_points(self, first, second, patterns): - points = [] - for pattern in patterns: - intersection = shgeo.LineString([first, second]).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(first)) - return points - def strip_control_points(self, subpath): return [point for control_before, point, control_after in subpath] @@ -409,7 +363,7 @@ class EmbroideryElement(object): self.validate() patches = self.to_patches(last_patch) - self._apply_patterns(patches) + apply_patterns(patches, self.node) for patch in patches: patch.tie_modus = self.ties diff --git a/lib/elements/pattern.py b/lib/elements/pattern.py index fa54b7ba..98f29456 100644 --- a/lib/elements/pattern.py +++ b/lib/elements/pattern.py @@ -6,7 +6,6 @@ import inkex from ..i18n import _ -from ..svg.tags import EMBROIDERABLE_TAGS from .element import EmbroideryElement from .validation import ObjectTypeWarning @@ -31,9 +30,3 @@ class PatternObject(EmbroideryElement): def to_patches(self, last_patch): return [] - - -def is_pattern(node): - if node.tag not in EMBROIDERABLE_TAGS: - return False - return "marker-start:url(#inkstitch-pattern-marker)" in node.get('style', '') diff --git a/lib/elements/utils.py b/lib/elements/utils.py index cd87cec8..99df7002 100644 --- a/lib/elements/utils.py +++ b/lib/elements/utils.py @@ -4,6 +4,7 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. from ..commands import is_command +from ..patterns import is_pattern from ..svg.tags import (EMBROIDERABLE_TAGS, SVG_IMAGE_TAG, SVG_PATH_TAG, SVG_POLYLINE_TAG, SVG_TEXT_TAG) from .auto_fill import AutoFill @@ -12,7 +13,7 @@ from .element import EmbroideryElement from .empty_d_object import EmptyDObject from .fill import Fill from .image import ImageObject -from .pattern import PatternObject, is_pattern +from .pattern import PatternObject from .polyline import Polyline from .satin_column import SatinColumn from .stroke import Stroke diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 3bd2fef6..7996770d 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -5,7 +5,6 @@ from lib.extensions.troubleshoot import Troubleshoot -from .apply_pattern import ApplyPattern from .auto_satin import AutoSatin from .break_apart import BreakApart from .cleanup import Cleanup @@ -29,6 +28,7 @@ from .params import Params from .print_pdf import Print from .remove_embroidery_settings import RemoveEmbroiderySettings from .reorder import Reorder +from .selection_to_pattern import SelectionToPattern from .simulator import Simulator from .stitch_plan_preview import StitchPlanPreview from .zip import Zip @@ -41,7 +41,7 @@ __all__ = extensions = [StitchPlanPreview, Output, Zip, Flip, - ApplyPattern, + SelectionToPattern, ObjectCommands, LayerCommands, GlobalCommands, diff --git a/lib/extensions/apply_pattern.py b/lib/extensions/apply_pattern.py deleted file mode 100644 index ad881604..00000000 --- a/lib/extensions/apply_pattern.py +++ /dev/null @@ -1,62 +0,0 @@ -# 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 lxml import etree - -from ..i18n import _ -from ..svg.tags import SVG_DEFS_TAG -from .base import InkstitchExtension - - -class ApplyPattern(InkstitchExtension): - # This extension will mark selected - - def effect(self): - if not self.get_elements(): - return - - if not self.svg.selected: - inkex.errormsg(_("Please select at least one object to be marked as a pattern.")) - return - - for pattern in self.svg.selected.values(): - self.set_marker(pattern) - - def set_marker(self, node): - xpath = ".//marker[@id='inkstitch-pattern-marker']" - pattern_marker = self.document.xpath(xpath) - - if not pattern_marker: - # get or create def element - defs = self.document.find(SVG_DEFS_TAG) - if defs is None: - defs = etree.SubElement(self.document, SVG_DEFS_TAG) - - # insert marker - marker = """ - - - - - """ # noqa: E501 - defs.append(etree.fromstring(marker)) - - # attach marker to node - style = node.get('style', '').split(";") - style = [i for i in style if not i.startswith('marker-start')] - style.append('marker-start:url(#inkstitch-pattern-marker)') - node.set('style', ";".join(style)) diff --git a/lib/extensions/base.py b/lib/extensions/base.py index 1c10cd4a..862d031e 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -15,8 +15,8 @@ from stringcase import snakecase from ..commands import is_command, layer_commands from ..elements import EmbroideryElement, nodes_to_elements from ..elements.clone import is_clone -from ..elements.pattern import is_pattern from ..i18n import _ +from ..patterns import is_pattern 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) diff --git a/lib/extensions/selection_to_pattern.py b/lib/extensions/selection_to_pattern.py new file mode 100644 index 00000000..3527cc5e --- /dev/null +++ b/lib/extensions/selection_to_pattern.py @@ -0,0 +1,61 @@ +# 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 lxml import etree + +from ..i18n import _ +from ..svg.tags import SVG_DEFS_TAG +from .base import InkstitchExtension + + +class SelectionToPattern(InkstitchExtension): + + def effect(self): + if not self.get_elements(): + return + + if not self.svg.selected: + inkex.errormsg(_("Please select at least one object to be marked as a pattern.")) + return + + for pattern in self.svg.selected.values(): + self.set_marker(pattern) + + def set_marker(self, node): + xpath = ".//marker[@id='inkstitch-pattern-marker']" + pattern_marker = self.document.xpath(xpath) + + if not pattern_marker: + # get or create def element + defs = self.document.find(SVG_DEFS_TAG) + if defs is None: + defs = etree.SubElement(self.document, SVG_DEFS_TAG) + + # insert marker + marker = """ + + + + + """ # noqa: E501 + defs.append(etree.fromstring(marker)) + + # attach marker to node + style = node.get('style', '').split(";") + style = [i for i in style if not i.startswith('marker-start')] + style.append('marker-start:url(#inkstitch-pattern-marker)') + node.set('style', ";".join(style)) diff --git a/lib/patterns.py b/lib/patterns.py new file mode 100644 index 00000000..8872a8ce --- /dev/null +++ b/lib/patterns.py @@ -0,0 +1,66 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +import inkex +from shapely import geometry as shgeo + +from .svg import apply_transforms +from .svg.tags import EMBROIDERABLE_TAGS +from .utils import Point + + +def is_pattern(node): + if node.tag not in EMBROIDERABLE_TAGS: + return False + return "marker-start:url(#inkstitch-pattern-marker)" in node.get('style', '') + + +def apply_patterns(patches, node): + patterns = _get_patterns(node) + if not patterns: + return patches + + patch_points = [] + for patch in patches: + for i, stitch in enumerate(patch.stitches): + patch_points.append(stitch) + if i == len(patch.stitches) - 1: + continue + intersection_points = _get_pattern_points(stitch, patch.stitches[i+1], patterns) + for point in intersection_points: + patch_points.append(point) + patch.stitches = patch_points + + +def _get_patterns(node): + xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" + patterns = node.xpath(xpath, namespaces=inkex.NSS) + line_strings = [] + for pattern in patterns: + if pattern.tag not in EMBROIDERABLE_TAGS: + continue + d = pattern.get_path() + path = inkex.paths.Path(d).to_superpath() + path = apply_transforms(path, pattern) + inkex.bezier.cspsubdiv(path, 0.1) + path = [[point for control_before, point, control_after in subpath] for subpath in path] + lines = [shgeo.LineString(p) for p in path] + for line in lines: + line_strings.append(line) + return shgeo.MultiLineString(line_strings) + + +def _get_pattern_points(first, second, patterns): + points = [] + for pattern in patterns: + intersection = shgeo.LineString([first, second]).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(first)) + return points -- cgit v1.2.3 From 8e229770a5b5d28f68e065f61af3452f7e25c6b5 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Wed, 30 Jun 2021 14:12:27 +0200 Subject: corrections --- lib/elements/satin_column.py | 5 +---- lib/svg/tags.py | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index a3cc527f..02d7a36c 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -12,7 +12,7 @@ from shapely import geometry as shgeo from shapely.ops import nearest_points from ..i18n import _ -from ..svg import PIXELS_PER_MM, line_strings_to_csp, point_lists_to_csp +from ..svg import 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 @@ -86,9 +86,6 @@ class SatinColumn(EmbroideryElement): tooltip=_('Maximum stitch length for split stitches.'), type='float', unit="mm") def max_stitch_length(self): - max_stitch_length = self.get_float_param("max_stitch_length_mm") or None - if max_stitch_length: - max_stitch_length *= PIXELS_PER_MM return self.get_float_param("max_stitch_length_mm") or None @property diff --git a/lib/svg/tags.py b/lib/svg/tags.py index cacf8b78..5c1d892a 100644 --- a/lib/svg/tags.py +++ b/lib/svg/tags.py @@ -86,7 +86,6 @@ inkstitch_attribs = [ 'zigzag_underlay_inset_mm', 'zigzag_underlay_spacing_mm', 'e_stitch', - 'split_stitch', 'pull_compensation_mm', 'stroke_first', # Legacy -- cgit v1.2.3 From a1581202646172707768d24bb5dcba63bd5d4538 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Wed, 30 Jun 2021 16:36:25 +0200 Subject: fix selection in selection to pattern --- lib/extensions/selection_to_pattern.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/extensions/selection_to_pattern.py b/lib/extensions/selection_to_pattern.py index 3527cc5e..52a0404b 100644 --- a/lib/extensions/selection_to_pattern.py +++ b/lib/extensions/selection_to_pattern.py @@ -7,7 +7,7 @@ import inkex from lxml import etree from ..i18n import _ -from ..svg.tags import SVG_DEFS_TAG +from ..svg.tags import EMBROIDERABLE_TAGS, SVG_DEFS_TAG from .base import InkstitchExtension @@ -21,8 +21,9 @@ class SelectionToPattern(InkstitchExtension): inkex.errormsg(_("Please select at least one object to be marked as a pattern.")) return - for pattern in self.svg.selected.values(): - self.set_marker(pattern) + for pattern in self.get_nodes(): + if pattern.tag in EMBROIDERABLE_TAGS: + self.set_marker(pattern) def set_marker(self, node): xpath = ".//marker[@id='inkstitch-pattern-marker']" -- cgit v1.2.3 From 8eba84a239ce655f32f131d6a82e3142693edd47 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Wed, 30 Jun 2021 21:47:33 +0200 Subject: update pattern troubleshoot description --- lib/elements/pattern.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/elements/pattern.py b/lib/elements/pattern.py index 98f29456..a32e72c0 100644 --- a/lib/elements/pattern.py +++ b/lib/elements/pattern.py @@ -12,13 +12,14 @@ from .validation import ObjectTypeWarning class PatternWarning(ObjectTypeWarning): name = _("Pattern Element") - description = _("This element will only be stitched out as a pattern within the specified object.") + description = _("This element will not be embroidered. " + "It will appear as a pattern in objects of it's group in the object panel. " + "Sub-group objects will be ignored.") steps_to_solve = [ - _("If you want to remove the pattern configuration for a pattern object follow these steps:"), - _("* Select pattern element(s)"), - _('* Run Extensions > Ink/Stitch > Troubleshoot > Remove embroidery settings...'), - _('* Make sure "Remove params" is enables'), - _('* Click "Apply"') + _("Turn the pattern marker off:"), + _('* Open the Fill and Stroke panel (Objects > Fill and Stroke)'), + _('* Go to the Stroke style tab'), + _('* Under "Markers" choose the first (empty) option in the first dropdown list.') ] -- cgit v1.2.3 From 736e10240a5c8357fac449371f8ac7451b7696b0 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 30 Jun 2021 19:48:51 -0400 Subject: slight wording adjustment --- lib/elements/pattern.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/elements/pattern.py b/lib/elements/pattern.py index a32e72c0..95ce81a1 100644 --- a/lib/elements/pattern.py +++ b/lib/elements/pattern.py @@ -13,10 +13,10 @@ from .validation import ObjectTypeWarning class PatternWarning(ObjectTypeWarning): name = _("Pattern Element") description = _("This element will not be embroidered. " - "It will appear as a pattern in objects of it's group in the object panel. " - "Sub-group objects will be ignored.") + "It will appear as a pattern applied to objects in the same group as it. " + "Objects in sub-groups will be ignored.") steps_to_solve = [ - _("Turn the pattern marker off:"), + _("To disable pattern mode, remove the pattern marker:"), _('* Open the Fill and Stroke panel (Objects > Fill and Stroke)'), _('* Go to the Stroke style tab'), _('* Under "Markers" choose the first (empty) option in the first dropdown list.') -- cgit v1.2.3 From a5d7ffaffd0b73b5848b7d55ea1f571ea94fd5a7 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Wed, 7 Jul 2021 20:07:04 +0200 Subject: add or remove stitches (stroke or fill) --- lib/extensions/selection_to_pattern.py | 3 +- lib/patterns.py | 108 ++++++++++++++++++++++----------- 2 files changed, 75 insertions(+), 36 deletions(-) (limited to 'lib') diff --git a/lib/extensions/selection_to_pattern.py b/lib/extensions/selection_to_pattern.py index 52a0404b..41f89a83 100644 --- a/lib/extensions/selection_to_pattern.py +++ b/lib/extensions/selection_to_pattern.py @@ -56,7 +56,8 @@ class SelectionToPattern(InkstitchExtension): defs.append(etree.fromstring(marker)) # attach marker to node - style = node.get('style', '').split(";") + style = node.get('style') or '' + style = style.split(";") style = [i for i in style if not i.startswith('marker-start')] style.append('marker-start:url(#inkstitch-pattern-marker)') node.set('style', ";".join(style)) diff --git a/lib/patterns.py b/lib/patterns.py index 8872a8ce..5ae763fc 100644 --- a/lib/patterns.py +++ b/lib/patterns.py @@ -5,8 +5,8 @@ import inkex from shapely import geometry as shgeo +import math -from .svg import apply_transforms from .svg.tags import EMBROIDERABLE_TAGS from .utils import Point @@ -14,53 +14,91 @@ from .utils import Point def is_pattern(node): if node.tag not in EMBROIDERABLE_TAGS: return False - return "marker-start:url(#inkstitch-pattern-marker)" in node.get('style', '') + style = node.get('style') or '' + return "marker-start:url(#inkstitch-pattern-marker)" in style def apply_patterns(patches, node): patterns = _get_patterns(node) - if not patterns: - return patches + _apply_stroke_patterns(patterns['stroke_patterns'], patches) + _apply_fill_patterns(patterns['fill_patterns'], patches) - patch_points = [] - for patch in patches: - for i, stitch in enumerate(patch.stitches): - patch_points.append(stitch) - if i == len(patch.stitches) - 1: - continue - intersection_points = _get_pattern_points(stitch, patch.stitches[i+1], patterns) - for point in intersection_points: - patch_points.append(point) - patch.stitches = patch_points + +def _apply_stroke_patterns(patterns, patches): + for pattern in patterns: + for patch in patches: + patch_points = [] + for i, stitch in enumerate(patch.stitches): + patch_points.append(stitch) + if i == len(patch.stitches) - 1: + continue + intersection_points = _get_pattern_points(stitch, patch.stitches[i+1], pattern) + for point in intersection_points: + patch_points.append(point) + patch.stitches = patch_points + + +def _apply_fill_patterns(patterns, patches): + for pattern in patterns: + for patch in patches: + patch_points = [] + for i, stitch in enumerate(patch.stitches): + # keep points outside the fill patter + if not shgeo.Point(stitch).within(pattern): + patch_points.append(stitch) + # keep start and end points + elif i - 1 < 0 or i >= len(patch.stitches) - 1: + patch_points.append(stitch) + # keep points if they have an angle + # the necessary angle can be variable with certain stitch types (later on) + # but they don't need to use filled patterns for those + elif not 179 < get_angle(patch.stitches[i-1], stitch, patch.stitches[i+1]) < 181: + patch_points.append(stitch) + patch.stitches = patch_points def _get_patterns(node): + from .elements import EmbroideryElement + from .elements.fill import Fill + from .elements.stroke import Stroke + + fills = [] + strokes = [] xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]" patterns = node.xpath(xpath, namespaces=inkex.NSS) - line_strings = [] for pattern in patterns: if pattern.tag not in EMBROIDERABLE_TAGS: continue - d = pattern.get_path() - path = inkex.paths.Path(d).to_superpath() - path = apply_transforms(path, pattern) - inkex.bezier.cspsubdiv(path, 0.1) - path = [[point for control_before, point, control_after in subpath] for subpath in path] - lines = [shgeo.LineString(p) for p in path] - for line in lines: - line_strings.append(line) - return shgeo.MultiLineString(line_strings) - - -def _get_pattern_points(first, second, patterns): + + element = EmbroideryElement(pattern) + fill = element.get_style('fill') + stroke = element.get_style('stroke') + + if fill is not None: + fill_pattern = Fill(pattern).shape + fills.append(fill_pattern) + + if stroke is not None: + stroke_pattern = Stroke(pattern).paths + line_strings = [shgeo.LineString(path) for path in stroke_pattern] + strokes.append(shgeo.MultiLineString(line_strings)) + + return {'fill_patterns': fills, 'stroke_patterns': strokes} + + +def _get_pattern_points(first, second, pattern): points = [] - for pattern in patterns: - intersection = shgeo.LineString([first, second]).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 + intersection = shgeo.LineString([first, second]).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 first points.sort(key=lambda point: point.distance(first)) return points + + +def get_angle(a, b, c): + ang = math.degrees(math.atan2(c[1]-b[1], c[0]-b[0]) - math.atan2(a[1]-b[1], a[0]-b[0])) + return ang + 360 if ang < 0 else ang -- cgit v1.2.3 From c1e6558f7852def419adfbeb087b2194e6030a2c Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 10:57:53 -0400 Subject: rename Patch to StitchGroup --- lib/elements/auto_fill.py | 4 ++-- lib/elements/element.py | 10 +++++----- lib/elements/fill.py | 4 ++-- lib/elements/polyline.py | 4 ++-- lib/elements/satin_column.py | 16 ++++++++-------- lib/elements/stroke.py | 6 +++--- lib/gui/simulator.py | 2 +- lib/stitch_plan/stitch_plan.py | 4 ++-- 8 files changed, 25 insertions(+), 25 deletions(-) (limited to 'lib') diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py index 69533f62..29ca3545 100644 --- a/lib/elements/auto_fill.py +++ b/lib/elements/auto_fill.py @@ -12,7 +12,7 @@ from shapely import geometry as shgeo from ..i18n import _ from ..stitches import auto_fill from ..utils import cache, version -from .element import Patch, param +from .element import StitchGroup, param from .fill import Fill from .validation import ValidationWarning @@ -260,7 +260,7 @@ class AutoFill(Fill): self.fatal(message) - return [Patch(stitches=stitches, color=self.color)] + return [StitchGroup(stitches=stitches, color=self.color)] def validation_warnings(self): if self.shape.area < 20: diff --git a/lib/elements/element.py b/lib/elements/element.py index dc466fbf..bcb59bf4 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -20,7 +20,7 @@ from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS, from ..utils import Point, cache -class Patch: +class StitchGroup: """A raw collection of stitches with attached instructions.""" def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False): @@ -32,10 +32,10 @@ class Patch: self.stitch_as_is = stitch_as_is def __add__(self, other): - if isinstance(other, Patch): - return Patch(self.color, self.stitches + other.stitches) + if isinstance(other, StitchGroup): + return StitchGroup(self.color, self.stitches + other.stitches) else: - raise TypeError("Patch can only be added to another Patch") + raise TypeError("StitchGroup can only be added to another StitchGroup") def __len__(self): # This method allows `len(patch)` and `if patch: @@ -45,7 +45,7 @@ class Patch: self.stitches.append(stitch) def reverse(self): - return Patch(self.color, self.stitches[::-1]) + return StitchGroup(self.color, self.stitches[::-1]) class Param(object): diff --git a/lib/elements/fill.py b/lib/elements/fill.py index b6799165..75a86ffd 100644 --- a/lib/elements/fill.py +++ b/lib/elements/fill.py @@ -14,7 +14,7 @@ from ..i18n import _ from ..stitches import legacy_fill from ..svg import PIXELS_PER_MM from ..utils import cache -from .element import EmbroideryElement, Patch, param +from .element import EmbroideryElement, StitchGroup, param from .validation import ValidationError @@ -198,4 +198,4 @@ class Fill(EmbroideryElement): self.flip, self.staggers, self.skip_last) - return [Patch(stitches=stitch_list, color=self.color) for stitch_list in stitch_lists] + return [StitchGroup(stitches=stitch_list, color=self.color) for stitch_list in stitch_lists] diff --git a/lib/elements/polyline.py b/lib/elements/polyline.py index 5ea00508..f63dfc3b 100644 --- a/lib/elements/polyline.py +++ b/lib/elements/polyline.py @@ -9,7 +9,7 @@ from shapely import geometry as shgeo from ..i18n import _ from ..utils import cache from ..utils.geometry import Point -from .element import EmbroideryElement, Patch, param +from .element import EmbroideryElement, StitchGroup, param from .validation import ValidationWarning @@ -101,7 +101,7 @@ class Polyline(EmbroideryElement): yield PolylineWarning(self.points[0]) def to_patches(self, last_patch): - patch = Patch(color=self.color) + patch = StitchGroup(color=self.color) for stitch in self.stitches: patch.add_stitch(Point(*stitch)) diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 02d7a36c..3d0e7ff5 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -14,7 +14,7 @@ from shapely.ops import nearest_points from ..i18n import _ from ..svg import line_strings_to_csp, point_lists_to_csp from ..utils import Point, cache, collapse_duplicate_point, cut -from .element import EmbroideryElement, Patch, param +from .element import EmbroideryElement, StitchGroup, param from .validation import ValidationError, ValidationWarning @@ -716,7 +716,7 @@ class SatinColumn(EmbroideryElement): # other. forward, back = self.plot_points_on_rails(self.contour_underlay_stitch_length, -self.contour_underlay_inset) - return Patch(color=self.color, stitches=(forward + list(reversed(back)))) + return StitchGroup(color=self.color, stitches=(forward + list(reversed(back)))) def do_center_walk(self): # Center walk underlay is just a running stitch down and back on the @@ -725,7 +725,7 @@ class SatinColumn(EmbroideryElement): # Do it like contour underlay, but inset all the way to the center. forward, back = self.plot_points_on_rails(self.center_walk_underlay_stitch_length, -100000) - return Patch(color=self.color, stitches=(forward + list(reversed(back)))) + return StitchGroup(color=self.color, stitches=(forward + list(reversed(back)))) def do_zigzag_underlay(self): # zigzag underlay, usually done at a much lower density than the @@ -738,7 +738,7 @@ class SatinColumn(EmbroideryElement): # "German underlay" described here: # http://www.mrxstitch.com/underlay-what-lies-beneath-machine-embroidery/ - patch = Patch(color=self.color) + patch = StitchGroup(color=self.color) sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, -self.zigzag_underlay_inset) @@ -767,7 +767,7 @@ class SatinColumn(EmbroideryElement): if self.max_stitch_length: return self.do_split_stitch() - patch = Patch(color=self.color) + patch = StitchGroup(color=self.color) sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation) @@ -785,7 +785,7 @@ class SatinColumn(EmbroideryElement): # print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation - patch = Patch(color=self.color) + patch = StitchGroup(color=self.color) sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation) @@ -800,7 +800,7 @@ class SatinColumn(EmbroideryElement): 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) + patch = StitchGroup(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) @@ -834,7 +834,7 @@ class SatinColumn(EmbroideryElement): # beziers. The boundary points between beziers serve as "checkpoints", # allowing the user to control how the zigzags flow around corners. - patch = Patch(color=self.color) + patch = StitchGroup(color=self.color) if self.center_walk_underlay: patch += self.do_center_walk() diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index cf034d21..edd5525a 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -11,7 +11,7 @@ from ..i18n import _ from ..stitches import bean_stitch, running_stitch from ..svg import parse_length_with_units from ..utils import Point, cache -from .element import EmbroideryElement, Patch, param +from .element import EmbroideryElement, StitchGroup, param warned_about_legacy_running_stitch = False @@ -190,7 +190,7 @@ class Stroke(EmbroideryElement): stitches = running_stitch(repeated_path, stitch_length) - return Patch(self.color, stitches) + return StitchGroup(self.color, stitches) def to_patches(self, last_patch): patches = [] @@ -198,7 +198,7 @@ class Stroke(EmbroideryElement): for path in self.paths: path = [Point(x, y) for x, y in path] if self.manual_stitch_mode: - patch = Patch(color=self.color, stitches=path, stitch_as_is=True) + patch = StitchGroup(color=self.color, stitches=path, stitch_as_is=True) elif self.is_running_stitch(): patch = self.running_stitch(path, self.running_stitch_length) diff --git a/lib/gui/simulator.py b/lib/gui/simulator.py index 3e1f68c5..94be66db 100644 --- a/lib/gui/simulator.py +++ b/lib/gui/simulator.py @@ -733,7 +733,7 @@ class SimulatorPreview(Thread): The parent is expected to be a wx.Window and also implement the following methods: def generate_patches(self, abort_event): - Produce an list of Patch instances. This method will be + Produce an list of StitchGroup instances. This method will be invoked in a background thread and it is expected that it may take awhile. diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py index fc0d3760..d1af5365 100644 --- a/lib/stitch_plan/stitch_plan.py +++ b/lib/stitch_plan/stitch_plan.py @@ -12,9 +12,9 @@ from .ties import add_ties def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False): # noqa: C901 - """Convert a collection of inkstitch.element.Patch objects to a StitchPlan. + """Convert a collection of inkstitch.element.StitchGroup objects to a StitchPlan. - * applies instructions embedded in the Patch such as trim_after and stop_after + * applies instructions embedded in the StitchGroup such as trim_after and stop_after * adds tie-ins and tie-offs * adds jump-stitches between patches if necessary """ -- cgit v1.2.3 From 84cb4e2c333d331eb863714797a55589f41e51b2 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 11:21:13 -0400 Subject: move StitchGroup into lib.stitch_plan --- lib/elements/auto_fill.py | 7 +- lib/elements/element.py | 28 -------- lib/elements/fill.py | 5 +- lib/elements/polyline.py | 5 +- lib/elements/satin_column.py | 5 +- lib/elements/stroke.py | 3 +- lib/stitch_plan/__init__.py | 4 +- lib/stitch_plan/color_block.py | 142 +++++++++++++++++++++++++++++++++++++++ lib/stitch_plan/stitch.py | 3 +- lib/stitch_plan/stitch_group.py | 34 ++++++++++ lib/stitch_plan/stitch_plan.py | 144 +--------------------------------------- 11 files changed, 197 insertions(+), 183 deletions(-) create mode 100644 lib/stitch_plan/color_block.py create mode 100644 lib/stitch_plan/stitch_group.py (limited to 'lib') diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py index 3c13a081..e72af1ef 100644 --- a/lib/elements/auto_fill.py +++ b/lib/elements/auto_fill.py @@ -9,13 +9,14 @@ import traceback from shapely import geometry as shgeo +from .element import param +from .fill import Fill +from .validation import ValidationWarning from ..i18n import _ +from ..stitch_plan import StitchGroup from ..stitches import auto_fill from ..svg.tags import INKSCAPE_LABEL from ..utils import cache, version -from .element import StitchGroup, param -from .fill import Fill -from .validation import ValidationWarning class SmallShapeWarning(ValidationWarning): diff --git a/lib/elements/element.py b/lib/elements/element.py index 17ed9167..a3577a5c 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -18,34 +18,6 @@ from ..svg.tags import INKSCAPE_LABEL, INKSTITCH_ATTRIBS from ..utils import Point, cache -class StitchGroup: - """A raw collection of stitches with attached instructions.""" - - def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False): - self.color = color - self.stitches = stitches or [] - self.trim_after = trim_after - self.stop_after = stop_after - self.tie_modus = tie_modus - self.stitch_as_is = stitch_as_is - - def __add__(self, other): - if isinstance(other, StitchGroup): - return StitchGroup(self.color, self.stitches + other.stitches) - else: - raise TypeError("StitchGroup can only be added to another StitchGroup") - - def __len__(self): - # This method allows `len(patch)` and `if patch: - return len(self.stitches) - - def add_stitch(self, stitch): - self.stitches.append(stitch) - - def reverse(self): - return StitchGroup(self.color, self.stitches[::-1]) - - class Param(object): def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, options=[], default=None, tooltip=None, sort_index=0): diff --git a/lib/elements/fill.py b/lib/elements/fill.py index 75a86ffd..2d2ae2ed 100644 --- a/lib/elements/fill.py +++ b/lib/elements/fill.py @@ -10,12 +10,13 @@ import re from shapely import geometry as shgeo from shapely.validation import explain_validity +from .element import EmbroideryElement, param +from .validation import ValidationError from ..i18n import _ +from ..stitch_plan import StitchGroup from ..stitches import legacy_fill from ..svg import PIXELS_PER_MM from ..utils import cache -from .element import EmbroideryElement, StitchGroup, param -from .validation import ValidationError class UnconnectedError(ValidationError): diff --git a/lib/elements/polyline.py b/lib/elements/polyline.py index f63dfc3b..aeae17d9 100644 --- a/lib/elements/polyline.py +++ b/lib/elements/polyline.py @@ -6,11 +6,12 @@ from inkex import Path from shapely import geometry as shgeo +from .element import EmbroideryElement, param +from .validation import ValidationWarning from ..i18n import _ +from ..stitch_plan import StitchGroup from ..utils import cache from ..utils.geometry import Point -from .element import EmbroideryElement, StitchGroup, param -from .validation import ValidationWarning class PolylineWarning(ValidationWarning): diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 1f28cb45..e066f3fb 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -11,11 +11,12 @@ from shapely import affinity as shaffinity from shapely import geometry as shgeo from shapely.ops import nearest_points +from .element import EmbroideryElement, param +from .validation import ValidationError, ValidationWarning from ..i18n import _ +from ..stitch_plan import StitchGroup from ..svg import line_strings_to_csp, point_lists_to_csp from ..utils import Point, cache, collapse_duplicate_point, cut -from .element import EmbroideryElement, StitchGroup, param -from .validation import ValidationError, ValidationWarning class SatinHasFillError(ValidationError): diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index 76e80688..9fcb4e21 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -7,11 +7,12 @@ import sys import shapely.geometry +from .element import EmbroideryElement, param from ..i18n import _ +from ..stitch_plan import StitchGroup from ..stitches import bean_stitch, running_stitch from ..svg import parse_length_with_units from ..utils import Point, cache -from .element import EmbroideryElement, StitchGroup, param warned_about_legacy_running_stitch = False diff --git a/lib/stitch_plan/__init__.py b/lib/stitch_plan/__init__.py index 68301e94..ba4729bb 100644 --- a/lib/stitch_plan/__init__.py +++ b/lib/stitch_plan/__init__.py @@ -3,6 +3,8 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -from .stitch_plan import patches_to_stitch_plan, StitchPlan, ColorBlock +from .stitch_plan import patches_to_stitch_plan, StitchPlan +from .color_block import ColorBlock +from .stitch_group import StitchGroup from .stitch import Stitch from .read_file import stitch_plan_from_file diff --git a/lib/stitch_plan/color_block.py b/lib/stitch_plan/color_block.py new file mode 100644 index 00000000..1cff8aa4 --- /dev/null +++ b/lib/stitch_plan/color_block.py @@ -0,0 +1,142 @@ +from .stitch import Stitch +from ..threads import ThreadColor +from ..utils.geometry import Point +from ..svg import PIXELS_PER_MM + + +class ColorBlock(object): + """Holds a set of stitches, all with the same thread color.""" + + def __init__(self, color=None, stitches=None): + self.color = color + self.stitches = stitches or [] + + def __iter__(self): + return iter(self.stitches) + + def __len__(self): + return len(self.stitches) + + def __repr__(self): + return "ColorBlock(%s, %s)" % (self.color, self.stitches) + + def __getitem__(self, item): + return self.stitches[item] + + def __delitem__(self, item): + del self.stitches[item] + + def __json__(self): + return dict(color=self.color, stitches=self.stitches) + + def has_color(self): + return self._color is not None + + @property + def color(self): + return self._color + + @color.setter + def color(self, value): + if isinstance(value, ThreadColor): + self._color = value + elif value is None: + self._color = None + else: + self._color = ThreadColor(value) + + @property + def last_stitch(self): + if self.stitches: + return self.stitches[-1] + else: + return None + + @property + def num_stitches(self): + """Number of stitches in this color block.""" + return len(self.stitches) + + @property + def num_trims(self): + """Number of trims in this color block.""" + + return sum(1 for stitch in self if stitch.trim) + + @property + def stop_after(self): + if self.last_stitch is not None: + return self.last_stitch.stop + else: + return False + + @property + def trim_after(self): + # If there's a STOP, it will be at the end. We still want to return + # True. + for stitch in reversed(self.stitches): + if stitch.stop or stitch.jump: + continue + elif stitch.trim: + return True + else: + break + + return False + + def filter_duplicate_stitches(self): + if not self.stitches: + return + + stitches = [self.stitches[0]] + + for stitch in self.stitches[1:]: + if stitches[-1].jump or stitch.stop or stitch.trim or stitch.color_change: + # Don't consider jumps, stops, color changes, or trims as candidates for filtering + pass + else: + length = (stitch - stitches[-1]).length() + if length <= 0.1 * PIXELS_PER_MM: + # duplicate stitch, skip this one + continue + + stitches.append(stitch) + + self.stitches = stitches + + def add_stitch(self, *args, **kwargs): + if not args: + # They're adding a command, e.g. `color_block.add_stitch(stop=True)``. + # Use the position from the last stitch. + if self.last_stitch: + args = (self.last_stitch.x, self.last_stitch.y) + else: + raise ValueError("internal error: can't add a command to an empty stitch block") + + if isinstance(args[0], Stitch): + self.stitches.append(args[0]) + elif isinstance(args[0], Point): + self.stitches.append(Stitch(args[0].x, args[0].y, *args[1:], **kwargs)) + else: + if not args and self.last_stitch: + args = (self.last_stitch.x, self.last_stitch.y) + self.stitches.append(Stitch(*args, **kwargs)) + + def add_stitches(self, stitches, *args, **kwargs): + for stitch in stitches: + if isinstance(stitch, (Stitch, Point)): + self.add_stitch(stitch, *args, **kwargs) + else: + self.add_stitch(*stitch, *args, **kwargs) + + def replace_stitches(self, stitches): + self.stitches = stitches + + @property + def bounding_box(self): + minx = min(stitch.x for stitch in self) + miny = min(stitch.y for stitch in self) + maxx = max(stitch.x for stitch in self) + maxy = max(stitch.y for stitch in self) + + return minx, miny, maxx, maxy diff --git a/lib/stitch_plan/stitch.py b/lib/stitch_plan/stitch.py index ae6fa480..85d71935 100644 --- a/lib/stitch_plan/stitch.py +++ b/lib/stitch_plan/stitch.py @@ -8,8 +8,7 @@ from ..utils.geometry import Point class Stitch(Point): def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, tie_modus=0, no_ties=False): - self.x = x - self.y = y + Point.__init__(self, x, y) self.color = color self.jump = jump self.trim = trim diff --git a/lib/stitch_plan/stitch_group.py b/lib/stitch_plan/stitch_group.py new file mode 100644 index 00000000..d1e6bae7 --- /dev/null +++ b/lib/stitch_plan/stitch_group.py @@ -0,0 +1,34 @@ +class StitchGroup: + """A collection of Stitch objects with attached instructions. + + StitchGroups will later be combined to make ColorBlocks, which in turn are + combined to make a StitchPlan. Jump stitches are allowed between + StitchGroups, but not between stitches inside a StitchGroup. This means + that EmbroideryElement classes should produce multiple StitchGroups only if + they want to allow for the possibility of jump stitches to be added in + between them by the stitch plan generation code. + """ + + def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False): + self.color = color + self.stitches = stitches or [] + self.trim_after = trim_after + self.stop_after = stop_after + self.tie_modus = tie_modus + self.stitch_as_is = stitch_as_is + + def __add__(self, other): + if isinstance(other, StitchGroup): + return StitchGroup(self.color, self.stitches + other.stitches) + else: + raise TypeError("StitchGroup can only be added to another StitchGroup") + + def __len__(self): + # This method allows `len(patch)` and `if patch: + return len(self.stitches) + + def add_stitch(self, stitch): + self.stitches.append(stitch) + + def reverse(self): + return StitchGroup(self.color, self.stitches[::-1]) diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py index d1af5365..f5981fb9 100644 --- a/lib/stitch_plan/stitch_plan.py +++ b/lib/stitch_plan/stitch_plan.py @@ -3,11 +3,9 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -from ..svg import PIXELS_PER_MM -from ..threads import ThreadColor -from ..utils.geometry import Point -from .stitch import Stitch from .ties import add_ties +from .color_block import ColorBlock +from ..svg import PIXELS_PER_MM def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False): # noqa: C901 @@ -168,141 +166,3 @@ class StitchPlan(object): return self.color_blocks[-1] else: return None - - -class ColorBlock(object): - """Holds a set of stitches, all with the same thread color.""" - - def __init__(self, color=None, stitches=None): - self.color = color - self.stitches = stitches or [] - - def __iter__(self): - return iter(self.stitches) - - def __len__(self): - return len(self.stitches) - - def __repr__(self): - return "ColorBlock(%s, %s)" % (self.color, self.stitches) - - def __getitem__(self, item): - return self.stitches[item] - - def __delitem__(self, item): - del self.stitches[item] - - def __json__(self): - return dict(color=self.color, stitches=self.stitches) - - def has_color(self): - return self._color is not None - - @property - def color(self): - return self._color - - @color.setter - def color(self, value): - if isinstance(value, ThreadColor): - self._color = value - elif value is None: - self._color = None - else: - self._color = ThreadColor(value) - - @property - def last_stitch(self): - if self.stitches: - return self.stitches[-1] - else: - return None - - @property - def num_stitches(self): - """Number of stitches in this color block.""" - return len(self.stitches) - - @property - def num_trims(self): - """Number of trims in this color block.""" - - return sum(1 for stitch in self if stitch.trim) - - @property - def stop_after(self): - if self.last_stitch is not None: - return self.last_stitch.stop - else: - return False - - @property - def trim_after(self): - # If there's a STOP, it will be at the end. We still want to return - # True. - for stitch in reversed(self.stitches): - if stitch.stop or stitch.jump: - continue - elif stitch.trim: - return True - else: - break - - return False - - def filter_duplicate_stitches(self): - if not self.stitches: - return - - stitches = [self.stitches[0]] - - for stitch in self.stitches[1:]: - if stitches[-1].jump or stitch.stop or stitch.trim or stitch.color_change: - # Don't consider jumps, stops, color changes, or trims as candidates for filtering - pass - else: - length = (stitch - stitches[-1]).length() - if length <= 0.1 * PIXELS_PER_MM: - # duplicate stitch, skip this one - continue - - stitches.append(stitch) - - self.stitches = stitches - - def add_stitch(self, *args, **kwargs): - if not args: - # They're adding a command, e.g. `color_block.add_stitch(stop=True)``. - # Use the position from the last stitch. - if self.last_stitch: - args = (self.last_stitch.x, self.last_stitch.y) - else: - raise ValueError("internal error: can't add a command to an empty stitch block") - - if isinstance(args[0], Stitch): - self.stitches.append(args[0]) - elif isinstance(args[0], Point): - self.stitches.append(Stitch(args[0].x, args[0].y, *args[1:], **kwargs)) - else: - if not args and self.last_stitch: - args = (self.last_stitch.x, self.last_stitch.y) - self.stitches.append(Stitch(*args, **kwargs)) - - def add_stitches(self, stitches, *args, **kwargs): - for stitch in stitches: - if isinstance(stitch, (Stitch, Point)): - self.add_stitch(stitch, *args, **kwargs) - else: - self.add_stitch(*(list(stitch) + args), **kwargs) - - def replace_stitches(self, stitches): - self.stitches = stitches - - @property - def bounding_box(self): - minx = min(stitch.x for stitch in self) - miny = min(stitch.y for stitch in self) - maxx = max(stitch.x for stitch in self) - maxy = max(stitch.y for stitch in self) - - return minx, miny, maxx, maxy -- cgit v1.2.3 From 8fc42628e285160f8f747772b6d5674a1bf23a09 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 11:37:17 -0400 Subject: add tags capability --- lib/stitch_plan/stitch.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/stitch_plan/stitch.py b/lib/stitch_plan/stitch.py index 85d71935..ea4423fa 100644 --- a/lib/stitch_plan/stitch.py +++ b/lib/stitch_plan/stitch.py @@ -7,7 +7,9 @@ from ..utils.geometry import Point class Stitch(Point): - def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, tie_modus=0, no_ties=False): + """A stitch is a Point with extra information telling how to sew it.""" + + def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, tie_modus=0, no_ties=False, tags=None): Point.__init__(self, x, y) self.color = color self.jump = jump @@ -16,6 +18,9 @@ class Stitch(Point): self.color_change = color_change self.tie_modus = tie_modus self.no_ties = no_ties + self.tags = set() + + self.add_tags(tags or []) # Allow creating a Stitch from a Point if isinstance(x, Point): @@ -34,8 +39,32 @@ class Stitch(Point): "NO TIES" if self.no_ties else " ", "COLOR CHANGE" if self.color_change else " ") + def add_tags(self, tags): + for tag in tags: + self.add_tag(tag) + + def add_tag(self, tag): + """Store arbitrary information about a stitch. + + Tags can be used to store any information about a stitch. This can be + used by other parts of the code to keep track of where a Stitch came + from. The Stitch treats tags as opaque. + + Use strings as tags. Python automatically optimizes this kind of + usage of strings, and it doesn't have to constantly do string + comparisons. More details here: + + https://stackabuse.com/guide-to-string-interning-in-python + """ + self.tags.add(tag) + + def has_tag(self, tag): + return tag in self.tags + def copy(self): - return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.tie_modus, self.no_ties) + return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.tie_modus, self.no_ties, self.tags) def __json__(self): - return vars(self) + attributes = dict(vars(self)) + attributes['tags'] = list(attributes['tags']) + return attributes -- cgit v1.2.3 From 28e394b2ae7f4dabcc331d456103d4b3d0efae84 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:00:56 -0400 Subject: StitchGroups now contain only Stitches --- lib/stitch_plan/color_block.py | 6 +----- lib/stitch_plan/stitch.py | 20 +++++++++++++------- lib/stitch_plan/stitch_group.py | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/stitch_plan/color_block.py b/lib/stitch_plan/color_block.py index 1cff8aa4..4ff33cdf 100644 --- a/lib/stitch_plan/color_block.py +++ b/lib/stitch_plan/color_block.py @@ -112,15 +112,11 @@ class ColorBlock(object): args = (self.last_stitch.x, self.last_stitch.y) else: raise ValueError("internal error: can't add a command to an empty stitch block") - + self.stitches.append(Stitch(*args, **kwargs)) if isinstance(args[0], Stitch): self.stitches.append(args[0]) elif isinstance(args[0], Point): self.stitches.append(Stitch(args[0].x, args[0].y, *args[1:], **kwargs)) - else: - if not args and self.last_stitch: - args = (self.last_stitch.x, self.last_stitch.y) - self.stitches.append(Stitch(*args, **kwargs)) def add_stitches(self, stitches, *args, **kwargs): for stitch in stitches: diff --git a/lib/stitch_plan/stitch.py b/lib/stitch_plan/stitch.py index ea4423fa..f163d09c 100644 --- a/lib/stitch_plan/stitch.py +++ b/lib/stitch_plan/stitch.py @@ -4,13 +4,25 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. from ..utils.geometry import Point +from copy import deepcopy class Stitch(Point): """A stitch is a Point with extra information telling how to sew it.""" def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, tie_modus=0, no_ties=False, tags=None): - Point.__init__(self, x, y) + if isinstance(x, Stitch): + # Allow creating a Stitch from another Stitch. Attributes passed as + # arguments will override any existing attributes. + vars(self).update(deepcopy(vars(x))) + elif isinstance(x, Point): + # Allow creating a Stitch from a Point + point = x + self.x = point.x + self.y = point.y + else: + Point.__init__(self, x, y) + self.color = color self.jump = jump self.trim = trim @@ -22,12 +34,6 @@ class Stitch(Point): self.add_tags(tags or []) - # Allow creating a Stitch from a Point - if isinstance(x, Point): - point = x - self.x = point.x - self.y = point.y - def __repr__(self): return "Stitch(%s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.x, self.y, diff --git a/lib/stitch_plan/stitch_group.py b/lib/stitch_plan/stitch_group.py index d1e6bae7..548ad892 100644 --- a/lib/stitch_plan/stitch_group.py +++ b/lib/stitch_plan/stitch_group.py @@ -1,5 +1,8 @@ +from .stitch import Stitch + + class StitchGroup: - """A collection of Stitch objects with attached instructions. + """A collection of Stitch objects with attached instructions and attributes. StitchGroups will later be combined to make ColorBlocks, which in turn are combined to make a StitchPlan. Jump stitches are allowed between @@ -11,11 +14,14 @@ class StitchGroup: def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False): self.color = color - self.stitches = stitches or [] self.trim_after = trim_after self.stop_after = stop_after self.tie_modus = tie_modus self.stitch_as_is = stitch_as_is + self.stitches = [] + + if stitches: + self.add_stitches(stitches) def __add__(self, other): if isinstance(other, StitchGroup): @@ -27,7 +33,15 @@ class StitchGroup: # This method allows `len(patch)` and `if patch: return len(self.stitches) + def add_stitches(self, stitches): + for stitch in stitches: + self.add_stitch(stitch) + def add_stitch(self, stitch): + if not isinstance(stitch, Stitch): + # probably a Point + stitch = Stitch(stitch) + self.stitches.append(stitch) def reverse(self): -- cgit v1.2.3 From 4b9df25a9a0203072c6a47b9ce29505561463fbf Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:14:43 -0400 Subject: Stitch + Stitch yields Stitch --- lib/utils/geometry.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/utils/geometry.py b/lib/utils/geometry.py index 1e0c6ad0..bce278ed 100644 --- a/lib/utils/geometry.py +++ b/lib/utils/geometry.py @@ -73,22 +73,22 @@ class Point: return vars(self) def __add__(self, other): - return Point(self.x + other.x, self.y + other.y) + return self.__class__(self.x + other.x, self.y + other.y) def __sub__(self, other): - return Point(self.x - other.x, self.y - other.y) + return self.__class__(self.x - other.x, self.y - other.y) def mul(self, scalar): - return Point(self.x * scalar, self.y * scalar) + return self.__class__(self.x * scalar, self.y * scalar) def __mul__(self, other): if isinstance(other, Point): # dot product return self.x * other.x + self.y * other.y elif isinstance(other, (int, float)): - return Point(self.x * other, self.y * other) + return self.__class__(self.x * other, self.y * other) else: - raise ValueError("cannot multiply Point by %s" % type(other)) + raise ValueError("cannot multiply %s by %s" % (type(self), type(other))) def __neg__(self): return self * -1 @@ -97,16 +97,16 @@ class Point: if isinstance(other, (int, float)): return self.__mul__(other) else: - raise ValueError("cannot multiply Point by %s" % type(other)) + raise ValueError("cannot multiply %s by %s" % (type(self), type(other))) def __div__(self, other): if isinstance(other, (int, float)): return self * (1.0 / other) else: - raise ValueError("cannot divide Point by %s" % type(other)) + raise ValueError("cannot divide %s by %s" % (type(self), type(other))) def __repr__(self): - return "Point(%s,%s)" % (self.x, self.y) + return "%s(%s,%s)" % (type(self), self.x, self.y) def length(self): return math.sqrt(math.pow(self.x, 2.0) + math.pow(self.y, 2.0)) @@ -118,13 +118,13 @@ class Point: return self.mul(1.0 / self.length()) def rotate_left(self): - return Point(-self.y, self.x) + return self.__class__(-self.y, self.x) def rotate(self, angle): - return Point(self.x * math.cos(angle) - self.y * math.sin(angle), self.y * math.cos(angle) + self.x * math.sin(angle)) + return self.__class__(self.x * math.cos(angle) - self.y * math.sin(angle), self.y * math.cos(angle) + self.x * math.sin(angle)) def as_int(self): - return Point(int(round(self.x)), int(round(self.y))) + return self.__class__(int(round(self.x)), int(round(self.y))) def as_tuple(self): return (self.x, self.y) -- cgit v1.2.3 From 173548dee569b0503ba1ddeba5cb8aae74c44c46 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:18:55 -0400 Subject: rename more patch references --- lib/api/stitch_plan.py | 4 ++-- lib/extensions/output.py | 4 ++-- lib/extensions/print_pdf.py | 4 ++-- lib/extensions/stitch_plan_preview.py | 4 ++-- lib/extensions/zip.py | 4 ++-- lib/gui/simulator.py | 4 ++-- lib/stitch_plan/__init__.py | 2 +- lib/stitch_plan/stitch_plan.py | 30 +++++++++++++++--------------- 8 files changed, 28 insertions(+), 28 deletions(-) (limited to 'lib') diff --git a/lib/api/stitch_plan.py b/lib/api/stitch_plan.py index 2011b592..9c9bb4ac 100644 --- a/lib/api/stitch_plan.py +++ b/lib/api/stitch_plan.py @@ -5,7 +5,7 @@ from flask import Blueprint, g, jsonify -from ..stitch_plan import patches_to_stitch_plan +from ..stitch_plan import stitch_groups_to_stitch_plan stitch_plan = Blueprint('stitch_plan', __name__) @@ -19,6 +19,6 @@ def get_stitch_plan(): metadata = g.extension.get_inkstitch_metadata() collapse_len = metadata['collapse_len_mm'] patches = g.extension.elements_to_patches(g.extension.elements) - stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len) + stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) return jsonify(stitch_plan) diff --git a/lib/extensions/output.py b/lib/extensions/output.py index e621f1b6..f993f79f 100644 --- a/lib/extensions/output.py +++ b/lib/extensions/output.py @@ -8,7 +8,7 @@ import sys import tempfile from ..output import write_embroidery_file -from ..stitch_plan import patches_to_stitch_plan +from ..stitch_plan import stitch_groups_to_stitch_plan from .base import InkstitchExtension @@ -53,7 +53,7 @@ class Output(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] patches = self.elements_to_patches(self.elements) - stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False)) + stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False)) temp_file = tempfile.NamedTemporaryFile(suffix=".%s" % self.file_extension, delete=False) diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py index 0facdf92..ed796bc7 100644 --- a/lib/extensions/print_pdf.py +++ b/lib/extensions/print_pdf.py @@ -23,7 +23,7 @@ from werkzeug.serving import make_server from ..gui import open_url from ..i18n import get_languages from ..i18n import translation as inkstitch_translation -from ..stitch_plan import patches_to_stitch_plan +from ..stitch_plan import stitch_groups_to_stitch_plan from ..svg import render_stitch_plan from ..svg.tags import INKSCAPE_GROUPMODE from ..threads import ThreadCatalog @@ -303,7 +303,7 @@ class Print(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] patches = self.elements_to_patches(self.elements) - stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len) + stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) palette = ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette']) overview_svg, color_block_svgs = self.render_svgs(stitch_plan, realistic=False) diff --git a/lib/extensions/stitch_plan_preview.py b/lib/extensions/stitch_plan_preview.py index 40ad6a2a..ac1a4a67 100644 --- a/lib/extensions/stitch_plan_preview.py +++ b/lib/extensions/stitch_plan_preview.py @@ -3,7 +3,7 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -from ..stitch_plan import patches_to_stitch_plan +from ..stitch_plan import stitch_groups_to_stitch_plan from ..svg import render_stitch_plan from .base import InkstitchExtension @@ -24,7 +24,7 @@ class StitchPlanPreview(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] patches = self.elements_to_patches(self.elements) - stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len) + stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) render_stitch_plan(svg, stitch_plan, realistic) # translate stitch plan to the right side of the canvas diff --git a/lib/extensions/zip.py b/lib/extensions/zip.py index 605b4573..3f81af71 100644 --- a/lib/extensions/zip.py +++ b/lib/extensions/zip.py @@ -16,7 +16,7 @@ import pyembroidery from ..i18n import _ from ..output import write_embroidery_file -from ..stitch_plan import patches_to_stitch_plan +from ..stitch_plan import stitch_groups_to_stitch_plan from ..threads import ThreadCatalog from .base import InkstitchExtension @@ -44,7 +44,7 @@ class Zip(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] patches = self.elements_to_patches(self.elements) - stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len) + stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) base_file_name = self.get_base_file_name() path = tempfile.mkdtemp() diff --git a/lib/gui/simulator.py b/lib/gui/simulator.py index 94be66db..d031590b 100644 --- a/lib/gui/simulator.py +++ b/lib/gui/simulator.py @@ -11,7 +11,7 @@ import wx from wx.lib.intctrl import IntCtrl from ..i18n import _ -from ..stitch_plan import patches_to_stitch_plan, stitch_plan_from_file +from ..stitch_plan import stitch_groups_to_stitch_plan, stitch_plan_from_file from ..svg import PIXELS_PER_MM # L10N command label at bottom of simulator window @@ -789,7 +789,7 @@ class SimulatorPreview(Thread): return if patches and not self.refresh_needed.is_set(): - stitch_plan = patches_to_stitch_plan(patches) + stitch_plan = stitch_groups_to_stitch_plan(patches) # GUI stuff needs to happen in the main thread, so we ask the main # thread to call refresh_simulator(). diff --git a/lib/stitch_plan/__init__.py b/lib/stitch_plan/__init__.py index ba4729bb..d4b43ace 100644 --- a/lib/stitch_plan/__init__.py +++ b/lib/stitch_plan/__init__.py @@ -3,7 +3,7 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -from .stitch_plan import patches_to_stitch_plan, StitchPlan +from .stitch_plan import stitch_groups_to_stitch_plan, StitchPlan from .color_block import ColorBlock from .stitch_group import StitchGroup from .stitch import Stitch diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py index f5981fb9..7e7621c1 100644 --- a/lib/stitch_plan/stitch_plan.py +++ b/lib/stitch_plan/stitch_plan.py @@ -8,49 +8,49 @@ from .color_block import ColorBlock from ..svg import PIXELS_PER_MM -def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False): # noqa: C901 +def stitch_groups_to_stitch_plan(stitch_groups, collapse_len=None, disable_ties=False): # noqa: C901 - """Convert a collection of inkstitch.element.StitchGroup objects to a StitchPlan. + """Convert a collection of StitchGroups to a StitchPlan. * applies instructions embedded in the StitchGroup such as trim_after and stop_after * adds tie-ins and tie-offs - * adds jump-stitches between patches if necessary + * adds jump-stitches between stitch_group if necessary """ if collapse_len is None: collapse_len = 3.0 collapse_len = collapse_len * PIXELS_PER_MM stitch_plan = StitchPlan() - color_block = stitch_plan.new_color_block(color=patches[0].color) + color_block = stitch_plan.new_color_block(color=stitch_groups[0].color) - for patch in patches: - if not patch.stitches: + for stitch_group in stitch_groups: + if not stitch_group.stitches: continue - if color_block.color != patch.color: + if color_block.color != stitch_group.color: if len(color_block) == 0: # We just processed a stop, which created a new color block. # We'll just claim this new block as ours: - color_block.color = patch.color + color_block.color = stitch_group.color else: # end the previous block with a color change color_block.add_stitch(color_change=True) # make a new block of our color - color_block = stitch_plan.new_color_block(color=patch.color) + color_block = stitch_plan.new_color_block(color=stitch_group.color) # always start a color with a JUMP to the first stitch position - color_block.add_stitch(patch.stitches[0], jump=True) + color_block.add_stitch(stitch_group.stitches[0], jump=True) else: - if len(color_block) and (patch.stitches[0] - color_block.stitches[-1]).length() > collapse_len: - color_block.add_stitch(patch.stitches[0], jump=True) + if len(color_block) and (stitch_group.stitches[0] - color_block.stitches[-1]).length() > collapse_len: + color_block.add_stitch(stitch_group.stitches[0], jump=True) - color_block.add_stitches(stitches=patch.stitches, tie_modus=patch.tie_modus, no_ties=patch.stitch_as_is) + color_block.add_stitches(stitches=stitch_group.stitches, tie_modus=stitch_group.tie_modus, no_ties=stitch_group.stitch_as_is) - if patch.trim_after: + if stitch_group.trim_after: color_block.add_stitch(trim=True) - if patch.stop_after: + if stitch_group.stop_after: color_block.add_stitch(stop=True) color_block = stitch_plan.new_color_block(color_block.color) -- cgit v1.2.3 From b1af926ea6018b869d2401aeece46318b88d9a5d Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:34:07 -0400 Subject: add tags to fill stitches --- lib/stitches/fill.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/stitches/fill.py b/lib/stitches/fill.py index 5aead9c7..0ac15657 100644 --- a/lib/stitches/fill.py +++ b/lib/stitches/fill.py @@ -7,6 +7,7 @@ import math import shapely +from ..stitch_plan import Stitch from ..svg import PIXELS_PER_MM from ..utils import Point as InkstitchPoint from ..utils import cache @@ -65,8 +66,8 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge # tile with each other. That's important because we often get # abutting fill regions from pull_runs(). - beg = InkstitchPoint(*beg) - end = InkstitchPoint(*end) + beg = Stitch(*beg, tags=('fill_row_start',)) + end = Stitch(*end, tags=('fill_row_end')) row_direction = (end - beg).unit() segment_length = (end - beg).length() @@ -85,11 +86,13 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge offset = (first_stitch - beg).length() while offset < segment_length: - stitches.append(beg + offset * row_direction) + stitches.append(Stitch(beg + offset * row_direction, tags=('fill_row'))) offset += max_stitch_length if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM and not skip_last: stitches.append(end) + else: + stitches[-1].add_tag('fill_row_end') def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=None, flip=False): -- cgit v1.2.3 From 923ff3cb97c764f9999ac908c9b3aa321fd02301 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:37:17 -0400 Subject: fix more patch references --- lib/api/stitch_plan.py | 2 +- lib/elements/auto_fill.py | 2 +- lib/elements/clone.py | 4 ++-- lib/elements/element.py | 6 +++--- lib/elements/empty_d_object.py | 2 +- lib/elements/fill.py | 2 +- lib/elements/image.py | 2 +- lib/elements/pattern.py | 2 +- lib/elements/polyline.py | 2 +- lib/elements/satin_column.py | 2 +- lib/elements/stroke.py | 2 +- lib/elements/text.py | 2 +- lib/extensions/base.py | 2 +- lib/extensions/output.py | 2 +- lib/extensions/print_pdf.py | 2 +- lib/extensions/stitch_plan_preview.py | 2 +- lib/extensions/zip.py | 2 +- 17 files changed, 20 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/api/stitch_plan.py b/lib/api/stitch_plan.py index 9c9bb4ac..6d64d781 100644 --- a/lib/api/stitch_plan.py +++ b/lib/api/stitch_plan.py @@ -18,7 +18,7 @@ def get_stitch_plan(): metadata = g.extension.get_inkstitch_metadata() collapse_len = metadata['collapse_len_mm'] - patches = g.extension.elements_to_patches(g.extension.elements) + patches = g.extension.elements_to_stitch_groups(g.extension.elements) stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) return jsonify(stitch_plan) diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py index e72af1ef..f5558cbb 100644 --- a/lib/elements/auto_fill.py +++ b/lib/elements/auto_fill.py @@ -213,7 +213,7 @@ class AutoFill(Fill): else: return None - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): stitches = [] starting_point = self.get_starting_point(last_patch) diff --git a/lib/elements/clone.py b/lib/elements/clone.py index 6dafa63d..a9e10d94 100644 --- a/lib/elements/clone.py +++ b/lib/elements/clone.py @@ -93,7 +93,7 @@ class Clone(EmbroideryElement): return elements - def to_patches(self, last_patch=None): + def to_stitch_groups(self, last_patch=None): patches = [] source_node = get_clone_source(self.node) @@ -123,7 +123,7 @@ class Clone(EmbroideryElement): elements = self.clone_to_element(self.node) for element in elements: - patches.extend(element.to_patches(last_patch)) + patches.extend(element.to_stitch_groups(last_patch)) return patches diff --git a/lib/elements/element.py b/lib/elements/element.py index a3577a5c..f06982b2 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -301,13 +301,13 @@ class EmbroideryElement(object): def stop_after(self): return self.get_boolean_param('stop_after', False) - def to_patches(self, last_patch): - raise NotImplementedError("%s must implement to_patches()" % self.__class__.__name__) + def to_stitch_groups(self, last_patch): + raise NotImplementedError("%s must implement to_stitch_groups()" % self.__class__.__name__) def embroider(self, last_patch): self.validate() - patches = self.to_patches(last_patch) + patches = self.to_stitch_groups(last_patch) apply_patterns(patches, self.node) for patch in patches: diff --git a/lib/elements/empty_d_object.py b/lib/elements/empty_d_object.py index 19fb58a4..3c24f333 100644 --- a/lib/elements/empty_d_object.py +++ b/lib/elements/empty_d_object.py @@ -23,5 +23,5 @@ class EmptyDObject(EmbroideryElement): label = self.node.get(INKSCAPE_LABEL) or self.node.get("id") yield EmptyD((0, 0), label) - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): return [] diff --git a/lib/elements/fill.py b/lib/elements/fill.py index 2d2ae2ed..442922b6 100644 --- a/lib/elements/fill.py +++ b/lib/elements/fill.py @@ -190,7 +190,7 @@ class Fill(EmbroideryElement): else: yield InvalidShapeError((x, y)) - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): stitch_lists = legacy_fill(self.shape, self.angle, self.row_spacing, diff --git a/lib/elements/image.py b/lib/elements/image.py index 0828b5ef..73a46871 100644 --- a/lib/elements/image.py +++ b/lib/elements/image.py @@ -29,5 +29,5 @@ class ImageObject(EmbroideryElement): def validation_warnings(self): yield ImageTypeWarning(self.center()) - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): return [] diff --git a/lib/elements/pattern.py b/lib/elements/pattern.py index 95ce81a1..4b92d366 100644 --- a/lib/elements/pattern.py +++ b/lib/elements/pattern.py @@ -29,5 +29,5 @@ class PatternObject(EmbroideryElement): repr_point = next(inkex.Path(self.parse_path()).end_points) yield PatternWarning(repr_point) - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): return [] diff --git a/lib/elements/polyline.py b/lib/elements/polyline.py index aeae17d9..c7a9ea48 100644 --- a/lib/elements/polyline.py +++ b/lib/elements/polyline.py @@ -101,7 +101,7 @@ class Polyline(EmbroideryElement): def validation_warnings(self): yield PolylineWarning(self.points[0]) - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): patch = StitchGroup(color=self.color) for stitch in self.stitches: diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index e066f3fb..b11bb2c3 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -828,7 +828,7 @@ class SatinColumn(EmbroideryElement): points.append(Point(split_point.x, split_point.y)) return [points, split_count] - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): # Stitch a variable-width satin column, zig-zagging between two paths. # The algorithm will draw zigzags between each consecutive pair of diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index 9fcb4e21..763167ad 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -193,7 +193,7 @@ class Stroke(EmbroideryElement): return StitchGroup(self.color, stitches) - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): patches = [] for path in self.paths: diff --git a/lib/elements/text.py b/lib/elements/text.py index dbf76c85..8a3846c0 100644 --- a/lib/elements/text.py +++ b/lib/elements/text.py @@ -29,5 +29,5 @@ class TextObject(EmbroideryElement): def validation_warnings(self): yield TextTypeWarning(self.pointer()) - def to_patches(self, last_patch): + def to_stitch_groups(self, last_patch): return [] diff --git a/lib/extensions/base.py b/lib/extensions/base.py index 476e4969..828e3685 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -188,7 +188,7 @@ class InkstitchExtension(inkex.Effect): selected.append(node) return selected - def elements_to_patches(self, elements): + def elements_to_stitch_groups(self, elements): patches = [] for element in elements: if patches: diff --git a/lib/extensions/output.py b/lib/extensions/output.py index f993f79f..7cc12ee0 100644 --- a/lib/extensions/output.py +++ b/lib/extensions/output.py @@ -52,7 +52,7 @@ class Output(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] - patches = self.elements_to_patches(self.elements) + patches = self.elements_to_stitch_groups(self.elements) stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False)) temp_file = tempfile.NamedTemporaryFile(suffix=".%s" % self.file_extension, delete=False) diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py index ed796bc7..e5cb25d8 100644 --- a/lib/extensions/print_pdf.py +++ b/lib/extensions/print_pdf.py @@ -302,7 +302,7 @@ class Print(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] - patches = self.elements_to_patches(self.elements) + patches = self.elements_to_stitch_groups(self.elements) stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) palette = ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette']) diff --git a/lib/extensions/stitch_plan_preview.py b/lib/extensions/stitch_plan_preview.py index ac1a4a67..c50fa738 100644 --- a/lib/extensions/stitch_plan_preview.py +++ b/lib/extensions/stitch_plan_preview.py @@ -23,7 +23,7 @@ class StitchPlanPreview(InkstitchExtension): realistic = False self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] - patches = self.elements_to_patches(self.elements) + patches = self.elements_to_stitch_groups(self.elements) stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) render_stitch_plan(svg, stitch_plan, realistic) diff --git a/lib/extensions/zip.py b/lib/extensions/zip.py index 3f81af71..22654560 100644 --- a/lib/extensions/zip.py +++ b/lib/extensions/zip.py @@ -43,7 +43,7 @@ class Zip(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] - patches = self.elements_to_patches(self.elements) + patches = self.elements_to_stitch_groups(self.elements) stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len) base_file_name = self.get_base_file_name() -- cgit v1.2.3 From 3b7994c01add202c8f3475956f093a143890160f Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:38:41 -0400 Subject: add headers --- lib/stitch_plan/color_block.py | 5 +++++ lib/stitch_plan/stitch_group.py | 5 +++++ 2 files changed, 10 insertions(+) (limited to 'lib') diff --git a/lib/stitch_plan/color_block.py b/lib/stitch_plan/color_block.py index 4ff33cdf..86edaff2 100644 --- a/lib/stitch_plan/color_block.py +++ b/lib/stitch_plan/color_block.py @@ -1,3 +1,8 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + from .stitch import Stitch from ..threads import ThreadColor from ..utils.geometry import Point diff --git a/lib/stitch_plan/stitch_group.py b/lib/stitch_plan/stitch_group.py index 548ad892..ee077f26 100644 --- a/lib/stitch_plan/stitch_group.py +++ b/lib/stitch_plan/stitch_group.py @@ -1,3 +1,8 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + from .stitch import Stitch -- cgit v1.2.3 From b411305c6742ff07dbddbdec07f1338f5beaf31b Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 18:38:57 -0400 Subject: use tags to decide which stitches to keep --- lib/patterns.py | 16 ++++++++-------- lib/stitches/fill.py | 9 ++------- 2 files changed, 10 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/patterns.py b/lib/patterns.py index 5ae763fc..bb19f2b4 100644 --- a/lib/patterns.py +++ b/lib/patterns.py @@ -3,10 +3,12 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. +import math + import inkex from shapely import geometry as shgeo -import math +from .stitch_plan import Stitch from .svg.tags import EMBROIDERABLE_TAGS from .utils import Point @@ -34,7 +36,7 @@ def _apply_stroke_patterns(patterns, patches): continue intersection_points = _get_pattern_points(stitch, patch.stitches[i+1], pattern) for point in intersection_points: - patch_points.append(point) + patch_points.append(Stitch(point, tags=('pattern_point',))) patch.stitches = patch_points @@ -43,16 +45,14 @@ def _apply_fill_patterns(patterns, patches): for patch in patches: patch_points = [] for i, stitch in enumerate(patch.stitches): - # keep points outside the fill patter if not shgeo.Point(stitch).within(pattern): + # keep points outside the fill patter patch_points.append(stitch) - # keep start and end points elif i - 1 < 0 or i >= len(patch.stitches) - 1: + # keep start and end points patch_points.append(stitch) - # keep points if they have an angle - # the necessary angle can be variable with certain stitch types (later on) - # but they don't need to use filled patterns for those - elif not 179 < get_angle(patch.stitches[i-1], stitch, patch.stitches[i+1]) < 181: + elif stitch.has_tag('fill_row_start') or stitch.has_tag('fill_row_end'): + # keep points if they are the start or end of a fill stitch row patch_points.append(stitch) patch.stitches = patch_points diff --git a/lib/stitches/fill.py b/lib/stitches/fill.py index 0ac15657..d134be32 100644 --- a/lib/stitches/fill.py +++ b/lib/stitches/fill.py @@ -67,15 +67,12 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge # abutting fill regions from pull_runs(). beg = Stitch(*beg, tags=('fill_row_start',)) - end = Stitch(*end, tags=('fill_row_end')) + end = Stitch(*end, tags=('fill_row_end',)) row_direction = (end - beg).unit() segment_length = (end - beg).length() - # only stitch the first point if it's a reasonable distance away from the - # last stitch - if not stitches or (beg - stitches[-1]).length() > 0.5 * PIXELS_PER_MM: - stitches.append(beg) + stitches.append(beg) first_stitch = adjust_stagger(beg, angle, row_spacing, max_stitch_length, staggers) @@ -91,8 +88,6 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM and not skip_last: stitches.append(end) - else: - stitches[-1].add_tag('fill_row_end') def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=None, flip=False): -- cgit v1.2.3 From d807b12870515e23bd9ac4f8ce024a3070de2805 Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Sun, 8 Aug 2021 21:51:29 +0200 Subject: remove get_angle --- lib/patterns.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/patterns.py b/lib/patterns.py index bb19f2b4..d282dc9c 100644 --- a/lib/patterns.py +++ b/lib/patterns.py @@ -3,8 +3,6 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -import math - import inkex from shapely import geometry as shgeo @@ -46,7 +44,7 @@ def _apply_fill_patterns(patterns, patches): patch_points = [] for i, stitch in enumerate(patch.stitches): if not shgeo.Point(stitch).within(pattern): - # keep points outside the fill patter + # keep points outside the fill pattern patch_points.append(stitch) elif i - 1 < 0 or i >= len(patch.stitches) - 1: # keep start and end points @@ -97,8 +95,3 @@ def _get_pattern_points(first, second, pattern): # sort points after their distance to first points.sort(key=lambda point: point.distance(first)) return points - - -def get_angle(a, b, c): - ang = math.degrees(math.atan2(c[1]-b[1], c[0]-b[0]) - math.atan2(a[1]-b[1], a[0]-b[0])) - return ang + 360 if ang < 0 else ang -- cgit v1.2.3 From dd865008356d1e04b29a5eb59a8480900f255628 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sun, 15 Aug 2021 17:24:59 -0400 Subject: keep underlay, underpath, and border travel --- lib/elements/auto_fill.py | 82 +++++++++++++++++++++++------------------ lib/patterns.py | 6 +++ lib/stitch_plan/stitch_group.py | 13 ++++++- lib/stitches/auto_fill.py | 8 +++- 4 files changed, 71 insertions(+), 38 deletions(-) (limited to 'lib') diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py index f5558cbb..fbbd86d3 100644 --- a/lib/elements/auto_fill.py +++ b/lib/elements/auto_fill.py @@ -214,7 +214,7 @@ class AutoFill(Fill): return None def to_stitch_groups(self, last_patch): - stitches = [] + stitch_groups = [] starting_point = self.get_starting_point(last_patch) ending_point = self.get_ending_point() @@ -222,29 +222,40 @@ class AutoFill(Fill): try: if self.fill_underlay: for i in range(len(self.fill_underlay_angle)): - stitches.extend(auto_fill(self.underlay_shape, - self.fill_underlay_angle[i], - self.fill_underlay_row_spacing, - self.fill_underlay_row_spacing, - self.fill_underlay_max_stitch_length, - self.running_stitch_length, - self.staggers, - self.fill_underlay_skip_last, - starting_point, - underpath=self.underlay_underpath)) - starting_point = stitches[-1] - - stitches.extend(auto_fill(self.fill_shape, - self.angle, - self.row_spacing, - self.end_row_spacing, - self.max_stitch_length, - self.running_stitch_length, - self.staggers, - self.skip_last, - starting_point, - ending_point, - self.underpath)) + underlay = StitchGroup( + color=self.color, + tags=("auto_fill", "auto_fill_underlay"), + stitches=auto_fill( + self.underlay_shape, + self.fill_underlay_angle[i], + self.fill_underlay_row_spacing, + self.fill_underlay_row_spacing, + self.fill_underlay_max_stitch_length, + self.running_stitch_length, + self.staggers, + self.fill_underlay_skip_last, + starting_point, + underpath=self.underlay_underpath)) + stitch_groups.append(underlay) + + starting_point = underlay.stitches[-1] + + stitch_group = StitchGroup( + color=self.color, + tags=("auto_fill", "auto_fill_top"), + stitches=auto_fill( + self.fill_shape, + self.angle, + self.row_spacing, + self.end_row_spacing, + self.max_stitch_length, + self.running_stitch_length, + self.staggers, + self.skip_last, + starting_point, + ending_point, + self.underpath)) + stitch_groups.append(stitch_group) except Exception: if hasattr(sys, 'gettrace') and sys.gettrace(): # if we're debugging, let the exception bubble up @@ -262,18 +273,19 @@ class AutoFill(Fill): self.fatal(message) - return [StitchGroup(stitches=stitches, color=self.color)] + return stitch_groups - def validation_warnings(self): - if self.shape.area < 20: - label = self.node.get(INKSCAPE_LABEL) or self.node.get("id") - yield SmallShapeWarning(self.shape.centroid, label) - if self.shrink_or_grow_shape(self.expand, True).is_empty: - yield ExpandWarning(self.shape.centroid) +def validation_warnings(self): + if self.shape.area < 20: + label = self.node.get(INKSCAPE_LABEL) or self.node.get("id") + yield SmallShapeWarning(self.shape.centroid, label) - if self.shrink_or_grow_shape(-self.fill_underlay_inset, True).is_empty: - yield UnderlayInsetWarning(self.shape.centroid) + if self.shrink_or_grow_shape(self.expand, True).is_empty: + yield ExpandWarning(self.shape.centroid) - for warning in super(AutoFill, self).validation_warnings(): - yield warning + if self.shrink_or_grow_shape(-self.fill_underlay_inset, True).is_empty: + yield UnderlayInsetWarning(self.shape.centroid) + + for warning in super(AutoFill, self).validation_warnings(): + yield warning diff --git a/lib/patterns.py b/lib/patterns.py index d282dc9c..70700f18 100644 --- a/lib/patterns.py +++ b/lib/patterns.py @@ -52,6 +52,12 @@ def _apply_fill_patterns(patterns, patches): elif stitch.has_tag('fill_row_start') or stitch.has_tag('fill_row_end'): # keep points if they are the start or end of a fill stitch row patch_points.append(stitch) + elif stitch.has_tag('auto_fill') and not stitch.has_tag('auto_fill_top'): + # keep auto-fill underlay + patch_points.append(stitch) + elif stitch.has_tag('auto_fill_travel'): + # keep travel stitches (underpath or travel around the border) + patch_points.append(stitch) patch.stitches = patch_points diff --git a/lib/stitch_plan/stitch_group.py b/lib/stitch_plan/stitch_group.py index ee077f26..98d9799e 100644 --- a/lib/stitch_plan/stitch_group.py +++ b/lib/stitch_plan/stitch_group.py @@ -17,7 +17,7 @@ class StitchGroup: between them by the stitch plan generation code. """ - def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False): + def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False, tags=None): self.color = color self.trim_after = trim_after self.stop_after = stop_after @@ -28,6 +28,9 @@ class StitchGroup: if stitches: self.add_stitches(stitches) + if tags: + self.add_tags(tags) + def __add__(self, other): if isinstance(other, StitchGroup): return StitchGroup(self.color, self.stitches + other.stitches) @@ -51,3 +54,11 @@ class StitchGroup: def reverse(self): return StitchGroup(self.color, self.stitches[::-1]) + + def add_tags(self, tags): + for stitch in self.stitches: + stitch.add_tags(tags) + + def add_tag(self, tag): + for stitch in self.stitches: + stitch.add_tag(tag) diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 07361f13..160d927e 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -14,6 +14,7 @@ from shapely.ops import snap from shapely.strtree import STRtree from ..debug import debug +from ..stitch_plan import Stitch from ..svg import PIXELS_PER_MM from ..utils.geometry import Point as InkstitchPoint from ..utils.geometry import line_string_to_point_list @@ -592,9 +593,12 @@ def travel(travel_graph, start, end, running_stitch_length, skip_last): """Create stitches to get from one point on an outline of the shape to another.""" path = networkx.shortest_path(travel_graph, start, end, weight='weight') - path = [InkstitchPoint(*p) for p in path] + path = [Stitch(*p) for p in path] stitches = running_stitch(path, running_stitch_length) + for stitch in stitches: + stitch.add_tag('auto_fill_travel') + # The path's first stitch will start at the end of a row of stitches. We # don't want to double that last stitch, so we'd like to skip it. if skip_last and len(path) > 2: @@ -619,7 +623,7 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, # If the very first stitch is travel, we'll omit it in travel(), so add it here. if not path[0].is_segment(): - stitches.append(InkstitchPoint(*path[0].nodes[0])) + stitches.append(Stitch(*path[0].nodes[0])) for edge in path: if edge.is_segment(): -- cgit v1.2.3 From b49f7d28314f30727f9f963bddb795b88a95f2bd Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Mon, 16 Aug 2021 16:30:22 +0200 Subject: keep satin column edges --- lib/elements/satin_column.py | 17 +++++++++++++++-- lib/patterns.py | 3 +++ 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index b11bb2c3..cf31c2af 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -717,7 +717,10 @@ class SatinColumn(EmbroideryElement): # other. forward, back = self.plot_points_on_rails(self.contour_underlay_stitch_length, -self.contour_underlay_inset) - return StitchGroup(color=self.color, stitches=(forward + list(reversed(back)))) + return StitchGroup( + color=self.color, + tags=("satin_column", "satin_column_underlay", "satin_contour_underlay"), + stitches=(forward + list(reversed(back)))) def do_center_walk(self): # Center walk underlay is just a running stitch down and back on the @@ -726,7 +729,10 @@ class SatinColumn(EmbroideryElement): # Do it like contour underlay, but inset all the way to the center. forward, back = self.plot_points_on_rails(self.center_walk_underlay_stitch_length, -100000) - return StitchGroup(color=self.color, stitches=(forward + list(reversed(back)))) + return StitchGroup( + color=self.color, + tags=("satin_column", "satin_column_underlay", "satin_center_walk"), + stitches=(forward + list(reversed(back)))) def do_zigzag_underlay(self): # zigzag underlay, usually done at a much lower density than the @@ -754,6 +760,7 @@ class SatinColumn(EmbroideryElement): for point in chain.from_iterable(zip(*sides)): patch.add_stitch(point) + patch.add_tags(("satin_column", "satin_column_underlay", "satin_zigzag_underlay")) return patch def do_satin(self): @@ -776,6 +783,7 @@ class SatinColumn(EmbroideryElement): for point in chain.from_iterable(zip(*sides)): patch.add_stitch(point) + patch.add_tags(("satin_column", "satin_column_edge")) return patch def do_e_stitch(self): @@ -797,6 +805,7 @@ class SatinColumn(EmbroideryElement): patch.add_stitch(right) patch.add_stitch(left) + patch.add_tags(("satin_column", "e_stitch")) return patch def do_split_stitch(self): @@ -805,10 +814,13 @@ class SatinColumn(EmbroideryElement): sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation) for i, (left, right) in enumerate(zip(*sides)): patch.add_stitch(left) + patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) points, count = self._get_split_points(left, right) for point in points: patch.add_stitch(point) + patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch")) patch.add_stitch(right) + patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) # 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 @@ -816,6 +828,7 @@ class SatinColumn(EmbroideryElement): points, count = self._get_split_points(right, sides[0][i+1], count) for point in points: patch.add_stitch(point) + patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch")) return patch def _get_split_points(self, left, right, count=None): diff --git a/lib/patterns.py b/lib/patterns.py index 70700f18..8a0c8449 100644 --- a/lib/patterns.py +++ b/lib/patterns.py @@ -58,6 +58,9 @@ def _apply_fill_patterns(patterns, patches): elif stitch.has_tag('auto_fill_travel'): # keep travel stitches (underpath or travel around the border) patch_points.append(stitch) + elif stitch.has_tag('satin_column') and not stitch.has_tag('satin_split_stitch'): + # keep satin column stitches unless they are split stitches + patch_points.append(stitch) patch.stitches = patch_points -- cgit v1.2.3