2016-10-29 17:28:37 +00:00
|
|
|
#!/usr/bin/env python
|
2014-12-26 22:15:48 +00:00
|
|
|
# http://www.achatina.de/sewing/main/TECHNICL.HTM
|
|
|
|
|
|
|
|
import math
|
2018-01-24 01:13:37 +00:00
|
|
|
import libembroidery
|
|
|
|
|
|
|
|
PIXELS_PER_MM = 96 / 25.4
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-11-12 00:59:00 +00:00
|
|
|
try:
|
|
|
|
from functools import lru_cache
|
|
|
|
except ImportError:
|
|
|
|
from backports.functools_lru_cache import lru_cache
|
|
|
|
|
|
|
|
# simplify use of lru_cache decorator
|
|
|
|
def cache(*args, **kwargs):
|
|
|
|
return lru_cache(maxsize=None)(*args, **kwargs)
|
2016-10-29 17:28:37 +00:00
|
|
|
|
2014-12-26 22:15:48 +00:00
|
|
|
class Point:
|
2016-10-29 17:28:37 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def __init__(self, x, y):
|
|
|
|
self.x = x
|
|
|
|
self.y = y
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def __add__(self, other):
|
2016-10-29 17:28:37 +00:00
|
|
|
return Point(self.x + other.x, self.y + other.y)
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def __sub__(self, other):
|
2016-10-29 17:28:37 +00:00
|
|
|
return Point(self.x - other.x, self.y - other.y)
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def mul(self, scalar):
|
2016-10-29 17:28:37 +00:00
|
|
|
return Point(self.x * scalar, self.y * scalar)
|
2016-10-27 16:49:05 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def __mul__(self, other):
|
|
|
|
if isinstance(other, Point):
|
|
|
|
# dot product
|
|
|
|
return self.x * other.x + self.y * other.y
|
|
|
|
elif isinstance(other, (int, float)):
|
2016-11-12 19:05:09 +00:00
|
|
|
return Point(self.x * other, self.y * other)
|
2016-10-27 16:48:12 +00:00
|
|
|
else:
|
|
|
|
raise ValueError("cannot multiply Point by %s" % type(other))
|
2016-02-14 03:13:47 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def __rmul__(self, other):
|
|
|
|
if isinstance(other, (int, float)):
|
2016-11-12 19:05:09 +00:00
|
|
|
return self.__mul__(other)
|
2016-10-27 16:48:12 +00:00
|
|
|
else:
|
|
|
|
raise ValueError("cannot multiply Point by %s" % type(other))
|
2016-02-14 03:13:47 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def __repr__(self):
|
2016-11-12 19:05:09 +00:00
|
|
|
return "Point(%s,%s)" % (self.x, self.y)
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def length(self):
|
2016-10-29 17:28:37 +00:00
|
|
|
return math.sqrt(math.pow(self.x, 2.0) + math.pow(self.y, 2.0))
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def unit(self):
|
2016-10-29 17:28:37 +00:00
|
|
|
return self.mul(1.0 / self.length())
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def rotate_left(self):
|
|
|
|
return Point(-self.y, self.x)
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def rotate(self, angle):
|
|
|
|
return Point(self.x * math.cos(angle) - self.y * math.sin(angle), self.y * math.cos(angle) + self.x * math.sin(angle))
|
2015-12-15 06:09:25 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def as_int(self):
|
|
|
|
return Point(int(round(self.x)), int(round(self.y)))
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def as_tuple(self):
|
2016-10-29 17:28:37 +00:00
|
|
|
return (self.x, self.y)
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-27 16:48:12 +00:00
|
|
|
def __cmp__(self, other):
|
|
|
|
return cmp(self.as_tuple(), other.as_tuple())
|
2014-12-26 22:15:48 +00:00
|
|
|
|
2016-10-31 02:58:51 +00:00
|
|
|
def __getitem__(self, item):
|
|
|
|
return self.as_tuple()[item]
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return 2
|
2016-10-29 17:28:37 +00:00
|
|
|
|
2016-11-07 00:30:49 +00:00
|
|
|
|
2016-10-26 19:04:15 +00:00
|
|
|
class Stitch(Point):
|
2018-01-24 01:13:37 +00:00
|
|
|
def __init__(self, x, y, color=None, jump=False, stop=False, trim=False):
|
|
|
|
self.x = x
|
|
|
|
self.y = y
|
2016-10-26 19:04:15 +00:00
|
|
|
self.color = color
|
2018-01-24 01:13:37 +00:00
|
|
|
self.jump = jump
|
|
|
|
self.trim = trim
|
|
|
|
self.stop = stop
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "Stitch(%s, %s, %s, %s, %s, %s)" % (self.x, self.y, self.color, "JUMP" if self.jump else "", "TRIM" if self.trim else "", "STOP" if self.stop else "")
|
|
|
|
|
|
|
|
def make_thread(color):
|
|
|
|
# strip off the leading "#"
|
|
|
|
if color.startswith("#"):
|
|
|
|
color = color[1:]
|
|
|
|
|
|
|
|
thread = libembroidery.EmbThread()
|
|
|
|
thread.color = libembroidery.embColor_fromHexStr(color)
|
|
|
|
|
|
|
|
thread.description = color
|
|
|
|
thread.catalogNumber = ""
|
|
|
|
|
|
|
|
return thread
|
|
|
|
|
|
|
|
def add_thread(pattern, thread):
|
|
|
|
"""Add a thread to a pattern and return the thread's index"""
|
|
|
|
|
|
|
|
libembroidery.embPattern_addThread(pattern, thread)
|
|
|
|
|
|
|
|
return libembroidery.embThreadList_count(pattern.threadList) - 1
|
|
|
|
|
|
|
|
def get_flags(stitch):
|
|
|
|
flags = 0
|
|
|
|
|
|
|
|
if stitch.jump:
|
|
|
|
flags |= libembroidery.JUMP
|
|
|
|
|
|
|
|
if stitch.trim:
|
|
|
|
flags |= libembroidery.TRIM
|
|
|
|
|
|
|
|
if stitch.stop:
|
|
|
|
flags |= libembroidery.STOP
|
|
|
|
|
|
|
|
return flags
|
|
|
|
|
|
|
|
def write_embroidery_file(file_path, stitches):
|
|
|
|
# Embroidery machines don't care about our canvas size, so we relocate the
|
|
|
|
# design to the origin. It might make sense to center it about the origin
|
|
|
|
# instead.
|
|
|
|
min_x = min(stitch.x for stitch in stitches)
|
|
|
|
min_y = min(stitch.y for stitch in stitches)
|
|
|
|
|
|
|
|
pattern = libembroidery.embPattern_create()
|
|
|
|
threads = {}
|
|
|
|
|
|
|
|
last_color = None
|
|
|
|
|
|
|
|
for stitch in stitches:
|
|
|
|
if stitch.color != last_color:
|
|
|
|
if stitch.color not in threads:
|
|
|
|
thread = make_thread(stitch.color)
|
|
|
|
thread_index = add_thread(pattern, thread)
|
|
|
|
threads[stitch.color] = thread_index
|
2016-10-27 16:48:12 +00:00
|
|
|
else:
|
2018-01-24 01:13:37 +00:00
|
|
|
thread_index = threads[stitch.color]
|
|
|
|
|
|
|
|
libembroidery.embPattern_changeColor(pattern, thread_index)
|
|
|
|
last_color = stitch.color
|
|
|
|
|
|
|
|
flags = get_flags(stitch)
|
|
|
|
libembroidery.embPattern_addStitchAbs(pattern, stitch.x - min_x, stitch.y - min_y, flags, 0)
|
|
|
|
|
|
|
|
libembroidery.embPattern_addStitchAbs(pattern, stitch.x - min_x, stitch.y - min_y, libembroidery.END, 0)
|
|
|
|
|
|
|
|
# convert from pixels to millimeters
|
|
|
|
libembroidery.embPattern_scale(pattern, 1/PIXELS_PER_MM)
|
|
|
|
|
|
|
|
# SVG and embroidery disagree on the direction of the Y axis
|
|
|
|
libembroidery.embPattern_flipVertical(pattern)
|
|
|
|
|
|
|
|
libembroidery.embPattern_write(pattern, file_path)
|