diff options
Diffstat (limited to 'lib/elements')
| -rw-r--r-- | lib/elements/auto_fill.py | 91 | ||||
| -rw-r--r-- | lib/elements/clone.py | 4 | ||||
| -rw-r--r-- | lib/elements/element.py | 36 | ||||
| -rw-r--r-- | lib/elements/empty_d_object.py | 2 | ||||
| -rw-r--r-- | lib/elements/fill.py | 9 | ||||
| -rw-r--r-- | lib/elements/image.py | 2 | ||||
| -rw-r--r-- | lib/elements/pattern.py | 33 | ||||
| -rw-r--r-- | lib/elements/polyline.py | 9 | ||||
| -rw-r--r-- | lib/elements/satin_column.py | 72 | ||||
| -rw-r--r-- | lib/elements/stroke.py | 9 | ||||
| -rw-r--r-- | lib/elements/text.py | 2 | ||||
| -rw-r--r-- | lib/elements/utils.py | 5 |
12 files changed, 178 insertions, 96 deletions
diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py index cf7a44a7..fbbd86d3 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 Patch, param -from .fill import Fill -from .validation import ValidationWarning class SmallShapeWarning(ValidationWarning): @@ -212,8 +213,8 @@ class AutoFill(Fill): else: return None - def to_patches(self, last_patch): - stitches = [] + def to_stitch_groups(self, last_patch): + stitch_groups = [] starting_point = self.get_starting_point(last_patch) ending_point = self.get_ending_point() @@ -221,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 @@ -261,18 +273,19 @@ class AutoFill(Fill): self.fatal(message) - return [Patch(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) +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) + if self.shrink_or_grow_shape(self.expand, True).is_empty: + yield ExpandWarning(self.shape.centroid) - 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.fill_underlay_inset, True).is_empty: + yield UnderlayInsetWarning(self.shape.centroid) - for warning in super(AutoFill, self).validation_warnings(): - yield warning + for warning in super(AutoFill, self).validation_warnings(): + yield warning 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 0b001f0b..f06982b2 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -11,40 +11,13 @@ from inkex import bezier 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 INKSCAPE_LABEL, INKSTITCH_ATTRIBS from ..utils import Point, cache -class Patch: - """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, Patch): - return Patch(self.color, self.stitches + other.stitches) - else: - raise TypeError("Patch can only be added to another Patch") - - 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 Patch(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): @@ -328,13 +301,14 @@ 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: patch.tie_modus = self.ties 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 b6799165..442922b6 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, Patch, param -from .validation import ValidationError class UnconnectedError(ValidationError): @@ -189,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, @@ -198,4 +199,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/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 new file mode 100644 index 00000000..4b92d366 --- /dev/null +++ b/lib/elements/pattern.py @@ -0,0 +1,33 @@ +# 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 not be embroidered. " + "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 = [ + _("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.') + ] + + +class PatternObject(EmbroideryElement): + + def validation_warnings(self): + repr_point = next(inkex.Path(self.parse_path()).end_points) + yield PatternWarning(repr_point) + + def to_stitch_groups(self, last_patch): + return [] diff --git a/lib/elements/polyline.py b/lib/elements/polyline.py index ead2c322..e923aac0 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, Patch, param -from .validation import ValidationWarning class PolylineWarning(ValidationWarning): @@ -100,8 +101,8 @@ class Polyline(EmbroideryElement): def validation_warnings(self): yield PolylineWarning(self.points[0]) - def to_patches(self, last_patch): - patch = Patch(color=self.color) + def to_stitch_groups(self, last_patch): + 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 d72680b7..cf31c2af 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, Patch, param -from .validation import ValidationError, ValidationWarning class SatinHasFillError(ValidationError): @@ -81,6 +82,14 @@ class SatinColumn(EmbroideryElement): 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") + def max_stitch_length(self): + return self.get_float_param("max_stitch_length_mm") or None + + @property def color(self): return self.get_style("stroke") @@ -708,7 +717,10 @@ 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, + 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 @@ -717,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 Patch(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 @@ -730,7 +745,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) @@ -745,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): @@ -756,7 +772,10 @@ class SatinColumn(EmbroideryElement): # print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation - patch = Patch(color=self.color) + if self.max_stitch_length: + return self.do_split_stitch() + + patch = StitchGroup(color=self.color) sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation) @@ -764,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): @@ -774,7 +794,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) @@ -785,16 +805,50 @@ 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): + # stitches exceeding the maximum stitch length will be divided into equal parts through additional stitches + 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) + 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 + 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) + patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch")) return patch - def to_patches(self, last_patch): + 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_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 # 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 39a8f6e3..763167ad 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, Patch, param warned_about_legacy_running_stitch = False @@ -190,15 +191,15 @@ 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): + def to_stitch_groups(self, last_patch): patches = [] 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/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/elements/utils.py b/lib/elements/utils.py index aceab485..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,6 +13,7 @@ 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 +30,9 @@ def node_to_elements(node): # noqa: C901 elif node.tag == SVG_PATH_TAG and not node.get('d', ''): return [EmptyDObject(node)] + elif is_pattern(node): + return [PatternObject(node)] + elif node.tag in EMBROIDERABLE_TAGS: element = EmbroideryElement(node) |
