summaryrefslogtreecommitdiff
path: root/lib/elements
diff options
context:
space:
mode:
Diffstat (limited to 'lib/elements')
-rw-r--r--lib/elements/auto_fill.py91
-rw-r--r--lib/elements/clone.py4
-rw-r--r--lib/elements/element.py36
-rw-r--r--lib/elements/empty_d_object.py2
-rw-r--r--lib/elements/fill.py9
-rw-r--r--lib/elements/image.py2
-rw-r--r--lib/elements/pattern.py33
-rw-r--r--lib/elements/polyline.py9
-rw-r--r--lib/elements/satin_column.py72
-rw-r--r--lib/elements/stroke.py9
-rw-r--r--lib/elements/text.py2
-rw-r--r--lib/elements/utils.py5
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)