From c1e6558f7852def419adfbeb087b2194e6030a2c Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 10:57:53 -0400 Subject: rename Patch to StitchGroup --- lib/stitch_plan/stitch_plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/stitch_plan') 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.3.1 From 84cb4e2c333d331eb863714797a55589f41e51b2 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 11:21:13 -0400 Subject: move StitchGroup into lib.stitch_plan --- lib/elements/auto_fill.py | 7 +- lib/elements/element.py | 28 -------- lib/elements/fill.py | 5 +- lib/elements/polyline.py | 5 +- lib/elements/satin_column.py | 5 +- lib/elements/stroke.py | 3 +- lib/stitch_plan/__init__.py | 4 +- lib/stitch_plan/color_block.py | 142 +++++++++++++++++++++++++++++++++++++++ lib/stitch_plan/stitch.py | 3 +- lib/stitch_plan/stitch_group.py | 34 ++++++++++ lib/stitch_plan/stitch_plan.py | 144 +--------------------------------------- 11 files changed, 197 insertions(+), 183 deletions(-) create mode 100644 lib/stitch_plan/color_block.py create mode 100644 lib/stitch_plan/stitch_group.py (limited to 'lib/stitch_plan') 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.3.1 From 8fc42628e285160f8f747772b6d5674a1bf23a09 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 11:37:17 -0400 Subject: add tags capability --- lib/stitch_plan/stitch.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'lib/stitch_plan') 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.3.1 From 28e394b2ae7f4dabcc331d456103d4b3d0efae84 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:00:56 -0400 Subject: StitchGroups now contain only Stitches --- lib/stitch_plan/color_block.py | 6 +----- lib/stitch_plan/stitch.py | 20 +++++++++++++------- lib/stitch_plan/stitch_group.py | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 14 deletions(-) (limited to 'lib/stitch_plan') 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.3.1 From 173548dee569b0503ba1ddeba5cb8aae74c44c46 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:18:55 -0400 Subject: rename more patch references --- lib/api/stitch_plan.py | 4 ++-- lib/extensions/output.py | 4 ++-- lib/extensions/print_pdf.py | 4 ++-- lib/extensions/stitch_plan_preview.py | 4 ++-- lib/extensions/zip.py | 4 ++-- lib/gui/simulator.py | 4 ++-- lib/stitch_plan/__init__.py | 2 +- lib/stitch_plan/stitch_plan.py | 30 +++++++++++++++--------------- 8 files changed, 28 insertions(+), 28 deletions(-) (limited to 'lib/stitch_plan') 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.3.1 From 3b7994c01add202c8f3475956f093a143890160f Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 12:38:41 -0400 Subject: add headers --- lib/stitch_plan/color_block.py | 5 +++++ lib/stitch_plan/stitch_group.py | 5 +++++ 2 files changed, 10 insertions(+) (limited to 'lib/stitch_plan') 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.3.1 From dd865008356d1e04b29a5eb59a8480900f255628 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sun, 15 Aug 2021 17:24:59 -0400 Subject: keep underlay, underpath, and border travel --- lib/elements/auto_fill.py | 82 +++++++++++++++++++++++------------------ lib/patterns.py | 6 +++ lib/stitch_plan/stitch_group.py | 13 ++++++- lib/stitches/auto_fill.py | 8 +++- 4 files changed, 71 insertions(+), 38 deletions(-) (limited to 'lib/stitch_plan') 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.3.1