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 + templates/apply_satin_pattern.xml | 17 ++++++ 6 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 lib/extensions/apply_satin_pattern.py create mode 100644 templates/apply_satin_pattern.xml 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 diff --git a/templates/apply_satin_pattern.xml b/templates/apply_satin_pattern.xml new file mode 100644 index 00000000..e52fb1a4 --- /dev/null +++ b/templates/apply_satin_pattern.xml @@ -0,0 +1,17 @@ + + + {% trans %}Apply Satin Pattern{% endtrans %} + org.inkstitch.apply_satin_pattern.{{ locale }} + apply_satin_pattern + + all + + + + + + + + -- 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 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 ++-- symbols/inkstitch.svg | 23 ++++++++++ templates/apply_satin_pattern.xml | 17 -------- 8 files changed, 49 insertions(+), 106 deletions(-) delete mode 100644 lib/extensions/apply_satin_pattern.py delete mode 100644 templates/apply_satin_pattern.xml 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 diff --git a/symbols/inkstitch.svg b/symbols/inkstitch.svg index 4a67ae1c..f380d106 100644 --- a/symbols/inkstitch.svg +++ b/symbols/inkstitch.svg @@ -58,6 +58,21 @@ id="title9425">Ink/Stitch Commands + + Pattern group + + + + <use + xlink:href="#inkstitch_pattern_group" + id="use9462" + x="0" + y="0" + width="100%" + height="100%" + transform="translate(189.0002,37.680421)" /> <use xlink:href="#inkstitch_trim" id="use9461" diff --git a/templates/apply_satin_pattern.xml b/templates/apply_satin_pattern.xml deleted file mode 100644 index e52fb1a4..00000000 --- a/templates/apply_satin_pattern.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> - <name>{% trans %}Apply Satin Pattern{% endtrans %}</name> - <id>org.inkstitch.apply_satin_pattern.{{ locale }}</id> - <param name="extension" type="string" gui-hidden="true">apply_satin_pattern</param> - <effect> - <object-type>all</object-type> - <effects-menu> - <submenu name="Ink/Stitch"> - <submenu name="{% trans %}Satin Tools{% endtrans %}" /> - </submenu> - </effects-menu> - </effect> - <script> - {{ command_tag | safe }} - </script> -</inkscape-extension> -- cgit v1.2.3 From 2f54ff2a436f2774bfdc730b6e95c43f18ed81ac Mon Sep 17 00:00:00 2001 From: Kaalleen <reni@allenka.de> 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 +++++++-- templates/group_commands.xml | 21 ++++++++++++++++++++ 7 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 lib/extensions/group_commands.py create mode 100644 templates/group_commands.xml 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())) diff --git a/templates/group_commands.xml b/templates/group_commands.xml new file mode 100644 index 00000000..2a67c5b7 --- /dev/null +++ b/templates/group_commands.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <name>{% trans %}Add Group Commands{% endtrans %}</name> + <id>org.inkstitch.group_commands.{{ locale }}</id> + <param name="description" type="description">{% trans %}Commands will be added to the currently-selected group.{% endtrans %}</param> + {% for command, description in group_commands %} + <param name="{{ command }}" type="boolean" _gui-text="{{ _(description) }}">false</param> + {% endfor %} + <param name="extension" type="string" gui-hidden="true">group_commands</param> + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu name="Ink/Stitch"> + <submenu name="{% trans %}Commands{% endtrans %}" /> + </submenu> + </effects-menu> + </effect> + <script> + {{ command_tag | safe }} + </script> +</inkscape-extension> -- cgit v1.2.3 From ecacb9829e9c2b7050486707211f9d176aafdf75 Mon Sep 17 00:00:00 2001 From: Kaalleen <reni@allenka.de> 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 ++---- templates/apply_pattern.xml | 15 ++++++++++ templates/group_commands.xml | 21 -------------- 11 files changed, 100 insertions(+), 93 deletions(-) create mode 100644 lib/extensions/apply_pattern.py delete mode 100644 lib/extensions/group_commands.py create mode 100644 templates/apply_pattern.xml delete mode 100644 templates/group_commands.xml 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 = """<marker + refX="10" + refY="5" + orient="auto" + id="inkstitch-pattern-marker"> + <g + id="inkstitch-pattern-group"> + <path + style="fill:#fafafa;stroke:#ff5500;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1, 1;stroke-dashoffset:0;stroke-opacity:1;fill-opacity:0.8;" + d="M 10.12911,5.2916678 A 4.8374424,4.8374426 0 0 1 5.2916656,10.12911 4.8374424,4.8374426 0 0 1 0.45422399,5.2916678 4.8374424,4.8374426 0 0 1 5.2916656,0.45422399 4.8374424,4.8374426 0 0 1 10.12911,5.2916678 Z" + id="inkstitch-pattern-marker-circle" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.4;stroke-linecap:round;stroke-miterlimit:4;" + id="inkstitch-pattern-marker-spiral" + d="M 4.9673651,5.7245662 C 4.7549848,5.7646159 4.6247356,5.522384 4.6430021,5.3419847 4.6765851,5.0103151 5.036231,4.835347 5.3381858,4.8987426 5.7863901,4.9928495 6.0126802,5.4853625 5.9002872,5.9065088 5.7495249,6.4714237 5.1195537,6.7504036 4.5799191,6.5874894 3.898118,6.3816539 3.5659013,5.6122905 3.7800789,4.9545192 4.0402258,4.1556558 4.9498996,3.7699484 5.7256318,4.035839 6.6416744,4.3498087 7.0810483,5.4003986 6.7631909,6.2939744 6.395633,7.3272552 5.2038143,7.8204128 4.1924535,7.4503931 3.0418762,7.0294421 2.4948761,5.6961604 2.9171752,4.567073 3.3914021,3.2991406 4.8663228,2.6982592 6.1130974,3.1729158 7.4983851,3.7003207 8.1531869,5.3169977 7.6260947,6.6814205 7.0456093,8.1841025 5.2870784,8.8928844 3.8050073,8.3132966 2.1849115,7.6797506 1.4221671,5.7793073 2.0542715,4.1796074 2.7408201,2.4420977 4.7832541,1.6253548 6.5005435,2.310012 8.3554869,3.0495434 9.2262638,5.2339874 8.4890181,7.0688861 8.4256397,7.2266036 8.3515789,7.379984 8.2675333,7.5277183" /> + </g> + </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())) diff --git a/templates/apply_pattern.xml b/templates/apply_pattern.xml new file mode 100644 index 00000000..cbd83dbc --- /dev/null +++ b/templates/apply_pattern.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <name>{% trans %}Apply Pattern{% endtrans %}</name> + <id>org.inkstitch.apply_pattern.{{ locale }}</id> + <param name="extension" type="string" gui-hidden="true">apply_pattern</param> + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu name="Ink/Stitch" /> + </effects-menu> + </effect> + <script> + {{ command_tag | safe }} + </script> +</inkscape-extension> diff --git a/templates/group_commands.xml b/templates/group_commands.xml deleted file mode 100644 index 2a67c5b7..00000000 --- a/templates/group_commands.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> - <name>{% trans %}Add Group Commands{% endtrans %}</name> - <id>org.inkstitch.group_commands.{{ locale }}</id> - <param name="description" type="description">{% trans %}Commands will be added to the currently-selected group.{% endtrans %}</param> - {% for command, description in group_commands %} - <param name="{{ command }}" type="boolean" _gui-text="{{ _(description) }}">false</param> - {% endfor %} - <param name="extension" type="string" gui-hidden="true">group_commands</param> - <effect> - <object-type>all</object-type> - <effects-menu> - <submenu name="Ink/Stitch"> - <submenu name="{% trans %}Commands{% endtrans %}" /> - </submenu> - </effects-menu> - </effect> - <script> - {{ command_tag | safe }} - </script> -</inkscape-extension> -- cgit v1.2.3 From b6ce6ccd814fdbaa754c60ca69901dd1c855d8b6 Mon Sep 17 00:00:00 2001 From: Kaalleen <reni@allenka.de> 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 - symbols/inkstitch.svg | 23 ----------------------- 3 files changed, 27 deletions(-) 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', diff --git a/symbols/inkstitch.svg b/symbols/inkstitch.svg index f380d106..4a67ae1c 100644 --- a/symbols/inkstitch.svg +++ b/symbols/inkstitch.svg @@ -58,21 +58,6 @@ id="title9425">Ink/Stitch Commands - - Pattern group - - - - <use - xlink:href="#inkstitch_pattern_group" - id="use9462" - x="0" - y="0" - width="100%" - height="100%" - transform="translate(189.0002,37.680421)" /> <use xlink:href="#inkstitch_trim" id="use9461" -- cgit v1.2.3 From aa24b3a3a215cc536bdac94d8ace667f692ffc45 Mon Sep 17 00:00:00 2001 From: Kaalleen <reni@allenka.de> 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(-) 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 <reni@allenka.de> 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(-) 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 <reni@allenka.de> 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(-) 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 <reni@allenka.de> 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(+) 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 <reni@allenka.de> 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 ++++++++++++++++++++++++++++++++++ templates/apply_pattern.xml | 15 -------- templates/selection_to_pattern.xml | 17 +++++++++ 10 files changed, 151 insertions(+), 136 deletions(-) delete mode 100644 lib/extensions/apply_pattern.py create mode 100644 lib/extensions/selection_to_pattern.py create mode 100644 lib/patterns.py delete mode 100644 templates/apply_pattern.xml create mode 100644 templates/selection_to_pattern.xml 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 = """<marker - refX="10" - refY="5" - orient="auto" - id="inkstitch-pattern-marker"> - <g - id="inkstitch-pattern-group"> - <path - style="fill:#fafafa;stroke:#ff5500;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1, 1;stroke-dashoffset:0;stroke-opacity:1;fill-opacity:0.8;" - d="M 10.12911,5.2916678 A 4.8374424,4.8374426 0 0 1 5.2916656,10.12911 4.8374424,4.8374426 0 0 1 0.45422399,5.2916678 4.8374424,4.8374426 0 0 1 5.2916656,0.45422399 4.8374424,4.8374426 0 0 1 10.12911,5.2916678 Z" - id="inkstitch-pattern-marker-circle" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.4;stroke-linecap:round;stroke-miterlimit:4;" - id="inkstitch-pattern-marker-spiral" - d="M 4.9673651,5.7245662 C 4.7549848,5.7646159 4.6247356,5.522384 4.6430021,5.3419847 4.6765851,5.0103151 5.036231,4.835347 5.3381858,4.8987426 5.7863901,4.9928495 6.0126802,5.4853625 5.9002872,5.9065088 5.7495249,6.4714237 5.1195537,6.7504036 4.5799191,6.5874894 3.898118,6.3816539 3.5659013,5.6122905 3.7800789,4.9545192 4.0402258,4.1556558 4.9498996,3.7699484 5.7256318,4.035839 6.6416744,4.3498087 7.0810483,5.4003986 6.7631909,6.2939744 6.395633,7.3272552 5.2038143,7.8204128 4.1924535,7.4503931 3.0418762,7.0294421 2.4948761,5.6961604 2.9171752,4.567073 3.3914021,3.2991406 4.8663228,2.6982592 6.1130974,3.1729158 7.4983851,3.7003207 8.1531869,5.3169977 7.6260947,6.6814205 7.0456093,8.1841025 5.2870784,8.8928844 3.8050073,8.3132966 2.1849115,7.6797506 1.4221671,5.7793073 2.0542715,4.1796074 2.7408201,2.4420977 4.7832541,1.6253548 6.5005435,2.310012 8.3554869,3.0495434 9.2262638,5.2339874 8.4890181,7.0688861 8.4256397,7.2266036 8.3515789,7.379984 8.2675333,7.5277183" /> - </g> - </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 = """<marker + refX="10" + refY="5" + orient="auto" + id="inkstitch-pattern-marker"> + <g + id="inkstitch-pattern-group"> + <path + style="fill:#fafafa;stroke:#ff5500;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1, 1;stroke-dashoffset:0;stroke-opacity:1;fill-opacity:0.8;" + d="M 10.12911,5.2916678 A 4.8374424,4.8374426 0 0 1 5.2916656,10.12911 4.8374424,4.8374426 0 0 1 0.45422399,5.2916678 4.8374424,4.8374426 0 0 1 5.2916656,0.45422399 4.8374424,4.8374426 0 0 1 10.12911,5.2916678 Z" + id="inkstitch-pattern-marker-circle" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.4;stroke-linecap:round;stroke-miterlimit:4;" + id="inkstitch-pattern-marker-spiral" + d="M 4.9673651,5.7245662 C 4.7549848,5.7646159 4.6247356,5.522384 4.6430021,5.3419847 4.6765851,5.0103151 5.036231,4.835347 5.3381858,4.8987426 5.7863901,4.9928495 6.0126802,5.4853625 5.9002872,5.9065088 5.7495249,6.4714237 5.1195537,6.7504036 4.5799191,6.5874894 3.898118,6.3816539 3.5659013,5.6122905 3.7800789,4.9545192 4.0402258,4.1556558 4.9498996,3.7699484 5.7256318,4.035839 6.6416744,4.3498087 7.0810483,5.4003986 6.7631909,6.2939744 6.395633,7.3272552 5.2038143,7.8204128 4.1924535,7.4503931 3.0418762,7.0294421 2.4948761,5.6961604 2.9171752,4.567073 3.3914021,3.2991406 4.8663228,2.6982592 6.1130974,3.1729158 7.4983851,3.7003207 8.1531869,5.3169977 7.6260947,6.6814205 7.0456093,8.1841025 5.2870784,8.8928844 3.8050073,8.3132966 2.1849115,7.6797506 1.4221671,5.7793073 2.0542715,4.1796074 2.7408201,2.4420977 4.7832541,1.6253548 6.5005435,2.310012 8.3554869,3.0495434 9.2262638,5.2339874 8.4890181,7.0688861 8.4256397,7.2266036 8.3515789,7.379984 8.2675333,7.5277183" /> + </g> + </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 diff --git a/templates/apply_pattern.xml b/templates/apply_pattern.xml deleted file mode 100644 index cbd83dbc..00000000 --- a/templates/apply_pattern.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> - <name>{% trans %}Apply Pattern{% endtrans %}</name> - <id>org.inkstitch.apply_pattern.{{ locale }}</id> - <param name="extension" type="string" gui-hidden="true">apply_pattern</param> - <effect> - <object-type>all</object-type> - <effects-menu> - <submenu name="Ink/Stitch" /> - </effects-menu> - </effect> - <script> - {{ command_tag | safe }} - </script> -</inkscape-extension> diff --git a/templates/selection_to_pattern.xml b/templates/selection_to_pattern.xml new file mode 100644 index 00000000..859a51ed --- /dev/null +++ b/templates/selection_to_pattern.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <name>{% trans %}Selection to pattern{% endtrans %}</name> + <id>org.inkstitch.selection_to_pattern.{{ locale }}</id> + <param name="extension" type="string" gui-hidden="true">selection_to_pattern</param> + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu name="Ink/Stitch"> + <submenu name="{% trans %}Edit{% endtrans %}" /> + </submenu> + </effects-menu> + </effect> + <script> + {{ command_tag | safe }} + </script> +</inkscape-extension> -- cgit v1.2.3 From 8e229770a5b5d28f68e065f61af3452f7e25c6b5 Mon Sep 17 00:00:00 2001 From: Kaalleen <reni@allenka.de> 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(-) 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 <reni@allenka.de> 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(-) 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 <reni@allenka.de> 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(-) 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 <lexelby@users.noreply.github.com> 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(-) 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 <reni@allenka.de> 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(-) 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 <github.com@lexneva.name> 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(-) 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 <github.com@lexneva.name> 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 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 <github.com@lexneva.name> 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(-) 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 <github.com@lexneva.name> 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(-) 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 <github.com@lexneva.name> 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(-) 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 <github.com@lexneva.name> 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(-) 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 <github.com@lexneva.name> 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(-) 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 <github.com@lexneva.name> 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(-) 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 <github.com@lexneva.name> 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(+) 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 <github.com@lexneva.name> 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(-) 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 <reni@allenka.de> Date: Sun, 8 Aug 2021 21:51:29 +0200 Subject: remove get_angle --- lib/patterns.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) 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 <github.com@lexneva.name> 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(-) 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 <reni@allenka.de> 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(-) 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