kopia lustrzana https://github.com/inkstitch/inkstitch
343 wiersze
10 KiB
Python
343 wiersze
10 KiB
Python
#!/usr/bin/env python
|
|
# http://www.achatina.de/sewing/main/TECHNICL.HTM
|
|
|
|
import math
|
|
import sys
|
|
from copy import deepcopy
|
|
|
|
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)
|
|
|
|
class Point:
|
|
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
|
|
def __add__(self, other):
|
|
return Point(self.x + other.x, self.y + other.y)
|
|
|
|
def __sub__(self, other):
|
|
return Point(self.x - other.x, self.y - other.y)
|
|
|
|
def mul(self, scalar):
|
|
return Point(self.x * scalar, self.y * scalar)
|
|
|
|
def __mul__(self, other):
|
|
if isinstance(other, Point):
|
|
# dot product
|
|
return self.x * other.x + self.y * other.y
|
|
elif isinstance(other, (int, float)):
|
|
return Point(self.x * other, self.y * other)
|
|
else:
|
|
raise ValueError("cannot multiply Point by %s" % type(other))
|
|
|
|
def __rmul__(self, other):
|
|
if isinstance(other, (int, float)):
|
|
return self.__mul__(other)
|
|
else:
|
|
raise ValueError("cannot multiply Point by %s" % type(other))
|
|
|
|
def __repr__(self):
|
|
return "Point(%s,%s)" % (self.x, self.y)
|
|
|
|
def length(self):
|
|
return math.sqrt(math.pow(self.x, 2.0) + math.pow(self.y, 2.0))
|
|
|
|
def unit(self):
|
|
return self.mul(1.0 / self.length())
|
|
|
|
def rotate_left(self):
|
|
return Point(-self.y, self.x)
|
|
|
|
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))
|
|
|
|
def as_int(self):
|
|
return Point(int(round(self.x)), int(round(self.y)))
|
|
|
|
def as_tuple(self):
|
|
return (self.x, self.y)
|
|
|
|
def __cmp__(self, other):
|
|
return cmp(self.as_tuple(), other.as_tuple())
|
|
|
|
def __getitem__(self, item):
|
|
return self.as_tuple()[item]
|
|
|
|
def __len__(self):
|
|
return 2
|
|
|
|
|
|
class Stitch(Point):
|
|
|
|
def __init__(self, x, y, color=None, jump_stitch=False):
|
|
Point.__init__(self, x, y)
|
|
self.color = color
|
|
self.jump_stitch = jump_stitch
|
|
|
|
|
|
class Embroidery:
|
|
|
|
def __init__(self, stitches, pixels_per_millimeter=1):
|
|
self.stitches = deepcopy(stitches)
|
|
self.scale(1.0 / pixels_per_millimeter)
|
|
self.scale((1, -1))
|
|
self.translate_to_origin()
|
|
|
|
def translate_to_origin(self):
|
|
if (len(self.stitches) == 0):
|
|
return
|
|
(maxx, maxy) = (self.stitches[0].x, self.stitches[0].y)
|
|
(minx, miny) = (self.stitches[0].x, self.stitches[0].y)
|
|
for p in self.stitches:
|
|
minx = min(minx, p.x)
|
|
miny = min(miny, p.y)
|
|
maxx = max(maxx, p.x)
|
|
maxy = max(maxy, p.y)
|
|
sx = maxx - minx
|
|
sy = maxy - miny
|
|
|
|
self.translate(-minx, -miny)
|
|
return (minx, miny)
|
|
|
|
def translate(self, dx, dy):
|
|
for p in self.stitches:
|
|
p.x += dx
|
|
p.y += dy
|
|
|
|
def scale(self, sc):
|
|
if not isinstance(sc, (tuple, list)):
|
|
sc = (sc, sc)
|
|
for p in self.stitches:
|
|
p.x *= sc[0]
|
|
p.y *= sc[1]
|
|
|
|
def export_ksm(self):
|
|
str = ""
|
|
self.pos = Point(0, 0)
|
|
lastColor = None
|
|
for stitch in self.stitches:
|
|
if (lastColor is not None and stitch.color != lastColor):
|
|
mode_byte = 0x99
|
|
# dbg.write("Color change!\n")
|
|
else:
|
|
mode_byte = 0x80
|
|
# dbg.write("color still %s\n" % stitch.color)
|
|
lastColor = stitch.color
|
|
new_int = stitch.as_int()
|
|
old_int = self.pos.as_int()
|
|
delta = new_int - old_int
|
|
assert(abs(delta.x) <= 127)
|
|
assert(abs(delta.y) <= 127)
|
|
str += chr(abs(delta.y))
|
|
str += chr(abs(delta.x))
|
|
if (delta.y < 0):
|
|
mode_byte |= 0x20
|
|
if (delta.x < 0):
|
|
mode_byte |= 0x40
|
|
str += chr(mode_byte)
|
|
self.pos = stitch
|
|
return str
|
|
|
|
def export_melco(self):
|
|
self.str = ""
|
|
self.pos = self.stitches[0]
|
|
# dbg.write("stitch count: %d\n" % len(self.stitches))
|
|
lastColor = None
|
|
numColors = 0x0
|
|
for stitch in self.stitches[1:]:
|
|
if (lastColor is not None and stitch.color != lastColor):
|
|
numColors += 1
|
|
# color change
|
|
self.str += chr(0x80)
|
|
self.str += chr(0x01)
|
|
# self.str += chr(numColors)
|
|
# self.str += chr(((numColors+0x80)>>8)&0xff)
|
|
# self.str += chr(((numColors+0x80)>>0)&0xff)
|
|
lastColor = stitch.color
|
|
new_int = stitch.as_int()
|
|
old_int = self.pos.as_int()
|
|
delta = new_int - old_int
|
|
|
|
def move(x, y):
|
|
if (x < 0):
|
|
x = x + 256
|
|
self.str += chr(x)
|
|
if (y < 0):
|
|
y = y + 256
|
|
self.str += chr(y)
|
|
|
|
while (delta.x != 0 or delta.y != 0):
|
|
def clamp(v):
|
|
if (v > 127):
|
|
v = 127
|
|
if (v < -127):
|
|
v = -127
|
|
return v
|
|
dx = clamp(delta.x)
|
|
dy = clamp(delta.y)
|
|
move(dx, dy)
|
|
delta.x -= dx
|
|
delta.y -= dy
|
|
|
|
# dbg.write("Stitch: %s delta %s\n" % (stitch, delta))
|
|
self.pos = stitch
|
|
return self.str
|
|
|
|
def export_csv(self):
|
|
self.str = ""
|
|
self.str += '"#","[THREAD_NUMBER]","[RED]","[GREEN]","[BLUE]","[DESCRIPTION]","[CATALOG_NUMBER]"\n'
|
|
self.str += '"#","[STITCH_TYPE]","[X]","[Y]"\n'
|
|
|
|
lastStitch = None
|
|
colorIndex = 0
|
|
for stitch in self.stitches:
|
|
if lastStitch is not None and stitch.color != lastStitch.color:
|
|
self.str += '"*","COLOR","%f","%f"\n' % (lastStitch.x, lastStitch.y)
|
|
if lastStitch is None or stitch.color != lastStitch.color:
|
|
colorIndex += 1
|
|
self.str += '"$","%d","%d","%d","%d","(null)","(null)"\n' % (
|
|
colorIndex,
|
|
int(stitch.color[1:3], 16),
|
|
int(stitch.color[3:5], 16),
|
|
int(stitch.color[5:7], 16))
|
|
if stitch.jump_stitch:
|
|
self.str += '"*","JUMP","%f","%f"\n' % (stitch.x, stitch.y)
|
|
self.str += '"*","STITCH","%f","%f"\n' % (stitch.x, stitch.y)
|
|
lastStitch = stitch
|
|
self.str += '"*","END","%f","%f"\n' % (lastStitch.x, lastStitch.y)
|
|
return self.str
|
|
|
|
def export_gcode(self):
|
|
ret = []
|
|
lastColor = None
|
|
for stitch in self.stitches:
|
|
if stitch.color != lastColor:
|
|
ret.append('M0 ;MSG, Color change; prepare for %s\n' % stitch.color)
|
|
lastColor = stitch.color
|
|
ret.append('G1 X%f Y%f\n' % stitch.as_tuple())
|
|
ret.append('M0 ;MSG, EMBROIDER stitch\n')
|
|
return ''.join(ret)
|
|
|
|
def export(self, filename, format):
|
|
fp = open(filename, "wb")
|
|
|
|
if format == "melco":
|
|
fp.write(self.export_melco())
|
|
elif format == "csv":
|
|
fp.write(self.export_csv())
|
|
elif format == "gcode":
|
|
fp.write(self.export_gcode())
|
|
fp.close()
|
|
|
|
|
|
class Test:
|
|
|
|
def __init__(self):
|
|
emb = Embroidery()
|
|
for x in range(0, 301, 30):
|
|
emb.addStitch(Point(x, 0))
|
|
emb.addStitch(Point(x, 15))
|
|
emb.addStitch(Point(x, 0))
|
|
|
|
for x in range(300, -1, -30):
|
|
emb.addStitch(Point(x, -12))
|
|
emb.addStitch(Point(x, -27))
|
|
emb.addStitch(Point(x, -12))
|
|
|
|
fp = open("test.exp", "wb")
|
|
fp.write(emb.export_melco())
|
|
fp.close()
|
|
|
|
|
|
class Turtle:
|
|
|
|
def __init__(self):
|
|
self.emb = Embroidery()
|
|
self.pos = Point(0.0, 0.0)
|
|
self.dir = Point(1.0, 0.0)
|
|
self.emb.addStitch(self.pos)
|
|
|
|
def forward(self, dist):
|
|
self.pos = self.pos + self.dir.mul(dist)
|
|
self.emb.addStitch(self.pos)
|
|
|
|
def turn(self, degreesccw):
|
|
radcw = -degreesccw / 180.0 * 3.141592653589
|
|
self.dir = Point(
|
|
math.cos(radcw) * self.dir.x - math.sin(radcw) * self.dir.y,
|
|
math.sin(radcw) * self.dir.x + math.cos(radcw) * self.dir.y)
|
|
|
|
def right(self, degreesccw):
|
|
self.turn(degreesccw)
|
|
|
|
def left(self, degreesccw):
|
|
self.turn(-degreesccw)
|
|
|
|
|
|
class Koch(Turtle):
|
|
|
|
def __init__(self, depth):
|
|
Turtle.__init__(self)
|
|
|
|
edgelen = 750.0
|
|
for i in range(3):
|
|
self.edge(depth, edgelen)
|
|
self.turn(120.0)
|
|
|
|
fp = open("koch%d.exp" % depth, "wb")
|
|
fp.write(self.emb.export_melco())
|
|
fp.close()
|
|
|
|
def edge(self, depth, dist):
|
|
if (depth == 0):
|
|
self.forward(dist)
|
|
else:
|
|
self.edge(depth - 1, dist / 3.0)
|
|
self.turn(-60.0)
|
|
self.edge(depth - 1, dist / 3.0)
|
|
self.turn(120.0)
|
|
self.edge(depth - 1, dist / 3.0)
|
|
self.turn(-60.0)
|
|
self.edge(depth - 1, dist / 3.0)
|
|
|
|
|
|
class Hilbert(Turtle):
|
|
|
|
def __init__(self, level):
|
|
Turtle.__init__(self)
|
|
|
|
self.size = 10.0
|
|
self.hilbert(level, 90.0)
|
|
|
|
fp = open("hilbert%d.exp" % level, "wb")
|
|
fp.write(self.emb.export_melco())
|
|
fp.close()
|
|
|
|
# http://en.wikipedia.org/wiki/Hilbert_curve#Python
|
|
def hilbert(self, level, angle):
|
|
if (level == 0):
|
|
return
|
|
self.right(angle)
|
|
self.hilbert(level - 1, -angle)
|
|
self.forward(self.size)
|
|
self.left(angle)
|
|
self.hilbert(level - 1, angle)
|
|
self.forward(self.size)
|
|
self.hilbert(level - 1, angle)
|
|
self.left(angle)
|
|
self.forward(self.size)
|
|
self.hilbert(level - 1, -angle)
|
|
self.right(angle)
|
|
|
|
if (__name__ == '__main__'):
|
|
# Koch(4)
|
|
Hilbert(6)
|