From 84cb4e2c333d331eb863714797a55589f41e51b2 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 7 Aug 2021 11:21:13 -0400 Subject: [PATCH] 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 3c13a081d..e72af1ef2 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 17ed9167b..a3577a5cf 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 75a86ffdd..2d2ae2eda 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 f63dfc3b5..aeae17d98 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 1f28cb459..e066f3fbf 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 76e806884..9fcb4e210 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 68301e94e..ba4729bbf 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 000000000..1cff8aa49 --- /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 ae6fa4805..85d71935b 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 000000000..d1e6bae71 --- /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 d1af53658..f5981fb99 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