summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorKaalleen <36401965+kaalleen@users.noreply.github.com>2024-12-26 16:19:35 +0100
committerGitHub <noreply@github.com>2024-12-26 16:19:35 +0100
commitef7d056173cc6d7782d6120c031dae9276725a3d (patch)
tree75d2ef67976336a93424b504e42bbf1a394b9a49 /lib
parente20161a4ec9a69cb0f1bdfdd16bcd27a8601fde7 (diff)
End points (#3370)
* end at nearest point to next element (if requested and possible)
Diffstat (limited to 'lib')
-rw-r--r--lib/elements/clone.py44
-rw-r--r--lib/elements/element.py71
-rw-r--r--lib/elements/empty_d_object.py6
-rw-r--r--lib/elements/fill_stitch.py25
-rw-r--r--lib/elements/marker.py2
-rw-r--r--lib/elements/satin_column.py104
-rw-r--r--lib/elements/stroke.py13
-rw-r--r--lib/elements/text.py4
-rw-r--r--lib/extensions/base.py9
-rwxr-xr-xlib/extensions/params.py9
-rw-r--r--lib/gui/lettering/main_panel.py8
-rw-r--r--lib/gui/satin_multicolor/main_panel.py5
-rw-r--r--lib/svg/tags.py1
13 files changed, 221 insertions, 80 deletions
diff --git a/lib/elements/clone.py b/lib/elements/clone.py
index 770fcc0c..0db8bf4c 100644
--- a/lib/elements/clone.py
+++ b/lib/elements/clone.py
@@ -35,7 +35,8 @@ class CloneWarning(ValidationWarning):
class Clone(EmbroideryElement):
- element_name = "Clone"
+ name = "Clone"
+ element_name = _("Clone")
def __init__(self, *args, **kwargs):
super(Clone, self).__init__(*args, **kwargs)
@@ -66,10 +67,10 @@ class Clone(EmbroideryElement):
def flip_angle(self):
return self.get_boolean_param('flip_angle', False)
- def get_cache_key_data(self, previous_stitch):
+ def get_cache_key_data(self, previous_stitch, next_element):
source_node = self.node.href
source_elements = self.clone_to_elements(source_node)
- return [element.get_cache_key(previous_stitch) for element in source_elements]
+ return [element.get_cache_key(previous_stitch, next_element) for element in source_elements]
def clone_to_elements(self, node) -> List[EmbroideryElement]:
# Only used in get_cache_key_data, actual embroidery uses nodes_to_elements+iterate_nodes
@@ -82,22 +83,53 @@ class Clone(EmbroideryElement):
elements.extend(node_to_elements(child, True))
return elements
- def to_stitch_groups(self, last_stitch_group=None) -> List[StitchGroup]:
+ def to_stitch_groups(self, last_stitch_group=None, next_element=None) -> List[StitchGroup]:
if not self.clone:
return []
with self.clone_elements() as elements:
+ if not elements:
+ return []
stitch_groups = []
- for element in elements:
+ next_elements = [next_element]
+ if len(elements) > 1:
+ next_elements = elements[1:] + next_elements
+ for element, next_element in zip(elements, next_elements):
# Using `embroider` here to get trim/stop after commands, etc.
- element_stitch_groups = element.embroider(last_stitch_group)
+ element_stitch_groups = element.embroider(last_stitch_group, next_element)
if len(element_stitch_groups):
last_stitch_group = element_stitch_groups[-1]
stitch_groups.extend(element_stitch_groups)
return stitch_groups
+ @property
+ def first_stitch(self):
+ first, last = self.first_and_last_element()
+ if first:
+ return first.first_stitch
+ return None
+
+ def uses_previous_stitch(self):
+ first, last = self.first_and_last_element()
+ if first:
+ return first.uses_previous_stitch()
+ return None
+
+ def uses_next_element(self):
+ first, last = self.first_and_last_element()
+ if last:
+ return last.uses_next_element()
+ return None
+
+ @cache
+ def first_and_last_element(self):
+ with self.clone_elements() as elements:
+ if len(elements):
+ return elements[0], elements[-1]
+ return None, None
+
@contextmanager
def clone_elements(self) -> Generator[List[EmbroideryElement], None, None]:
"""
diff --git a/lib/elements/element.py b/lib/elements/element.py
index ac5cf51f..0846b7ab 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -5,26 +5,29 @@
import sys
from contextlib import contextmanager
from copy import deepcopy
+from typing import List, Optional
import inkex
import numpy as np
-from inkex import bezier, BaseElement
-from typing import List, Optional
+from inkex import BaseElement, bezier
+from shapely import Point as ShapelyPoint
+from shapely.ops import nearest_points
from ..commands import Command, find_commands
-from ..stitch_plan import StitchGroup
from ..debug.debug import debug
from ..exceptions import InkstitchException, format_uncaught_exception
from ..i18n import _
from ..marker import get_marker_elements_cache_key_data
from ..patterns import apply_patterns, get_patterns_cache_key_data
+from ..stitch_plan import StitchGroup
from ..stitch_plan.lock_stitch import (LOCK_DEFAULTS, AbsoluteLock, CustomLock,
LockStitch, SVGLock)
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
-from ..utils.cache import get_stitch_plan_cache, is_cache_disabled, CacheKeyGenerator
+from ..utils.cache import (CacheKeyGenerator, get_stitch_plan_cache,
+ is_cache_disabled)
class Param(object):
@@ -437,6 +440,12 @@ class EmbroideryElement(object):
raise NotImplementedError("INTERNAL ERROR: %s must implement shape()", self.__class__)
@property
+ def first_stitch(self):
+ # first stitch is an approximation to where the first stitch may possibly be
+ # if not defined through commands or repositioned by the previous element
+ raise NotImplementedError("INTERNAL ERROR: %s must implement first_stitch()", self.__class__)
+
+ @property
@cache
def commands(self) -> List[Command]:
return find_commands(self.node)
@@ -497,11 +506,11 @@ class EmbroideryElement(object):
return lock_start, lock_end
- def to_stitch_groups(self, last_stitch_group: Optional[StitchGroup]) -> List[StitchGroup]:
+ def to_stitch_groups(self, last_stitch_group: Optional[StitchGroup], next_element: Optional[ShapelyPoint] = None) -> List[StitchGroup]:
raise NotImplementedError("%s must implement to_stitch_groups()" % self.__class__.__name__)
@debug.time
- def _load_cached_stitch_groups(self, previous_stitch):
+ def _load_cached_stitch_groups(self, previous_stitch, next_element):
if is_cache_disabled():
return None
@@ -509,7 +518,7 @@ class EmbroideryElement(object):
# we don't care about the previous stitch
previous_stitch = None
- cache_key = self.get_cache_key(previous_stitch)
+ cache_key = self.get_cache_key(previous_stitch, next_element)
stitch_groups = get_stitch_plan_cache().get(cache_key)
if stitch_groups:
@@ -526,20 +535,27 @@ class EmbroideryElement(object):
"""
return False
+ def uses_next_element(self) -> bool:
+ """Returns True if the shape of the next element can affect this Element's stitches.
+
+ This function may be overridden in a subclass.
+ """
+ return False
+
@debug.time
- def _save_cached_stitch_groups(self, stitch_groups, previous_stitch):
+ def _save_cached_stitch_groups(self, stitch_groups, previous_stitch, next_element):
if is_cache_disabled():
return
stitch_plan_cache = get_stitch_plan_cache()
- cache_key = self.get_cache_key(previous_stitch)
+ cache_key = self.get_cache_key(previous_stitch, next_element)
if cache_key not in stitch_plan_cache:
stitch_plan_cache[cache_key] = stitch_groups
if previous_stitch is not None:
# Also store it with None as the previous stitch, so that it can be used next time
# if we don't care about the previous stitch
- cache_key = self.get_cache_key(None)
+ cache_key = self.get_cache_key(None, None)
if cache_key not in stitch_plan_cache:
stitch_plan_cache[cache_key] = stitch_groups
@@ -569,10 +585,10 @@ class EmbroideryElement(object):
def _get_tartan_key_data(self):
return (self.node.get('inkstitch:tartan', None))
- def get_cache_key_data(self, previous_stitch):
+ def get_cache_key_data(self, previous_stitch, next_element):
return []
- def get_cache_key(self, previous_stitch):
+ def get_cache_key(self, previous_stitch, next_element):
cache_key_generator = CacheKeyGenerator()
cache_key_generator.update(self.__class__.__name__)
cache_key_generator.update(self.get_params_and_values())
@@ -580,18 +596,20 @@ class EmbroideryElement(object):
cache_key_generator.update(list(self._get_specified_style().items()))
cache_key_generator.update(self._get_gradient_cache_key_data())
cache_key_generator.update(previous_stitch)
+ if next_element is not None:
+ cache_key_generator.update(next_element.get_cache_key(None, None))
cache_key_generator.update([(c.command, c.target_point) for c in self.commands])
cache_key_generator.update(self._get_patterns_cache_key_data())
cache_key_generator.update(self._get_guides_cache_key_data())
- cache_key_generator.update(self.get_cache_key_data(previous_stitch))
+ cache_key_generator.update(self.get_cache_key_data(previous_stitch, next_element))
cache_key_generator.update(self._get_tartan_key_data())
cache_key = cache_key_generator.get_cache_key()
- debug.log(f"cache key for {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)} {previous_stitch}: {cache_key}")
+ debug.log(f"cache key for {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)} {previous_stitch} {next_element}: {cache_key}")
return cache_key
- def embroider(self, last_stitch_group: Optional[StitchGroup]) -> List[StitchGroup]:
+ def embroider(self, last_stitch_group: Optional[StitchGroup], next_element=None) -> List[StitchGroup]:
debug.log(f"starting {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
with self.handle_unexpected_exceptions():
@@ -599,12 +617,13 @@ class EmbroideryElement(object):
previous_stitch = last_stitch_group.stitches[-1]
else:
previous_stitch = None
- stitch_groups = self._load_cached_stitch_groups(previous_stitch)
+
+ stitch_groups = self._load_cached_stitch_groups(previous_stitch, next_element)
if not stitch_groups:
self.validate()
- stitch_groups = self.to_stitch_groups(last_stitch_group)
+ stitch_groups = self.to_stitch_groups(last_stitch_group, next_element)
apply_patterns(stitch_groups, self.node)
if stitch_groups:
@@ -617,11 +636,27 @@ class EmbroideryElement(object):
stitch_group.min_jump_stitch_length = self.min_jump_stitch_length
stitch_group.set_minimum_stitch_length(self.min_stitch_length)
- self._save_cached_stitch_groups(stitch_groups, previous_stitch)
+ self._save_cached_stitch_groups(stitch_groups, previous_stitch, next_element)
debug.log(f"ending {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
return stitch_groups
+ def next_stitch(self, next_element):
+ next_stitch = None
+ if next_element is not None and self.uses_next_element():
+ # in fact we really only try an approximation to the next stitch
+ if next_element.uses_previous_stitch():
+ try:
+ next_stitch = nearest_points(next_element.shape, self.shape)[1]
+ except (ValueError, AttributeError):
+ pass
+ else:
+ try:
+ next_stitch = nearest_points(next_element.first_stitch, self.shape)[1]
+ except (ValueError, AttributeError):
+ pass
+ return next_stitch
+
def fatal(self, message, point_to_troubleshoot=False):
label = self.node.get(INKSCAPE_LABEL)
id = self.node.get("id")
diff --git a/lib/elements/empty_d_object.py b/lib/elements/empty_d_object.py
index c2e56eac..ca31a35c 100644
--- a/lib/elements/empty_d_object.py
+++ b/lib/elements/empty_d_object.py
@@ -27,5 +27,9 @@ class EmptyDObject(EmbroideryElement):
def shape(self):
return
- def to_stitch_groups(self, last_stitch_group):
+ @property
+ def first_stitch(self):
+ return
+
+ def to_stitch_groups(self, last_stitch_group, next_element=None):
return []
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py
index 9b330947..07466303 100644
--- a/lib/elements/fill_stitch.py
+++ b/lib/elements/fill_stitch.py
@@ -143,6 +143,7 @@ class InvalidShapeError(ValidationError):
class FillStitch(EmbroideryElement):
+ name = "FillStitch"
element_name = _("FillStitch")
@property
@@ -890,6 +891,12 @@ class FillStitch(EmbroideryElement):
def fill_shape(self, shape):
return self.shrink_or_grow_shape(shape, self.expand)
+ @property
+ def first_stitch(self):
+ # Serves as a reverence point for the end point of the previous element
+ # This isn't really used for fill stitches as they always make their first stitch point dependent on the previous element itself
+ return None
+
def get_starting_point(self, previous_stitch_group):
# If there is a "starting_point" Command, then use that; otherwise pick
# the point closest to the end of the last stitch_group.
@@ -901,19 +908,27 @@ class FillStitch(EmbroideryElement):
else:
return None
+ def get_ending_point(self, next_stitch):
+ if self.get_command('ending_point'):
+ return self.get_command('ending_point').target_point
+ elif next_stitch:
+ return next_stitch.coords
+ else:
+ return None
+
def uses_previous_stitch(self):
if self.get_command('starting_point'):
return False
else:
return True
- def get_ending_point(self):
+ def uses_next_element(self):
if self.get_command('ending_point'):
- return self.get_command('ending_point').target_point
+ return False
else:
- return None
+ return True
- def to_stitch_groups(self, previous_stitch_group): # noqa: C901
+ def to_stitch_groups(self, previous_stitch_group, next_element=None): # noqa: C901
# backwards compatibility: legacy_fill used to be inkstitch:auto_fill == False
if not self.auto_fill or self.fill_method == 'legacy_fill':
return self.do_legacy_fill()
@@ -922,7 +937,7 @@ class FillStitch(EmbroideryElement):
# start and end points
start = self.get_starting_point(previous_stitch_group)
- final_end = self.get_ending_point()
+ final_end = self.get_ending_point(self.next_stitch(next_element))
# sort shapes to get a nicer routing
shapes = list(self.shape.geoms)
diff --git a/lib/elements/marker.py b/lib/elements/marker.py
index 7085d31f..49f9110e 100644
--- a/lib/elements/marker.py
+++ b/lib/elements/marker.py
@@ -28,5 +28,5 @@ class MarkerObject(EmbroideryElement):
repr_point = next(inkex.Path(self.parse_path()).end_points)
yield MarkerWarning(repr_point)
- def to_stitch_groups(self, last_stitch_group):
+ def to_stitch_groups(self, last_stitch_group, next_element=None):
return []
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index 71beba6a..597b7dc6 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -98,6 +98,7 @@ class UnequalPointsWarning(ValidationWarning):
class SatinColumn(EmbroideryElement):
+ name = "SatinColumn"
element_name = _("Satin Column")
def __init__(self, *args, **kwargs):
@@ -399,6 +400,14 @@ class SatinColumn(EmbroideryElement):
return self.get_boolean_param('start_at_nearest_point')
@property
+ @param('end_at_nearest_point',
+ _('End at nearest point'),
+ tooltip=_('End at nearest point to the next element. An end position command will overwrite this setting.'),
+ default=False, type='boolean', sort_index=24)
+ def end_at_nearest_point(self):
+ return self.get_boolean_param('end_at_nearest_point')
+
+ @property
@param('contour_underlay', _('Contour underlay'), type='toggle', group=_('Contour Underlay'))
def contour_underlay(self):
# "Contour underlay" is stitching just inside the rectangular shape
@@ -1282,23 +1291,23 @@ class SatinColumn(EmbroideryElement):
stitch_group.add_tags(("satin_column", "satin_column_underlay"))
return stitch_group
- def do_end_path(self):
+ def do_end_path(self, end_point):
return StitchGroup(
color=self.color,
tags=("satin_column",),
- stitches=[Point(*self.end_point)]
+ stitches=[Point(*end_point)]
)
- def _do_underlay_stitch_groups(self):
+ def _do_underlay_stitch_groups(self, end_point):
stitch_groups = []
if self.center_walk_underlay:
- stitch_groups.extend(self.do_center_walk())
+ stitch_groups.extend(self.do_center_walk(end_point))
if self.contour_underlay:
- stitch_groups.extend(self.do_contour_underlay())
+ stitch_groups.extend(self.do_contour_underlay(end_point))
if self.zigzag_underlay:
- stitch_groups.extend(self.do_zigzag_underlay())
+ stitch_groups.extend(self.do_zigzag_underlay(end_point))
return stitch_groups
@@ -1311,7 +1320,7 @@ class SatinColumn(EmbroideryElement):
stitches=[Stitch(*coord) for coord in linestring.coords]
)
- def do_contour_underlay(self):
+ def do_contour_underlay(self, end_point):
# "contour walk" underlay: do stitches up one side and down the
# other. if the two sides are far away, adding a running stitch to travel
# in between avoids a long jump or a trim.
@@ -1336,7 +1345,7 @@ class SatinColumn(EmbroideryElement):
else:
second_side.reverse()
- if self.end_point:
+ if end_point:
stitch_groups = []
tags = ("satin_column", "satin_column_underlay", "satin_contour_underlay")
first_linestring = shgeo.LineString(first_side)
@@ -1359,14 +1368,14 @@ class SatinColumn(EmbroideryElement):
stitch_group.stitches += second_side
return [stitch_group]
- def do_center_walk(self):
+ def do_center_walk(self, end_point):
# Center walk underlay is just a running stitch down and back on the
# center line between the bezier curves.
repeats = self.center_walk_underlay_repeats
stitch_groups = []
stitches = self._get_center_line_stitches(self.center_walk_underlay_position)
- if self.end_point:
+ if end_point:
tags = ("satin_column", "satin_column_underlay", "satin_center_walk")
stitches = shgeo.LineString(stitches)
start, end = self._split_linestring_at_end_point(stitches)
@@ -1391,7 +1400,7 @@ class SatinColumn(EmbroideryElement):
stitch_group.stitches += stitch_group.stitches[:stitch_count]
return stitch_groups
- def do_zigzag_underlay(self):
+ def do_zigzag_underlay(self, end_point):
# zigzag underlay, usually done at a much lower density than the
# satin itself. It looks like this:
#
@@ -1418,7 +1427,7 @@ class SatinColumn(EmbroideryElement):
start_groups = []
end_groups = []
for points in point_groups:
- if not self.end_point:
+ if not end_point:
stitch_groups.append(self._generate_zigzag_stitch_group(points))
continue
zigzag_line = shgeo.LineString(points)
@@ -1456,21 +1465,19 @@ class SatinColumn(EmbroideryElement):
else:
stitch_group = self.do_satin()
- if self.end_point:
- return self._split_top_layer(stitch_group)
- return [stitch_group]
+ return stitch_group
- def _split_linestring_at_end_point(self, linestring):
- split_line = shgeo.LineString(self.find_cut_points(self.end_point))
+ def _split_linestring_at_end_point(self, linestring, end_point):
+ split_line = shgeo.LineString(self.find_cut_points(end_point))
split_point = nearest_points(linestring, split_line)[0]
project = linestring.project(split_point)
start = substring(linestring, 0, project)
end = substring(linestring, project, linestring.length)
return start, end
- def _split_top_layer(self, stitch_group):
+ def _split_top_layer(self, stitch_group, end_point):
top_layer = shgeo.LineString(stitch_group.stitches)
- start, end = self._split_linestring_at_end_point(top_layer)
+ start, end = self._split_linestring_at_end_point(top_layer, end_point)
stitch_group2 = deepcopy(stitch_group)
stitch_group2.stitches = [Stitch(*point) for point in end.reverse().coords]
stitch_group1 = stitch_group
@@ -1793,12 +1800,34 @@ class SatinColumn(EmbroideryElement):
return stitch_group
@property
- def start_point(self):
- return self._get_command_point('starting_point')
+ def first_stitch(self):
+ return shgeo.Point(self.flattened_rails[0].coords[0])
- @property
- def end_point(self):
- return self._get_command_point('ending_point')
+ def start_point(self, last_stitch_group):
+ start_point = self._get_command_point('starting_point')
+ if start_point is None and self.start_at_nearest_point and last_stitch_group is not None:
+ start_point = nearest_points(shgeo.Point(*last_stitch_group.stitches[-1]), self.center_line)[1]
+ start_point = Point(*list(start_point.coords[0]))
+ return start_point
+
+ def end_point(self, next_stitch):
+ end_point = self._get_command_point('ending_point')
+ if end_point is None and self.end_at_nearest_point and next_stitch is not None:
+ end_point = nearest_points(next_stitch, self.center_line)[1]
+ end_point = Point(*list(end_point.coords[0]))
+ return end_point
+
+ def uses_previous_stitch(self):
+ if not self.start_at_nearest_point or self.get_command('starting_point'):
+ return False
+ else:
+ return True
+
+ def uses_next_element(self):
+ if not self.end_at_nearest_point or self.get_command('ending_point'):
+ return False
+ else:
+ return True
def _get_command_point(self, command):
point = self.get_command(command)
@@ -1806,8 +1835,8 @@ class SatinColumn(EmbroideryElement):
point = point.target_point
return point
- def _sort_stitch_groups(self, stitch_groups):
- if self.end_point:
+ def _sort_stitch_groups(self, stitch_groups, end_point):
+ if end_point:
ordered_stitch_groups = []
ordered_stitch_groups.extend(stitch_groups[::2])
ordered_stitch_groups.append(self._connect_stitch_group_with_point(stitch_groups[1], ordered_stitch_groups[-1].stitches[-1], True))
@@ -1815,33 +1844,34 @@ class SatinColumn(EmbroideryElement):
return ordered_stitch_groups
return stitch_groups
- def to_stitch_groups(self, last_stitch_group=None):
+ def to_stitch_groups(self, last_stitch_group=None, next_element=None):
# 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.
+ start_point = self.start_point(last_stitch_group)
+ end_point = self.end_point(self.next_stitch(next_element))
stitch_groups = []
- start_point = self.start_point
- if start_point is None and last_stitch_group is not None and self.start_at_nearest_point:
- start_point = nearest_points(shgeo.Point(*last_stitch_group.stitches[-1]), self.center_line)[1]
- start_point = Point(*list(start_point.coords[0]))
-
# underlays
- stitch_groups.extend(self._do_underlay_stitch_groups())
+ stitch_groups.extend(self._do_underlay_stitch_groups(end_point))
# top layer
- stitch_groups.extend(self._do_top_layer_stitch_group())
+ top_layer_group = self._do_top_layer_stitch_group()
+ if end_point:
+ stitch_groups.extend(self._split_top_layer(top_layer_group, end_point))
+ else:
+ stitch_groups.append(top_layer_group)
# order stitch groups
- stitch_groups = self._sort_stitch_groups(stitch_groups)
+ stitch_groups = self._sort_stitch_groups(stitch_groups, end_point)
# start and end
if start_point is not None:
stitch_groups = [self._connect_stitch_group_with_point(stitch_groups[0], start_point)] + stitch_groups
- if self.end_point:
- stitch_groups.append(self.do_end_path())
+ if end_point:
+ stitch_groups.append(self.do_end_path(end_point))
# assemble stitch groups
stitch_group = StitchGroup(
diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py
index ff9718c5..5c6a0b08 100644
--- a/lib/elements/stroke.py
+++ b/lib/elements/stroke.py
@@ -13,11 +13,13 @@ from ..i18n import _
from ..marker import get_marker_elements
from ..stitch_plan import StitchGroup
from ..stitches.ripple_stitch import ripple_stitch
-from ..stitches.running_stitch import (bean_stitch, running_stitch, zigzag_stitch)
+from ..stitches.running_stitch import (bean_stitch, running_stitch,
+ zigzag_stitch)
from ..svg import get_node_transform, parse_length_with_units
from ..svg.clip import get_clip_path
from ..threads import ThreadColor
from ..utils import Point, cache
+from ..utils.geometry import ensure_multi_line_string
from ..utils.param import ParamOption
from .element import EmbroideryElement, param
from .validation import ValidationWarning
@@ -41,6 +43,7 @@ class TooFewSubpathsWarning(ValidationWarning):
class Stroke(EmbroideryElement):
+ name = "Stroke"
element_name = _("Stroke")
@property
@@ -483,13 +486,17 @@ class Stroke(EmbroideryElement):
@property
@cache
def shape(self):
- return self.as_multi_line_string().convex_hull
+ return ensure_multi_line_string(self.as_multi_line_string().convex_hull)
@cache
def as_multi_line_string(self):
line_strings = [shgeo.LineString(path) for path in self.paths]
return shgeo.MultiLineString(line_strings)
+ @property
+ def first_stitch(self):
+ return shgeo.Point(self.as_multi_line_string().geoms[0].coords[0])
+
def _get_clipped_path(self, paths):
clip_path = get_clip_path(self.node)
if clip_path is None:
@@ -583,7 +590,7 @@ class Stroke(EmbroideryElement):
def do_bean_repeats(self, stitches):
return bean_stitch(stitches, self.bean_stitch_repeats)
- def to_stitch_groups(self, last_stitch_group): # noqa: C901
+ def to_stitch_groups(self, last_stitch_group, next_element=None): # noqa: C901
stitch_groups = []
# ripple stitch
diff --git a/lib/elements/text.py b/lib/elements/text.py
index 4203c8f9..37a38bc8 100644
--- a/lib/elements/text.py
+++ b/lib/elements/text.py
@@ -4,9 +4,9 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from ..i18n import _
+from ..svg.path import get_node_transform
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
-from ..svg.path import get_node_transform
class TextTypeWarning(ObjectTypeWarning):
@@ -29,5 +29,5 @@ class TextObject(EmbroideryElement):
def validation_warnings(self):
yield TextTypeWarning(self.pointer())
- def to_stitch_groups(self, last_stitch_group):
+ def to_stitch_groups(self, last_stitch_group, next_element=None):
return []
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 5f840417..4a2895d0 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -7,11 +7,11 @@ import os
import inkex
+from ..elements.utils import iterate_nodes, nodes_to_elements
from ..i18n import _
from ..metadata import InkStitchMetadata
from ..svg import generate_unique_id
from ..svg.tags import INKSCAPE_GROUPMODE, SVG_GROUP_TAG
-from ..elements.utils import iterate_nodes, nodes_to_elements
from ..update import update_inkstitch_document
@@ -75,14 +75,17 @@ class InkstitchExtension(inkex.EffectExtension):
return False
def elements_to_stitch_groups(self, elements):
+ next_elements = [None]
+ if len(elements) > 1:
+ next_elements = elements[1:] + next_elements
stitch_groups = []
- for element in elements:
+ for element, next_element in zip(elements, next_elements):
if stitch_groups:
last_stitch_group = stitch_groups[-1]
else:
last_stitch_group = None
- stitch_groups.extend(element.embroider(last_stitch_group))
+ stitch_groups.extend(element.embroider(last_stitch_group, next_element))
return stitch_groups
diff --git a/lib/extensions/params.py b/lib/extensions/params.py
index dd5c427c..0cac9365 100755
--- a/lib/extensions/params.py
+++ b/lib/extensions/params.py
@@ -563,12 +563,12 @@ class SettingsPanel(wx.Panel):
try:
wx.CallAfter(self._hide_warning)
last_stitch_group = None
- for node in nodes:
+ for node, next_node in zip_longest(nodes, self._get_next_nodes(nodes)):
# Making a copy of the embroidery element is an easy
# way to drop the cache in the @cache decorators used
# for many params in embroider.py.
- stitch_groups.extend(copy(node).embroider(last_stitch_group))
+ stitch_groups.extend(copy(node).embroider(last_stitch_group, next_node))
if stitch_groups:
last_stitch_group = stitch_groups[-1]
@@ -587,6 +587,11 @@ class SettingsPanel(wx.Panel):
except Exception:
wx.CallAfter(self._show_warning, format_uncaught_exception())
+ def _get_next_nodes(self, nodes):
+ if len(nodes) > 1:
+ return nodes[1:]
+ return []
+
def get_stroke_last_tabs(self):
tabs = self.tabs
stroke_index = [tabs.index(tab) for tab in tabs if tab.name == _("Stroke")]
diff --git a/lib/gui/lettering/main_panel.py b/lib/gui/lettering/main_panel.py
index 30583070..3d0f65ba 100644
--- a/lib/gui/lettering/main_panel.py
+++ b/lib/gui/lettering/main_panel.py
@@ -320,10 +320,14 @@ class LetteringPanel(wx.Panel):
elements = nodes_to_elements(self.group.iterdescendants(SVG_PATH_TAG))
last_stitch_group = None
- for element in elements:
+ next_elements = [None]
+ if len(elements) > 1:
+ next_elements = elements[1:] + next_elements
+ for element, next_element in zip(elements, next_elements):
check_stop_flag()
- stitch_groups.extend(element.embroider(last_stitch_group))
+ stitch_groups.extend(element.embroider(last_stitch_group, next_element))
+
if stitch_groups:
last_stitch_group = stitch_groups[-1]
diff --git a/lib/gui/satin_multicolor/main_panel.py b/lib/gui/satin_multicolor/main_panel.py
index c27372b3..fd053731 100644
--- a/lib/gui/satin_multicolor/main_panel.py
+++ b/lib/gui/satin_multicolor/main_panel.py
@@ -187,6 +187,11 @@ class MultiColorSatinPanel(wx.Panel):
new_satin.set('inkstitch:contour_underlay', False)
new_satin.set('inkstitch:zigzag_underlay', False)
+ # TODO: adapt start and end position, as well as running_stitch_position
+ # For now, turn start and end points off as they may produce bad looking output
+ new_satin.set('inkstitch:start_at_nearest_point', False)
+ new_satin.set('inkstitch:end_at_nearest_point', False)
+
previous_margin = margin
current_position += width + margin
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
index 5afb2064..99027571 100644
--- a/lib/svg/tags.py
+++ b/lib/svg/tags.py
@@ -169,6 +169,7 @@ inkstitch_attribs = [
'min_random_split_length_mm',
'running_stitch_position',
'start_at_nearest_point',
+ 'end_at_nearest_point',
# stitch_plan
'invisible_layers',
'layer_visibility',