summaryrefslogtreecommitdiff
path: root/lib/elements/element.py
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/elements/element.py
parente20161a4ec9a69cb0f1bdfdd16bcd27a8601fde7 (diff)
End points (#3370)
* end at nearest point to next element (if requested and possible)
Diffstat (limited to 'lib/elements/element.py')
-rw-r--r--lib/elements/element.py71
1 files changed, 53 insertions, 18 deletions
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")