kopia lustrzana https://github.com/inkstitch/inkstitch
pep8 fixes
rodzic
579ef18b56
commit
9249a3ae77
161
PyEmb.py
161
PyEmb.py
|
@ -1,24 +1,25 @@
|
|||
#!python
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# http://www.achatina.de/sewing/main/TECHNICL.HTM
|
||||
|
||||
import math
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
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)
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
|
||||
def __sub__(self, other):
|
||||
return Point(self.x-other.x, self.y-other.y)
|
||||
return Point(self.x - other.x, self.y - other.y)
|
||||
|
||||
def mul(self, scalar):
|
||||
return Point(self.x*scalar, self.y*scalar)
|
||||
return Point(self.x * scalar, self.y * scalar)
|
||||
|
||||
def __mul__(self, other):
|
||||
if isinstance(other, Point):
|
||||
|
@ -36,13 +37,13 @@ class Point:
|
|||
raise ValueError("cannot multiply Point by %s" % type(other))
|
||||
|
||||
def __repr__(self):
|
||||
return "Pt(%s,%s)" % (self.x,self.y)
|
||||
return "Pt(%s,%s)" % (self.x, self.y)
|
||||
|
||||
def length(self):
|
||||
return math.sqrt(math.pow(self.x,2.0)+math.pow(self.y,2.0))
|
||||
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())
|
||||
return self.mul(1.0 / self.length())
|
||||
|
||||
def rotate_left(self):
|
||||
return Point(-self.y, self.x)
|
||||
|
@ -54,36 +55,40 @@ class Point:
|
|||
return Point(int(round(self.x)), int(round(self.y)))
|
||||
|
||||
def as_tuple(self):
|
||||
return (self.x,self.y)
|
||||
return (self.x, self.y)
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.as_tuple(), other.as_tuple())
|
||||
|
||||
|
||||
class Stitch(Point):
|
||||
def __init__(self, x, y, color=None, jumpStitch=False):
|
||||
|
||||
def __init__(self, x, y, color=None, jump_stitch=False):
|
||||
Point.__init__(self, x, y)
|
||||
self.color = color
|
||||
self.jumpStitch = jumpStitch
|
||||
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.0 / pixels_per_millimeter)
|
||||
self.scale((1, -1))
|
||||
self.translate_to_origin()
|
||||
|
||||
def translate_to_origin(self):
|
||||
if (len(self.stitches)==0):
|
||||
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)
|
||||
(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
|
||||
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)
|
||||
|
@ -102,39 +107,39 @@ class Embroidery:
|
|||
|
||||
def export_ksm(self):
|
||||
str = ""
|
||||
self.pos = Point(0,0)
|
||||
self.pos = Point(0, 0)
|
||||
lastColor = None
|
||||
for stitch in self.stitches:
|
||||
if (lastColor!=None and stitch.color!=lastColor):
|
||||
if (lastColor is not None and stitch.color != lastColor):
|
||||
mode_byte = 0x99
|
||||
#dbg.write("Color change!\n")
|
||||
# dbg.write("Color change!\n")
|
||||
else:
|
||||
mode_byte = 0x80
|
||||
#dbg.write("color still %s\n" % stitch.color)
|
||||
# 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):
|
||||
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):
|
||||
if (delta.x < 0):
|
||||
mode_byte |= 0x40
|
||||
str+=chr(mode_byte)
|
||||
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))
|
||||
# dbg.write("stitch count: %d\n" % len(self.stitches))
|
||||
lastColor = None
|
||||
numColors = 0x0
|
||||
for stitch in self.stitches[1:]:
|
||||
if (lastColor!=None and stitch.color!=lastColor):
|
||||
if (lastColor is not None and stitch.color != lastColor):
|
||||
numColors += 1
|
||||
# color change
|
||||
self.str += chr(0x80)
|
||||
|
@ -147,26 +152,28 @@ class Embroidery:
|
|||
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)
|
||||
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):
|
||||
while (delta.x != 0 or delta.y != 0):
|
||||
def clamp(v):
|
||||
if (v>127):
|
||||
if (v > 127):
|
||||
v = 127
|
||||
if (v<-127):
|
||||
if (v < -127):
|
||||
v = -127
|
||||
return v
|
||||
dx = clamp(delta.x)
|
||||
dy = clamp(delta.y)
|
||||
move(dx,dy)
|
||||
move(dx, dy)
|
||||
delta.x -= dx
|
||||
delta.y -= dy
|
||||
|
||||
#dbg.write("Stitch: %s delta %s\n" % (stitch, delta))
|
||||
# dbg.write("Stitch: %s delta %s\n" % (stitch, delta))
|
||||
self.pos = stitch
|
||||
return self.str
|
||||
|
||||
|
@ -187,7 +194,7 @@ class Embroidery:
|
|||
int(stitch.color[1:3], 16),
|
||||
int(stitch.color[3:5], 16),
|
||||
int(stitch.color[5:7], 16))
|
||||
if stitch.jumpStitch:
|
||||
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
|
||||
|
@ -211,44 +218,48 @@ class Embroidery:
|
|||
if format == "melco":
|
||||
fp.write(self.export_melco())
|
||||
elif format == "csv":
|
||||
fp.write(self.export_csv())
|
||||
fp.write(self.export_csv())
|
||||
elif format == "gcode":
|
||||
fp.write(self.export_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(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));
|
||||
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.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.pos = self.pos + self.dir.mul(dist)
|
||||
self.emb.addStitch(self.pos)
|
||||
|
||||
def turn(self, degreesccw):
|
||||
radcw = -degreesccw/180.0*3.141592653589
|
||||
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)
|
||||
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)
|
||||
|
@ -256,7 +267,9 @@ class Turtle:
|
|||
def left(self, degreesccw):
|
||||
self.turn(-degreesccw)
|
||||
|
||||
|
||||
class Koch(Turtle):
|
||||
|
||||
def __init__(self, depth):
|
||||
Turtle.__init__(self)
|
||||
|
||||
|
@ -270,18 +283,20 @@ class Koch(Turtle):
|
|||
fp.close()
|
||||
|
||||
def edge(self, depth, dist):
|
||||
if (depth==0):
|
||||
if (depth == 0):
|
||||
self.forward(dist)
|
||||
else:
|
||||
self.edge(depth-1, dist/3.0)
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
self.turn(-60.0)
|
||||
self.edge(depth-1, dist/3.0)
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
self.turn(120.0)
|
||||
self.edge(depth-1, dist/3.0)
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
self.turn(-60.0)
|
||||
self.edge(depth-1, dist/3.0)
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
|
||||
|
||||
class Hilbert(Turtle):
|
||||
|
||||
def __init__(self, level):
|
||||
Turtle.__init__(self)
|
||||
|
||||
|
@ -294,20 +309,20 @@ class Hilbert(Turtle):
|
|||
|
||||
# http://en.wikipedia.org/wiki/Hilbert_curve#Python
|
||||
def hilbert(self, level, angle):
|
||||
if (level==0):
|
||||
if (level == 0):
|
||||
return
|
||||
self.right(angle)
|
||||
self.hilbert(level-1, -angle)
|
||||
self.hilbert(level - 1, -angle)
|
||||
self.forward(self.size)
|
||||
self.left(angle)
|
||||
self.hilbert(level-1, angle)
|
||||
self.hilbert(level - 1, angle)
|
||||
self.forward(self.size)
|
||||
self.hilbert(level-1, angle)
|
||||
self.hilbert(level - 1, angle)
|
||||
self.left(angle)
|
||||
self.forward(self.size)
|
||||
self.hilbert(level-1, -angle)
|
||||
self.hilbert(level - 1, -angle)
|
||||
self.right(angle)
|
||||
|
||||
if (__name__=='__main__'):
|
||||
#Koch(4)
|
||||
if (__name__ == '__main__'):
|
||||
# Koch(4)
|
||||
Hilbert(6)
|
||||
|
|
318
embroider.py
318
embroider.py
|
@ -45,12 +45,14 @@ PyEmb.dbg = dbg
|
|||
# a 0.5pt stroke becomes a straight line.
|
||||
STROKE_MIN = 0.5
|
||||
|
||||
|
||||
def parse_boolean(s):
|
||||
if isinstance(s, bool):
|
||||
return s
|
||||
else:
|
||||
return s and (s.lower() in ('yes', 'y', 'true', 't', '1'))
|
||||
|
||||
|
||||
def get_param(node, param, default):
|
||||
value = node.get("embroider_" + param)
|
||||
|
||||
|
@ -59,11 +61,13 @@ def get_param(node, param, default):
|
|||
|
||||
return value.strip()
|
||||
|
||||
|
||||
def get_boolean_param(node, param, default=False):
|
||||
value = get_param(node, param, default)
|
||||
|
||||
return parse_boolean(value)
|
||||
|
||||
|
||||
def get_float_param(node, param, default=None):
|
||||
value = get_param(node, param, default)
|
||||
|
||||
|
@ -72,6 +76,7 @@ def get_float_param(node, param, default=None):
|
|||
except ValueError:
|
||||
return default
|
||||
|
||||
|
||||
def get_int_param(node, param, default=None):
|
||||
value = get_param(node, param, default)
|
||||
|
||||
|
@ -80,6 +85,7 @@ def get_int_param(node, param, default=None):
|
|||
except ValueError:
|
||||
return default
|
||||
|
||||
|
||||
def parse_path(node):
|
||||
path = cubicsuperpath.parsePath(node.get("d"))
|
||||
|
||||
|
@ -96,6 +102,7 @@ def parse_path(node):
|
|||
|
||||
return path
|
||||
|
||||
|
||||
def flatten(path, flatness):
|
||||
"""approximate a path containing beziers with a series of points"""
|
||||
|
||||
|
@ -113,16 +120,17 @@ def flatten(path, flatness):
|
|||
|
||||
return flattened
|
||||
|
||||
|
||||
def csp_to_shapely_polygon(path):
|
||||
poly_ary = []
|
||||
for sub_path in path:
|
||||
point_ary = []
|
||||
last_pt = None
|
||||
for pt in sub_path:
|
||||
if (last_pt!=None):
|
||||
vp = (pt[0]-last_pt[0],pt[1]-last_pt[1])
|
||||
dp = math.sqrt(math.pow(vp[0],2.0)+math.pow(vp[1],2.0))
|
||||
#dbg.write("dp %s\n" % dp)
|
||||
if (last_pt is not None):
|
||||
vp = (pt[0] - last_pt[0], pt[1] - last_pt[1])
|
||||
dp = math.sqrt(math.pow(vp[0], 2.0) + math.pow(vp[1], 2.0))
|
||||
# dbg.write("dp %s\n" % dp)
|
||||
if (dp > 0.01):
|
||||
# I think too-close points confuse shapely.
|
||||
point_ary.append(pt)
|
||||
|
@ -137,11 +145,12 @@ def csp_to_shapely_polygon(path):
|
|||
poly_ary.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
|
||||
|
||||
polygon = shgeo.MultiPolygon([(poly_ary[0], poly_ary[1:])])
|
||||
#print >> sys.stderr, "polygon valid:", polygon.is_valid
|
||||
# print >> sys.stderr, "polygon valid:", polygon.is_valid
|
||||
return polygon
|
||||
|
||||
|
||||
class Patch:
|
||||
|
||||
def __init__(self, color=None, stitches=None):
|
||||
self.color = color
|
||||
self.stitches = stitches or []
|
||||
|
@ -152,7 +161,7 @@ class Patch:
|
|||
else:
|
||||
raise TypeError("Patch can only be added to another Patch")
|
||||
|
||||
def addStitch(self, stitch):
|
||||
def add_stitch(self, stitch):
|
||||
self.stitches.append(stitch)
|
||||
|
||||
def reverse(self):
|
||||
|
@ -162,53 +171,53 @@ class Patch:
|
|||
def patches_to_stitches(patch_list, collapse_len_px=0):
|
||||
stitches = []
|
||||
|
||||
lastStitch = None
|
||||
lastColor = None
|
||||
last_stitch = None
|
||||
last_color = None
|
||||
for patch in patch_list:
|
||||
jumpStitch = True
|
||||
jump_stitch = True
|
||||
for stitch in patch.stitches:
|
||||
if lastStitch and lastColor == patch.color:
|
||||
l = (stitch - lastStitch).length()
|
||||
if last_stitch and last_color == patch.color:
|
||||
l = (stitch - last_stitch).length()
|
||||
if l <= 0.1:
|
||||
# filter out duplicate successive stitches
|
||||
jumpStitch = False
|
||||
jump_stitch = False
|
||||
continue
|
||||
|
||||
if jumpStitch:
|
||||
if jump_stitch:
|
||||
# consider collapsing jump stitch, if it is pretty short
|
||||
if l < collapse_len_px:
|
||||
#dbg.write("... collapsed\n")
|
||||
jumpStitch = False
|
||||
# dbg.write("... collapsed\n")
|
||||
jump_stitch = False
|
||||
|
||||
#dbg.write("stitch color %s\n" % patch.color)
|
||||
# dbg.write("stitch color %s\n" % patch.color)
|
||||
|
||||
newStitch = PyEmb.Stitch(stitch.x, stitch.y, patch.color, jumpStitch)
|
||||
newStitch = PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump_stitch)
|
||||
stitches.append(newStitch)
|
||||
|
||||
jumpStitch = False
|
||||
lastStitch = stitch
|
||||
lastColor = patch.color
|
||||
jump_stitch = False
|
||||
last_stitch = stitch
|
||||
last_color = patch.color
|
||||
|
||||
return stitches
|
||||
|
||||
|
||||
def stitches_to_paths(stitches):
|
||||
paths = []
|
||||
lastColor = None
|
||||
lastStitch = None
|
||||
last_color = None
|
||||
last_stitch = None
|
||||
for stitch in stitches:
|
||||
if stitch.jumpStitch:
|
||||
if lastColor == stitch.color:
|
||||
if stitch.jump_stitch:
|
||||
if last_color == stitch.color:
|
||||
paths.append([None, []])
|
||||
if lastStitch is not None:
|
||||
paths[-1][1].append(['M', lastStitch.as_tuple()])
|
||||
if last_stitch is not None:
|
||||
paths[-1][1].append(['M', last_stitch.as_tuple()])
|
||||
paths[-1][1].append(['L', stitch.as_tuple()])
|
||||
lastColor = None
|
||||
if stitch.color != lastColor:
|
||||
last_color = None
|
||||
if stitch.color != last_color:
|
||||
paths.append([stitch.color, []])
|
||||
paths[-1][1].append(['L' if len(paths[-1][1]) > 0 else 'M', stitch.as_tuple()])
|
||||
lastColor = stitch.color
|
||||
lastStitch = stitch
|
||||
last_color = stitch.color
|
||||
last_stitch = stitch
|
||||
return paths
|
||||
|
||||
|
||||
|
@ -216,69 +225,70 @@ def emit_inkscape(parent, stitches):
|
|||
for color, path in stitches_to_paths(stitches):
|
||||
dbg.write('path: %s %s\n' % (color, repr(path)))
|
||||
inkex.etree.SubElement(parent,
|
||||
inkex.addNS('path', 'svg'),
|
||||
{ 'style':simplestyle.formatStyle(
|
||||
{ 'stroke': color if color is not None else '#000000',
|
||||
'stroke-width':"0.4",
|
||||
'fill': 'none' }),
|
||||
'd':simplepath.formatPath(path),
|
||||
})
|
||||
inkex.addNS('path', 'svg'),
|
||||
{'style': simplestyle.formatStyle(
|
||||
{'stroke': color if color is not None else '#000000',
|
||||
'stroke-width': "0.4",
|
||||
'fill': 'none'}),
|
||||
'd': simplepath.formatPath(path),
|
||||
})
|
||||
|
||||
|
||||
class Embroider(inkex.Effect):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
#dbg.write("args: %s\n" % repr(sys.argv))
|
||||
# dbg.write("args: %s\n" % repr(sys.argv))
|
||||
inkex.Effect.__init__(self)
|
||||
self.OptionParser.add_option("-r", "--row_spacing_mm",
|
||||
action="store", type="float",
|
||||
dest="row_spacing_mm", default=0.4,
|
||||
help="row spacing (mm)")
|
||||
action="store", type="float",
|
||||
dest="row_spacing_mm", default=0.4,
|
||||
help="row spacing (mm)")
|
||||
self.OptionParser.add_option("-z", "--zigzag_spacing_mm",
|
||||
action="store", type="float",
|
||||
dest="zigzag_spacing_mm", default=1.0,
|
||||
help="zigzag spacing (mm)")
|
||||
action="store", type="float",
|
||||
dest="zigzag_spacing_mm", default=1.0,
|
||||
help="zigzag spacing (mm)")
|
||||
self.OptionParser.add_option("-l", "--max_stitch_len_mm",
|
||||
action="store", type="float",
|
||||
dest="max_stitch_len_mm", default=3.0,
|
||||
help="max stitch length (mm)")
|
||||
action="store", type="float",
|
||||
dest="max_stitch_len_mm", default=3.0,
|
||||
help="max stitch length (mm)")
|
||||
self.OptionParser.add_option("--running_stitch_len_mm",
|
||||
action="store", type="float",
|
||||
dest="running_stitch_len_mm", default=3.0,
|
||||
help="running stitch length (mm)")
|
||||
action="store", type="float",
|
||||
dest="running_stitch_len_mm", default=3.0,
|
||||
help="running stitch length (mm)")
|
||||
self.OptionParser.add_option("-c", "--collapse_len_mm",
|
||||
action="store", type="float",
|
||||
dest="collapse_len_mm", default=0.0,
|
||||
help="max collapse length (mm)")
|
||||
action="store", type="float",
|
||||
dest="collapse_len_mm", default=0.0,
|
||||
help="max collapse length (mm)")
|
||||
self.OptionParser.add_option("-f", "--flatness",
|
||||
action="store", type="float",
|
||||
dest="flat", default=0.1,
|
||||
help="Minimum flatness of the subdivided curves")
|
||||
action="store", type="float",
|
||||
dest="flat", default=0.1,
|
||||
help="Minimum flatness of the subdivided curves")
|
||||
self.OptionParser.add_option("--hide_layers",
|
||||
action="store", type="choice",
|
||||
choices=["true","false"],
|
||||
dest="hide_layers", default="true",
|
||||
help="Hide all other layers when the embroidery layer is generated")
|
||||
action="store", type="choice",
|
||||
choices=["true", "false"],
|
||||
dest="hide_layers", default="true",
|
||||
help="Hide all other layers when the embroidery layer is generated")
|
||||
self.OptionParser.add_option("-O", "--output_format",
|
||||
action="store", type="choice",
|
||||
choices=["melco", "csv", "gcode"],
|
||||
dest="output_format", default="melco",
|
||||
help="File output format")
|
||||
action="store", type="choice",
|
||||
choices=["melco", "csv", "gcode"],
|
||||
dest="output_format", default="melco",
|
||||
help="File output format")
|
||||
self.OptionParser.add_option("-P", "--path",
|
||||
action="store", type="string",
|
||||
dest="path", default=".",
|
||||
help="Directory in which to store output file")
|
||||
action="store", type="string",
|
||||
dest="path", default=".",
|
||||
help="Directory in which to store output file")
|
||||
self.OptionParser.add_option("-b", "--max-backups",
|
||||
action="store", type="int",
|
||||
dest="max_backups", default=5,
|
||||
help="Max number of backups of output files to keep.")
|
||||
action="store", type="int",
|
||||
dest="max_backups", default=5,
|
||||
help="Max number of backups of output files to keep.")
|
||||
self.OptionParser.add_option("-p", "--pixels_per_mm",
|
||||
action="store", type="int",
|
||||
dest="pixels_per_millimeter", default=10,
|
||||
help="Number of on-screen pixels per millimeter.")
|
||||
action="store", type="int",
|
||||
dest="pixels_per_millimeter", default=10,
|
||||
help="Number of on-screen pixels per millimeter.")
|
||||
self.patches = []
|
||||
|
||||
def process_one_path(self, node, shpath, threadcolor, angle):
|
||||
#self.add_shapely_geo_to_svg(shpath.boundary, color="#c0c000")
|
||||
# self.add_shapely_geo_to_svg(shpath.boundary, color="#c0c000")
|
||||
|
||||
flip = get_boolean_param(node, "flip", False)
|
||||
row_spacing_px = get_float_param(node, "row_spacing", self.options.row_spacing_mm) * self.options.pixels_per_millimeter
|
||||
|
@ -287,11 +297,11 @@ class Embroider(inkex.Effect):
|
|||
|
||||
rows_of_segments = self.intersect_region_with_grating(shpath, row_spacing_px, angle, flip)
|
||||
groups_of_segments = self.pull_runs(rows_of_segments, shpath, row_spacing_px)
|
||||
|
||||
|
||||
# "east" is the name of the direction that is to the right along a row
|
||||
east = PyEmb.Point(1, 0).rotate(-angle)
|
||||
|
||||
#print >> sys.stderr, len(groups_of_segments)
|
||||
# print >> sys.stderr, len(groups_of_segments)
|
||||
|
||||
patches = []
|
||||
for group_of_segments in groups_of_segments:
|
||||
|
@ -302,7 +312,7 @@ class Embroider(inkex.Effect):
|
|||
|
||||
for segment in group_of_segments:
|
||||
# We want our stitches to look like this:
|
||||
#
|
||||
#
|
||||
# ---*-----------*-----------
|
||||
# ------*-----------*--------
|
||||
# ---------*-----------*-----
|
||||
|
@ -324,7 +334,7 @@ class Embroider(inkex.Effect):
|
|||
(beg, end) = segment
|
||||
|
||||
if (swap):
|
||||
(beg,end)=(end,beg)
|
||||
(beg, end) = (end, beg)
|
||||
|
||||
beg = PyEmb.Point(*beg)
|
||||
end = PyEmb.Point(*end)
|
||||
|
@ -335,7 +345,7 @@ class Embroider(inkex.Effect):
|
|||
# only stitch the first point if it's a reasonable distance away from the
|
||||
# last stitch
|
||||
if last_end is None or (beg - last_end).length() > 0.5 * self.options.pixels_per_millimeter:
|
||||
patch.addStitch(beg)
|
||||
patch.add_stitch(beg)
|
||||
|
||||
# Now, imagine the coordinate axes rotated by 'angle' degrees, such that
|
||||
# the rows are parallel to the X axis. We can find the coordinates in these
|
||||
|
@ -357,11 +367,11 @@ class Embroider(inkex.Effect):
|
|||
offset = (first_stitch - beg).length()
|
||||
|
||||
while offset < segment_length:
|
||||
patch.addStitch(beg + offset * row_direction)
|
||||
patch.add_stitch(beg + offset * row_direction)
|
||||
offset += max_stitch_len_px
|
||||
|
||||
if (end - patch.stitches[-1]).length() > 0.1 * self.options.pixels_per_millimeter:
|
||||
patch.addStitch(end)
|
||||
patch.add_stitch(end)
|
||||
|
||||
last_end = end
|
||||
swap = not swap
|
||||
|
@ -383,7 +393,7 @@ class Embroider(inkex.Effect):
|
|||
direction = PyEmb.Point(1, 0).rotate(-angle)
|
||||
|
||||
# and get a normal vector
|
||||
normal = direction.rotate(math.pi/2)
|
||||
normal = direction.rotate(math.pi / 2)
|
||||
|
||||
# I'll start from the center, move in the normal direction some amount,
|
||||
# and then walk left and right half_length in each direction to create
|
||||
|
@ -395,7 +405,7 @@ class Embroider(inkex.Effect):
|
|||
# angle degrees clockwise and ask for the new bounding box. The max
|
||||
# and min y tell me how far to go.
|
||||
|
||||
_, start, _, end = affinity.rotate(shpath, angle, origin='center', use_radians = True).bounds
|
||||
_, start, _, end = affinity.rotate(shpath, angle, origin='center', use_radians=True).bounds
|
||||
|
||||
# convert start and end to be relative to center (simplifies things later)
|
||||
start -= center.y
|
||||
|
@ -450,7 +460,7 @@ class Embroider(inkex.Effect):
|
|||
# start a new patch.
|
||||
|
||||
# Segments more than this far apart are considered not to be part of
|
||||
# the same run.
|
||||
# the same run.
|
||||
row_distance_cutoff = row_spacing_px * 1.1
|
||||
|
||||
def make_quadrilateral(segment1, segment2):
|
||||
|
@ -470,10 +480,10 @@ class Embroider(inkex.Effect):
|
|||
|
||||
return (intersection_area / quad_area) >= 0.9
|
||||
|
||||
#for row in rows:
|
||||
# for row in rows:
|
||||
# print >> sys.stderr, len(row)
|
||||
|
||||
#print >>sys.stderr, "\n".join(str(len(row)) for row in rows)
|
||||
# print >>sys.stderr, "\n".join(str(len(row)) for row in rows)
|
||||
|
||||
runs = []
|
||||
count = 0
|
||||
|
@ -488,13 +498,13 @@ class Embroider(inkex.Effect):
|
|||
# TODO: only accept actually adjacent rows here
|
||||
if prev is not None and not is_same_run(prev, first):
|
||||
break
|
||||
|
||||
|
||||
run.append(first)
|
||||
prev = first
|
||||
|
||||
rows[row_num] = rest
|
||||
|
||||
#print >> sys.stderr, len(run)
|
||||
# print >> sys.stderr, len(run)
|
||||
runs.append(run)
|
||||
rows = [row for row in rows if len(row) > 0]
|
||||
|
||||
|
@ -515,7 +525,7 @@ class Embroider(inkex.Effect):
|
|||
if node.tag != self.svgpath:
|
||||
return
|
||||
|
||||
#dbg.write("Node: %s\n"%str((id, etree.tostring(node, pretty_print=True))))
|
||||
# dbg.write("Node: %s\n"%str((id, etree.tostring(node, pretty_print=True))))
|
||||
|
||||
if get_boolean_param(node, "satin_column"):
|
||||
self.patch_list.extend(self.satin_column(node))
|
||||
|
@ -523,9 +533,9 @@ class Embroider(inkex.Effect):
|
|||
stroke = []
|
||||
fill = []
|
||||
|
||||
if (self.get_style(node, "stroke")!=None):
|
||||
if (self.get_style(node, "stroke") is not None):
|
||||
stroke = self.path_to_patch_list(node)
|
||||
if (self.get_style(node, "fill")!=None):
|
||||
if (self.get_style(node, "fill") is not None):
|
||||
fill = self.filled_region_to_patchlist(node)
|
||||
|
||||
if get_boolean_param(node, "stroke_first", False):
|
||||
|
@ -540,7 +550,7 @@ class Embroider(inkex.Effect):
|
|||
if (style_name not in style):
|
||||
return None
|
||||
value = style[style_name]
|
||||
if (value==None or value=="none"):
|
||||
if (value is None or value == "none"):
|
||||
return None
|
||||
return value
|
||||
|
||||
|
@ -615,7 +625,7 @@ class Embroider(inkex.Effect):
|
|||
emb.export(self.get_output_path(), self.options.output_format)
|
||||
|
||||
new_layer = inkex.etree.SubElement(self.document.getroot(),
|
||||
inkex.addNS('g', 'svg'), {})
|
||||
inkex.addNS('g', 'svg'), {})
|
||||
new_layer.set('id', self.uniqueId("embroidery"))
|
||||
new_layer.set(inkex.addNS('label', 'inkscape'), 'Embroidery')
|
||||
new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
|
@ -624,7 +634,7 @@ class Embroider(inkex.Effect):
|
|||
sys.stdout = old_stdout
|
||||
|
||||
def hide_layers(self):
|
||||
for g in self.document.getroot().findall(inkex.addNS("g","svg")):
|
||||
for g in self.document.getroot().findall(inkex.addNS("g", "svg")):
|
||||
if g.get(inkex.addNS("groupmode", "inkscape")) == "layer":
|
||||
g.set("style", "display:none")
|
||||
|
||||
|
@ -637,8 +647,8 @@ class Embroider(inkex.Effect):
|
|||
stroke_width_str = stroke_width_str[:-2]
|
||||
stroke_width = float(stroke_width_str)
|
||||
dashed = self.get_style(node, "stroke-dasharray") is not None
|
||||
#dbg.write("stroke_width is <%s>\n" % repr(stroke_width))
|
||||
#dbg.flush()
|
||||
# dbg.write("stroke_width is <%s>\n" % repr(stroke_width))
|
||||
# dbg.flush()
|
||||
|
||||
running_stitch_len_px = get_float_param(node, "stitch_length", self.options.running_stitch_len_mm) * self.pixels_per_millimeter
|
||||
zigzag_spacing_px = get_float_param(node, "zigzag_spacing", self.options.zigzag_spacing_mm) * self.options.pixels_per_millimeter
|
||||
|
@ -655,10 +665,10 @@ class Embroider(inkex.Effect):
|
|||
for path in paths:
|
||||
path = [PyEmb.Point(x, y) for x, y in path]
|
||||
if (stroke_width <= STROKE_MIN or dashed):
|
||||
#dbg.write("self.max_stitch_len_px = %s\n" % self.max_stitch_len_px)
|
||||
# dbg.write("self.max_stitch_len_px = %s\n" % self.max_stitch_len_px)
|
||||
patch = self.stroke_points(path, running_stitch_len_px, 0.0, repeats, threadcolor)
|
||||
else:
|
||||
patch = self.stroke_points(path, zigzag_spacing_px*0.5, stroke_width, repeats, threadcolor)
|
||||
patch = self.stroke_points(path, zigzag_spacing_px * 0.5, stroke_width, repeats, threadcolor)
|
||||
patches.extend(patch)
|
||||
|
||||
return patches
|
||||
|
@ -687,19 +697,19 @@ class Embroider(inkex.Effect):
|
|||
# vector pointing along segment
|
||||
along = (p1 - p0).unit()
|
||||
# vector pointing to edge of stroke width
|
||||
perp = along.rotate_left().mul(stroke_width*0.5)
|
||||
perp = along.rotate_left().mul(stroke_width * 0.5)
|
||||
|
||||
if stroke_width == 0.0 and last_segment_direction is not None:
|
||||
if abs(1.0 - along * last_segment_direction) > 0.5:
|
||||
# if greater than 45 degree angle, stitch the corner
|
||||
#print >> sys.stderr, "corner", along * last_segment_direction
|
||||
# print >> sys.stderr, "corner", along * last_segment_direction
|
||||
rho = zigzag_spacing_px
|
||||
patch.addStitch(p0)
|
||||
patch.add_stitch(p0)
|
||||
|
||||
# iteration variable: how far we are along segment
|
||||
while (rho <= seg_len):
|
||||
left_pt = p0+along.mul(rho)+perp.mul(fact)
|
||||
patch.addStitch(left_pt)
|
||||
left_pt = p0 + along.mul(rho) + perp.mul(fact)
|
||||
patch.add_stitch(left_pt)
|
||||
rho += zigzag_spacing_px
|
||||
fact = -fact
|
||||
|
||||
|
@ -708,20 +718,20 @@ class Embroider(inkex.Effect):
|
|||
rho -= seg_len
|
||||
|
||||
if (p0 - patch.stitches[-1]).length() > 0.1:
|
||||
patch.addStitch(p0)
|
||||
patch.add_stitch(p0)
|
||||
|
||||
return [patch]
|
||||
|
||||
def filled_region_to_patchlist(self, node):
|
||||
angle = math.radians(float(get_float_param(node,'angle',0)))
|
||||
angle = math.radians(float(get_float_param(node, 'angle', 0)))
|
||||
paths = flatten(parse_path(node), self.options.flat)
|
||||
shapelyPolygon = csp_to_shapely_polygon(paths)
|
||||
threadcolor = simplestyle.parseStyle(node.get("style"))["fill"]
|
||||
return self.process_one_path(
|
||||
node,
|
||||
shapelyPolygon,
|
||||
threadcolor,
|
||||
angle)
|
||||
node,
|
||||
shapelyPolygon,
|
||||
threadcolor,
|
||||
angle)
|
||||
|
||||
def fatal(self, message):
|
||||
print >> sys.stderr, "error:", message
|
||||
|
@ -733,7 +743,7 @@ class Embroider(inkex.Effect):
|
|||
if len(csp) != 2:
|
||||
self.fatal("satin column: object %s invalid: expected exactly two sub-paths, but there are %s" % (node_id, len(csp)))
|
||||
|
||||
if self.get_style(node, "fill")!=None:
|
||||
if self.get_style(node, "fill") is not None:
|
||||
self.fatal("satin column: object %s has a fill (but should not)" % node_id)
|
||||
|
||||
if len(csp[0]) != len(csp[1]):
|
||||
|
@ -805,15 +815,15 @@ class Embroider(inkex.Effect):
|
|||
|
||||
# if offset is negative, don't contract so far that pos1
|
||||
# and pos2 switch places
|
||||
if offset_px < -distance/2.0:
|
||||
offset_px = -distance/2.0
|
||||
if offset_px < -distance / 2.0:
|
||||
offset_px = -distance / 2.0
|
||||
|
||||
midpoint = (pos2 + pos1) * 0.5
|
||||
pos1 = pos1 + (pos1 - midpoint).unit() * offset_px
|
||||
pos2 = pos2 + (pos2 - midpoint).unit() * offset_px
|
||||
|
||||
|
||||
return pos1, pos2
|
||||
|
||||
|
||||
def walk_paths(spacing, offset):
|
||||
# Take a bezier segment from each path in turn, and plot out an
|
||||
# equal number of points on each side. Later code can alternate
|
||||
|
@ -821,46 +831,46 @@ class Embroider(inkex.Effect):
|
|||
|
||||
side1 = []
|
||||
side2 = []
|
||||
|
||||
|
||||
def add_pair(pos1, pos2):
|
||||
# Stitches in satin tend to pull toward each other. We can compensate
|
||||
# by spreading the points out.
|
||||
pos1, pos2 = offset_points(pos1, pos2, offset)
|
||||
side1.append(pos1)
|
||||
side2.append(pos2)
|
||||
|
||||
|
||||
remainder_path1 = []
|
||||
remainder_path2 = []
|
||||
|
||||
|
||||
for segment in xrange(1, len(path1)):
|
||||
# construct the current bezier segments
|
||||
bezier1 = (path1[segment - 1][1], # point from previous 3-tuple
|
||||
path1[segment - 1][2], # "after" control point from previous 3-tuple
|
||||
path1[segment][0], # "before" control point from this 3-tuple
|
||||
path1[segment][1], # point from this 3-tuple
|
||||
)
|
||||
|
||||
bezier1 = (path1[segment - 1][1], # point from previous 3-tuple
|
||||
path1[segment - 1][2], # "after" control point from previous 3-tuple
|
||||
path1[segment][0], # "before" control point from this 3-tuple
|
||||
path1[segment][1], # point from this 3-tuple
|
||||
)
|
||||
|
||||
bezier2 = (path2[segment - 1][1],
|
||||
path2[segment - 1][2],
|
||||
path2[segment][0],
|
||||
path2[segment][1],
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
# Here's what I want to be able to do. However, beziertatlength is so incredibly slow that it's unusable.
|
||||
#for stitch in xrange(num_zigzags):
|
||||
# patch.addStitch(bezierpointatt(bezier1, beziertatlength(bezier1, stitch_len1 * stitch)))
|
||||
# patch.addStitch(bezierpointatt(bezier2, beziertatlength(bezier2, stitch_len2 * (stitch + 0.5))))
|
||||
|
||||
# for stitch in xrange(num_zigzags):
|
||||
# patch.add_stitch(bezierpointatt(bezier1, beziertatlength(bezier1, stitch_len1 * stitch)))
|
||||
# patch.add_stitch(bezierpointatt(bezier2, beziertatlength(bezier2, stitch_len2 * (stitch + 0.5))))
|
||||
|
||||
# Instead, flatten the beziers down to a set of line segments.
|
||||
subpath1 = remainder_path1 + flatten([[path1[segment - 1], path1[segment]]], self.options.flat)[0]
|
||||
subpath2 = remainder_path2 + flatten([[path2[segment - 1], path2[segment]]], self.options.flat)[0]
|
||||
|
||||
|
||||
len1 = shgeo.LineString(subpath1).length
|
||||
len2 = shgeo.LineString(subpath2).length
|
||||
|
||||
|
||||
subpath1 = [PyEmb.Point(*p) for p in subpath1]
|
||||
subpath2 = [PyEmb.Point(*p) for p in subpath2]
|
||||
|
||||
|
||||
# Base the number of stitches in each section on the _longest_ of
|
||||
# the two beziers. Otherwise, things could get too sparse when one
|
||||
# side is significantly longer (e.g. when going around a corner).
|
||||
|
@ -868,73 +878,73 @@ class Embroider(inkex.Effect):
|
|||
# cram too many stitches on the short bezier. The user will need
|
||||
# to avoid this through careful construction of paths.
|
||||
num_points = max(len1, len2) / spacing
|
||||
|
||||
|
||||
spacing1 = len1 / num_points
|
||||
spacing2 = len2 / num_points
|
||||
|
||||
|
||||
def walk(path, start_pos, start_index, distance):
|
||||
# Move <distance> pixels along <path>'s line segments.
|
||||
# <start_index> is the index of the line segment in <path> that
|
||||
# we're currently on. <start_pos> is where along that line
|
||||
# segment we are. Return a new position and index.
|
||||
|
||||
|
||||
pos = start_pos
|
||||
index = start_index
|
||||
|
||||
|
||||
if index >= len(path) - 1:
|
||||
# it's possible we'll go too far due to inaccuracy in the
|
||||
# bezier length calculation
|
||||
return start_pos, start_index
|
||||
|
||||
|
||||
while True:
|
||||
segment_end = path[index + 1]
|
||||
segment_remaining = (segment_end - pos)
|
||||
distance_remaining = segment_remaining.length()
|
||||
|
||||
|
||||
if distance_remaining > distance:
|
||||
return pos + segment_remaining.unit().mul(distance), index
|
||||
else:
|
||||
index += 1
|
||||
|
||||
|
||||
if index >= len(path) - 1:
|
||||
return segment_end, index
|
||||
|
||||
|
||||
distance -= distance_remaining
|
||||
pos = segment_end
|
||||
|
||||
|
||||
pos1 = subpath1[0]
|
||||
i1 = 0
|
||||
|
||||
|
||||
pos2 = subpath2[0]
|
||||
i2 = 0
|
||||
|
||||
|
||||
# if num_zigzags >= 1.0:
|
||||
# for stitch in xrange(int(num_zigzags) + 1):
|
||||
for i in xrange(int(num_points)):
|
||||
add_pair(pos1, pos2)
|
||||
|
||||
|
||||
pos2, i2 = walk(subpath2, pos2, i2, spacing2)
|
||||
pos1, i1 = walk(subpath1, pos1, i1, spacing1)
|
||||
|
||||
|
||||
if i1 < len(subpath1) - 1:
|
||||
remainder_path1 = [pos1] + subpath1[i1 + 1:]
|
||||
else:
|
||||
remainder_path1 = []
|
||||
|
||||
|
||||
if i2 < len(subpath2) - 1:
|
||||
remainder_path2 = [pos2] + subpath2[i2 + 1:]
|
||||
else:
|
||||
remainder_path2 = []
|
||||
|
||||
|
||||
remainder_path1 = [p.as_tuple() for p in remainder_path1]
|
||||
remainder_path2 = [p.as_tuple() for p in remainder_path2]
|
||||
|
||||
|
||||
# We're off by one in the algorithm above, so we need one more
|
||||
# pair of points. We also want to add points at the very end to
|
||||
# make sure we match the vectors on screen as best as possible.
|
||||
# Try to avoid doing both if they're going to stack up too
|
||||
# closely.
|
||||
|
||||
|
||||
end1 = PyEmb.Point(*remainder_path1[-1])
|
||||
end2 = PyEmb.Point(*remainder_path2[-1])
|
||||
if (end1 - pos1).length() > 0.3 * spacing:
|
||||
|
@ -963,13 +973,13 @@ class Embroider(inkex.Effect):
|
|||
|
||||
patch = Patch(color=threadcolor)
|
||||
|
||||
sides = walk_paths(zigzag_spacing/2.0, -inset)
|
||||
sides = [sides[0][::2] + list(reversed(sides[0][1::2])), sides[1][1::2] + list(reversed(sides[1][::2]))]
|
||||
sides = walk_paths(zigzag_spacing / 2.0, -inset)
|
||||
sides = [sides[0][::2] + list(reversed(sides[0][1::2])), sides[1][1::2] + list(reversed(sides[1][::2]))]
|
||||
|
||||
# this fancy bit of iterable magic just repeatedly takes a point
|
||||
# from each list in turn
|
||||
for point in chain.from_iterable(izip(*sides)):
|
||||
patch.addStitch(point)
|
||||
patch.add_stitch(point)
|
||||
|
||||
return patch
|
||||
|
||||
|
@ -984,7 +994,7 @@ class Embroider(inkex.Effect):
|
|||
sides = walk_paths(zigzag_spacing, pull_compensation)
|
||||
|
||||
for point in chain.from_iterable(izip(*sides)):
|
||||
patch.addStitch(point)
|
||||
patch.add_stitch(point)
|
||||
|
||||
return patch
|
||||
|
||||
|
@ -1012,7 +1022,7 @@ class Embroider(inkex.Effect):
|
|||
return [patch]
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.setrecursionlimit(100000);
|
||||
sys.setrecursionlimit(100000)
|
||||
e = Embroider()
|
||||
e.affect()
|
||||
dbg.flush()
|
||||
|
|
|
@ -14,7 +14,7 @@ import inkex
|
|||
class EmbroiderParams(inkex.Effect):
|
||||
def __init__(self, *args, **kwargs):
|
||||
inkex.Effect.__init__(self)
|
||||
|
||||
|
||||
self.params = ["zigzag_spacing",
|
||||
"stitch_length",
|
||||
"row_spacing",
|
||||
|
@ -31,7 +31,7 @@ class EmbroiderParams(inkex.Effect):
|
|||
"satin_center_walk",
|
||||
"satin_zigzag_underlay_spacing",
|
||||
]
|
||||
|
||||
|
||||
for param in self.params:
|
||||
self.OptionParser.add_option("--%s" % param, default="")
|
||||
|
||||
|
@ -40,7 +40,7 @@ class EmbroiderParams(inkex.Effect):
|
|||
for param in self.params:
|
||||
value = getattr(self.options, param).strip()
|
||||
param = "embroider_" + param
|
||||
|
||||
|
||||
if node.get(param) is not None and not value:
|
||||
# only overwrite existing params if they gave a value
|
||||
continue
|
||||
|
|
Ładowanie…
Reference in New Issue