inkstitch/lib/stitch_plan/lock_stitch.py

224 wiersze
7.9 KiB
Python

import re
from math import degrees
from inkex import DirectedLineSegment, Path
from shapely.geometry import LineString
from ..i18n import _
from ..svg import PIXELS_PER_MM
from ..utils import string_to_floats
from .stitch import Stitch
class LockStitchDefinition:
def __init__(self, lock_id=None, name=None, path=None, preview_image=None):
self.id: str = lock_id
self.name: str = name
self._path: str = path
self.preview_image: str = None
def __repr__(self):
return "LockStitchDefinition(%s, %s, %s, %s)" % (self.id, self.name, self._path, self.preview_image)
def stitches(self):
raise NotImplementedError(f"{self.__class__.__name__} must implement stitches()")
class LockStitch:
def __init__(self, lock_type, lock_id, scale_percent, scale_absolute):
self.lock_stitch_definition = get_lock_stitch_definition_by_id(lock_type, lock_id)
self.scale = LockStitchScale(scale_percent, scale_absolute)
def stitches(self, stitches, pos):
return self.lock_stitch_definition.stitches(stitches, pos, self.scale)
class LockStitchScale:
def __init__(self, scale_percent, scale_absolute):
self.percent = scale_percent / 100
self.absolute = scale_absolute
class CustomLock(LockStitchDefinition):
@property
def path(self):
return self._path
@path.setter
def path(self, path):
path_type = self._get_path_type(path)
if path_type in ['svg', 'absolute']:
self._path = path
else:
self._path = None
def stitches(self, stitches, pos, scale):
if self.path is None:
return half_stitch.stitches(stitches, pos)
path_type = self._get_path_type(self.path)
if path_type == "svg":
return SVGLock(self.id,
self.name,
self.path).stitches(stitches, pos, scale.percent)
else:
return AbsoluteLock(self.id,
self.name,
self.path).stitches(stitches, pos, scale.absolute)
def _get_path_type(self, path):
if not path:
return "invalid"
if not re.match("^ *[0-9 .,-]*$", path):
path = Path(path)
if not path or len(list(path.end_points)) < 3:
return None
else:
return "svg"
else:
path = string_to_floats(path, " ")
if not path:
return "invalid"
else:
return "absolute"
class RelativeLock(LockStitchDefinition):
def stitches(self, stitches, pos, scale):
if pos == "end":
stitches = list(reversed(stitches))
path = string_to_floats(self._path, " ")
to_previous = stitches[1] - stitches[0]
length = to_previous.length()
lock_stitches = []
if length > 0.5 * PIXELS_PER_MM:
# Travel back one stitch, stopping halfway there.
# Then go forward one stitch, stopping halfway between
# again.
# but travel at most 1.5 mm
length = min(length, 1.5 * PIXELS_PER_MM)
direction = to_previous.unit()
for delta in path:
lock_stitches.append(Stitch(stitches[0] + delta * length * direction, tags=('lock_stitch')))
else:
# Too short to travel part of the way to the previous stitch; just go
# back and forth to it a couple times.
for i in (1, 0, 1, 0):
lock_stitches.append(stitches[i])
return lock_stitches
class AbsoluteLock(LockStitchDefinition):
def stitches(self, stitches, pos, scale):
if pos == "end":
stitches = list(reversed(stitches))
# make sure the path consists of only floats
path = string_to_floats(self._path, " ")
# get the length of our lock stitch path
if pos == 'start':
lock_pos = []
lock = 0
# reverse the list to make sure we end with the first stitch of the target path
for tie_path in reversed(path):
lock = lock - tie_path * scale.absolute
lock_pos.insert(0, lock)
elif pos == 'end':
lock_pos = []
lock = 0
for tie_path in path:
lock = lock + tie_path * scale.absolute
lock_pos.append(lock)
max_lock_length = max(lock_pos)
# calculate the amount stitches we need from the target path
# and generate a line
upcoming = [stitches[0]]
for stitch in stitches[1:]:
to_start = stitch - upcoming[-1]
upcoming.append(stitch)
if to_start.length() >= max_lock_length:
break
line = LineString(upcoming)
# add tie stitches
lock_stitches = []
for i, tie_path in enumerate(lock_pos):
if tie_path < 0:
stitch = Stitch(stitches[0] + tie_path * (stitches[1] - stitches[0]).unit())
else:
point = line.interpolate(tie_path)
stitch = Stitch(point.x, point.y, tags=('lock_stitch',))
lock_stitches.append(stitch)
return lock_stitches
class SVGLock(LockStitchDefinition):
def stitches(self, stitches, pos, scale):
if pos == "end":
stitches = list(reversed(stitches))
path = Path(self._path)
path.scale(PIXELS_PER_MM, PIXELS_PER_MM, True)
path.scale(scale.percent, scale.percent, True)
end_points = list(path.end_points)
lock = DirectedLineSegment(end_points[-2], end_points[-1])
lock_stitch_angle = lock.angle
stitch = DirectedLineSegment((stitches[0].x, stitches[0].y),
(stitches[1].x, stitches[1].y))
stitch_angle = stitch.angle
# rotate and translate the lock stitch
path.rotate(degrees(stitch_angle - lock_stitch_angle), lock.start, True)
translate = stitch.start - lock.start
path.translate(translate.x, translate.y, True)
# Remove direction indicator from path and also
# remove start:last/end:first stitch (it is the position of the first/last stitch of the target path)
path = list(path.end_points)[:-2]
if pos == 'end':
path = reversed(path)
lock_stitches = []
for i, stitch in enumerate(path):
stitch = Stitch(stitch[0], stitch[1], tags=('lock_stitch',))
lock_stitches.append(stitch)
return lock_stitches
def get_lock_stitch_definition_by_id(pos, lock_type, default="half_stitch"):
id_list = [lock.id for lock in LOCK_DEFAULTS[pos]]
try:
lock = LOCK_DEFAULTS[pos][id_list.index(lock_type)]
except ValueError:
lock = LOCK_DEFAULTS[pos][id_list.index(default)]
return lock
half_stitch = RelativeLock("half_stitch", _("Half Stitch"), "0 0.5 1 0.5 0")
arrow = SVGLock("arrow", _("Arrow"), "M 0.5,0.3 0.3,1.31 -0.11,0.68 H 0.9 L 0.5,1.31 0.4,0.31 V 0.31 1.3")
back_forth = AbsoluteLock("back_forth", _("Back and forth"), "1 1 -1 -1")
bowtie = SVGLock("bowtie", _("Bowtie"), "M 0,0 -0.39,0.97 0.3,0.03 0.14,1.02 0,0 V 0.15")
cross = SVGLock("cross", _("Cross"), "M 0,0 -0.7,-0.7 0.7,0.7 0,0 -0.7,0.7 0.7,-0.7 0,0 -0,-0.7")
star = SVGLock("star", _("Star"), "M 0.67,-0.2 C 0.27,-0.06 -0.22,0.11 -0.67,0.27 L 0.57,0.33 -0.5,-0.27 0,0.67 V 0 -0.5")
simple = SVGLock("simple", _("Simple"), "M -0.03,0 0.09,0.81 0,1.49 V 0 0.48")
triangle = SVGLock("triangle", _("Triangle"), "M -0.26,0.33 H 0.55 L 0,0.84 V 0 L 0.34,0.82")
zigzag = SVGLock("zigzag", _("Zig-zag"), "M -0.25,0.2 0.17,0.77 -0.22,1.45 0.21,2.05 -0.03,3 0,0")
custom = CustomLock("custom", _("Custom"))
LOCK_DEFAULTS = {'start': [half_stitch, arrow, back_forth, bowtie, cross, star, simple, triangle, zigzag, custom],
'end': [half_stitch, arrow, back_forth, cross, bowtie, star, simple, triangle, zigzag, custom]}