summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embroider.py1534
1 files changed, 767 insertions, 767 deletions
diff --git a/embroider.py b/embroider.py
index 320c214a..1b6f2c0f 100644
--- a/embroider.py
+++ b/embroider.py
@@ -117,802 +117,802 @@ def flatten(path, flatness):
return flattened
def bboxarea(poly):
- x0=None
- x1=None
- y0=None
- y1=None
- for pt in poly:
- if (x0==None or pt[0]<x0): x0 = pt[0]
- if (x1==None or pt[0]>x1): x1 = pt[0]
- if (y0==None or pt[1]<y0): y0 = pt[1]
- if (y1==None or pt[1]>y1): y1 = pt[1]
- return (x1-x0)*(y1-y0)
+ x0=None
+ x1=None
+ y0=None
+ y1=None
+ for pt in poly:
+ if (x0==None or pt[0]<x0): x0 = pt[0]
+ if (x1==None or pt[0]>x1): x1 = pt[0]
+ if (y0==None or pt[1]<y0): y0 = pt[1]
+ if (y1==None or pt[1]>y1): y1 = pt[1]
+ return (x1-x0)*(y1-y0)
def area(poly):
- return bboxarea(poly)
+ return bboxarea(poly)
def byarea(a,b):
- return -cmp(area(a), area(b))
+ return -cmp(area(a), area(b))
def cspToShapelyPolygon(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 (dp > 0.01):
- # I think too-close points confuse shapely.
- point_ary.append(pt)
- last_pt = pt
- else:
- last_pt = pt
- poly_ary.append(point_ary)
- # shapely's idea of "holes" are to subtract everything in the second set
- # from the first. So let's at least make sure the "first" thing is the
- # biggest path.
- poly_ary.sort(byarea)
-
- polygon = shgeo.MultiPolygon([(poly_ary[0], poly_ary[1:])])
- return polygon
+ 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 (dp > 0.01):
+ # I think too-close points confuse shapely.
+ point_ary.append(pt)
+ last_pt = pt
+ else:
+ last_pt = pt
+ poly_ary.append(point_ary)
+ # shapely's idea of "holes" are to subtract everything in the second set
+ # from the first. So let's at least make sure the "first" thing is the
+ # biggest path.
+ poly_ary.sort(byarea)
+
+ polygon = shgeo.MultiPolygon([(poly_ary[0], poly_ary[1:])])
+ return polygon
def shapelyCoordsToSvgD(geo):
- coords = list(geo.coords)
- new_path = []
- new_path.append(['M', coords[0]])
- for c in coords[1:]:
- new_path.append(['L', c])
- return simplepath.formatPath(new_path)
+ coords = list(geo.coords)
+ new_path = []
+ new_path.append(['M', coords[0]])
+ for c in coords[1:]:
+ new_path.append(['L', c])
+ return simplepath.formatPath(new_path)
def shapelyLineSegmentToPyTuple(shline):
- tuple = ((shline.coords[0][0],shline.coords[0][1]),
- (shline.coords[1][0],shline.coords[1][1]))
- return tuple
+ tuple = ((shline.coords[0][0],shline.coords[0][1]),
+ (shline.coords[1][0],shline.coords[1][1]))
+ return tuple
def dupNodeAttrs(node):
- n2 = E.node()
- for k in node.attrib.keys():
- n2.attrib[k] = node.attrib[k]
- del n2.attrib["id"]
- del n2.attrib["d"]
- return n2
+ n2 = E.node()
+ for k in node.attrib.keys():
+ n2.attrib[k] = node.attrib[k]
+ del n2.attrib["id"]
+ del n2.attrib["d"]
+ return n2
class Patch:
- def __init__(self, color, sortorder, stitches=None):
- self.color = color
- self.sortorder = sortorder
- if (stitches!=None):
- self.stitches = stitches
- else:
- self.stitches = []
+ def __init__(self, color, sortorder, stitches=None):
+ self.color = color
+ self.sortorder = sortorder
+ if (stitches!=None):
+ self.stitches = stitches
+ else:
+ self.stitches = []
- def addStitch(self, stitch):
- self.stitches.append(stitch)
+ def addStitch(self, stitch):
+ self.stitches.append(stitch)
- def reverse(self):
- return Patch(self.color, self.sortorder, self.stitches[::-1])
+ def reverse(self):
+ return Patch(self.color, self.sortorder, self.stitches[::-1])
class DebugHole:
- pass
+ pass
class PatchList:
- def __init__(self, patches):
- self.patches = patches
-
- def __len__(self):
- return len(self.patches)
-
- def sort_by_sortorder(self):
- def by_sort_order(a,b):
- return cmp(a.sortorder, b.sortorder)
- self.patches.sort(by_sort_order)
-
- def partition_by_color(self):
- self.sort_by_sortorder()
- #dbg.write("Sorted by sortorder:\n");
- #dbg.write(" %s\n" % ("\n".join(map(lambda p: str(p.sortorder), self.patches))))
- out = []
- lastPatch = None
- for patch in self.patches:
- if (lastPatch!=None and patch.sortorder==lastPatch.sortorder):
- out[-1].patches.append(patch)
- else:
- out.append(PatchList([patch]))
- lastPatch = patch
- #dbg.write("Emitted %s partitions\n" % len(out))
- return out
-
- def tsp_by_color(self):
- list_of_patchLists = self.partition_by_color()
- for patchList in list_of_patchLists:
- patchList.traveling_salesman()
- return PatchList(reduce(operator.add,
- map(lambda pl: pl.patches, list_of_patchLists)))
-
-# # TODO apparently dead code; replaced by partition_by_color above
-# def clump_like_colors_together(self):
-# out = PatchList([])
-# lastPatch = None
-# for patch in self.patches:
-# if (lastPatch!=None and patch.color==lastPatch.color):
-# out.patches[-1] = Patch(
-# out.patches[-1].color,
-# out.patches[-1].sortorder,
-# out.patches[-1].stitches+patch.stitches)
-# else:
-# out.patches.append(patch)
-# lastPatch = patch
-# return out
-
- def get(self, i):
- if (i<0 or i>=len(self.patches)):
- return None
- return self.patches[i]
-
- def cost(self, a, b):
- if (a==None or b==None):
- rc = 0.0
- else:
- rc = (a.stitches[-1] - b.stitches[0]).length()
- #dbg.write("cost(%s, %s) = %5.1f\n" % (a, b, rc))
- return rc
-
- def try_swap(self, i, j):
- # i,j are indices;
- #dbg.write("swap(%d, %d)\n" % (i,j))
- oldCost = (
- self.cost(self.get(i-1), self.get(i))
- +self.cost(self.get(i), self.get(i+1))
- +self.cost(self.get(j-1), self.get(j))
- +self.cost(self.get(j), self.get(j+1)))
- npi = self.get(j)
- npj = self.get(i)
- rpi = npi.reverse()
- rpj = npj.reverse()
- options = [
- (npi,npj),
- (rpi,npj),
- (npi,rpj),
- (rpi,rpj),
- ]
- def costOf(np):
- (npi,npj) = np
- return (
- self.cost(self.get(i-1), npi)
- +self.cost(npi, self.get(i+1))
- +self.cost(self.get(j-1), npj)
- +self.cost(npj, self.get(j+1)))
- costs = map(lambda o: (costOf(o), o), options)
- costs.sort()
- (cost,option) = costs[0]
- savings = oldCost - cost
- if (savings > 0):
- self.patches[i] = option[0]
- self.patches[j] = option[1]
- success = "!"
- else:
- success = "."
-
- #dbg.write("old %5.1f new %5.1f savings: %5.1f\n" % (oldCost, cost, savings))
- return success
-
- def try_reverse(self, i):
- #dbg.write("reverse(%d)\n" % i)
- oldCost = (self.cost(self.get(i-1), self.get(i))
- +self.cost(self.get(i), self.get(i+1)))
- reversed = self.get(i).reverse()
- newCost = (self.cost(self.get(i-1), reversed)
- +self.cost(reversed, self.get(i+1)))
- savings = oldCost - newCost
- if (savings > 0.0):
- self.patches[i] = reversed
- success = "#"
- else:
- success = "_"
- return success
-
- def traveling_salesman(self):
- # shockingly, this is non-optimal and pretty much non-efficient. Sorry.
- self.centroid = PyEmb.Point(0.0,0.0)
- self.pointList = []
- for patch in self.patches:
- def visit(idx):
- ep = deepcopy(patch.stitches[idx])
- ep.patch = patch
- self.centroid+=ep
- self.pointList.append(ep)
-
- visit(0)
- visit(-1)
-
- self.centroid = self.centroid.mul(1.0/float(len(self.pointList)))
-
- def linear_min(list, func):
- min_item = None
- min_value = None
- for item in list:
- value = func(item)
- #dbg.write('linear_min %s: value %s => %s (%s)\n' % (func, item, value, value<min_value))
- if (min_value==None or value<min_value):
- min_item = item
- min_value = value
- #dbg.write('linear_min final item %s value %s\n' % (min_item, min_value))
- return min_item
-
- sortedPatchList = PatchList([])
- def takePatchStartingAtPoint(point):
- patch = point.patch
- #dbg.write("takePatchStartingAtPoint angling for patch %s--%s\n" % (patch.stitches[0],patch.stitches[-1]))
- self.pointList = filter(lambda pt: pt.patch!=patch, self.pointList)
- reversed = ""
- if (point!=patch.stitches[0]):
- reversed = " (reversed)"
- #dbg.write('patch.stitches[0] %s point %s match %s\n' % (patch.stitches[0], point, point==patch.stitches[0]))
- patch = patch.reverse()
- sortedPatchList.patches.append(patch)
- #dbg.write('took patch %s--%s %s\n' % (patch.stitches[0], patch.stitches[-1], reversed))
-
- # Take the patch farthest from the centroid first
- # O(n)
- #dbg.write('centroid: %s\n' % self.centroid)
- def neg_distance_from_centroid(p):
- return -(p-self.centroid).length()
- farthestPoint = linear_min(self.pointList, neg_distance_from_centroid)
- takePatchStartingAtPoint(farthestPoint)
- #sortedPatchList.patches[0].color = "#000000"
-
- # Then greedily take closer-and-closer patches
- # O(n^2)
- while (len(self.pointList)>0):
- #dbg.write('pass %s\n' % len(self.pointList));
- last_point = sortedPatchList.patches[-1].stitches[-1]
- #dbg.write('last_point now %s\n' % last_point)
- def distance_from_last_point(p):
- return (p-last_point).length()
- nearestPoint = linear_min(self.pointList, distance_from_last_point)
- takePatchStartingAtPoint(nearestPoint)
-
- # install the initial result
- self.patches = sortedPatchList.patches
-
- if (1):
- # Then hill-climb.
- #dbg.write("len(self.patches) = %d\n" % len(self.patches))
- count = 0
- successStr = ""
- while (count < 100):
- i = random.randint(0, len(self.patches)-1)
- j = random.randint(0, len(self.patches)-1)
- successStr += self.try_swap(i,j)
-
- count += 1
- # tidy up at end as best we can
- for i in range(len(self.patches)):
- successStr += self.try_reverse(i)
-
- #dbg.write("success: %s\n" % successStr)
+ def __init__(self, patches):
+ self.patches = patches
+
+ def __len__(self):
+ return len(self.patches)
+
+ def sort_by_sortorder(self):
+ def by_sort_order(a,b):
+ return cmp(a.sortorder, b.sortorder)
+ self.patches.sort(by_sort_order)
+
+ def partition_by_color(self):
+ self.sort_by_sortorder()
+ #dbg.write("Sorted by sortorder:\n");
+ #dbg.write(" %s\n" % ("\n".join(map(lambda p: str(p.sortorder), self.patches))))
+ out = []
+ lastPatch = None
+ for patch in self.patches:
+ if (lastPatch!=None and patch.sortorder==lastPatch.sortorder):
+ out[-1].patches.append(patch)
+ else:
+ out.append(PatchList([patch]))
+ lastPatch = patch
+ #dbg.write("Emitted %s partitions\n" % len(out))
+ return out
+
+ def tsp_by_color(self):
+ list_of_patchLists = self.partition_by_color()
+ for patchList in list_of_patchLists:
+ patchList.traveling_salesman()
+ return PatchList(reduce(operator.add,
+ map(lambda pl: pl.patches, list_of_patchLists)))
+
+# # TODO apparently dead code; replaced by partition_by_color above
+# def clump_like_colors_together(self):
+# out = PatchList([])
+# lastPatch = None
+# for patch in self.patches:
+# if (lastPatch!=None and patch.color==lastPatch.color):
+# out.patches[-1] = Patch(
+# out.patches[-1].color,
+# out.patches[-1].sortorder,
+# out.patches[-1].stitches+patch.stitches)
+# else:
+# out.patches.append(patch)
+# lastPatch = patch
+# return out
+
+ def get(self, i):
+ if (i<0 or i>=len(self.patches)):
+ return None
+ return self.patches[i]
+
+ def cost(self, a, b):
+ if (a==None or b==None):
+ rc = 0.0
+ else:
+ rc = (a.stitches[-1] - b.stitches[0]).length()
+ #dbg.write("cost(%s, %s) = %5.1f\n" % (a, b, rc))
+ return rc
+
+ def try_swap(self, i, j):
+ # i,j are indices;
+ #dbg.write("swap(%d, %d)\n" % (i,j))
+ oldCost = (
+ self.cost(self.get(i-1), self.get(i))
+ +self.cost(self.get(i), self.get(i+1))
+ +self.cost(self.get(j-1), self.get(j))
+ +self.cost(self.get(j), self.get(j+1)))
+ npi = self.get(j)
+ npj = self.get(i)
+ rpi = npi.reverse()
+ rpj = npj.reverse()
+ options = [
+ (npi,npj),
+ (rpi,npj),
+ (npi,rpj),
+ (rpi,rpj),
+ ]
+ def costOf(np):
+ (npi,npj) = np
+ return (
+ self.cost(self.get(i-1), npi)
+ +self.cost(npi, self.get(i+1))
+ +self.cost(self.get(j-1), npj)
+ +self.cost(npj, self.get(j+1)))
+ costs = map(lambda o: (costOf(o), o), options)
+ costs.sort()
+ (cost,option) = costs[0]
+ savings = oldCost - cost
+ if (savings > 0):
+ self.patches[i] = option[0]
+ self.patches[j] = option[1]
+ success = "!"
+ else:
+ success = "."
+
+ #dbg.write("old %5.1f new %5.1f savings: %5.1f\n" % (oldCost, cost, savings))
+ return success
+
+ def try_reverse(self, i):
+ #dbg.write("reverse(%d)\n" % i)
+ oldCost = (self.cost(self.get(i-1), self.get(i))
+ +self.cost(self.get(i), self.get(i+1)))
+ reversed = self.get(i).reverse()
+ newCost = (self.cost(self.get(i-1), reversed)
+ +self.cost(reversed, self.get(i+1)))
+ savings = oldCost - newCost
+ if (savings > 0.0):
+ self.patches[i] = reversed
+ success = "#"
+ else:
+ success = "_"
+ return success
+
+ def traveling_salesman(self):
+ # shockingly, this is non-optimal and pretty much non-efficient. Sorry.
+ self.centroid = PyEmb.Point(0.0,0.0)
+ self.pointList = []
+ for patch in self.patches:
+ def visit(idx):
+ ep = deepcopy(patch.stitches[idx])
+ ep.patch = patch
+ self.centroid+=ep
+ self.pointList.append(ep)
+
+ visit(0)
+ visit(-1)
+
+ self.centroid = self.centroid.mul(1.0/float(len(self.pointList)))
+
+ def linear_min(list, func):
+ min_item = None
+ min_value = None
+ for item in list:
+ value = func(item)
+ #dbg.write('linear_min %s: value %s => %s (%s)\n' % (func, item, value, value<min_value))
+ if (min_value==None or value<min_value):
+ min_item = item
+ min_value = value
+ #dbg.write('linear_min final item %s value %s\n' % (min_item, min_value))
+ return min_item
+
+ sortedPatchList = PatchList([])
+ def takePatchStartingAtPoint(point):
+ patch = point.patch
+ #dbg.write("takePatchStartingAtPoint angling for patch %s--%s\n" % (patch.stitches[0],patch.stitches[-1]))
+ self.pointList = filter(lambda pt: pt.patch!=patch, self.pointList)
+ reversed = ""
+ if (point!=patch.stitches[0]):
+ reversed = " (reversed)"
+ #dbg.write('patch.stitches[0] %s point %s match %s\n' % (patch.stitches[0], point, point==patch.stitches[0]))
+ patch = patch.reverse()
+ sortedPatchList.patches.append(patch)
+ #dbg.write('took patch %s--%s %s\n' % (patch.stitches[0], patch.stitches[-1], reversed))
+
+ # Take the patch farthest from the centroid first
+ # O(n)
+ #dbg.write('centroid: %s\n' % self.centroid)
+ def neg_distance_from_centroid(p):
+ return -(p-self.centroid).length()
+ farthestPoint = linear_min(self.pointList, neg_distance_from_centroid)
+ takePatchStartingAtPoint(farthestPoint)
+ #sortedPatchList.patches[0].color = "#000000"
+
+ # Then greedily take closer-and-closer patches
+ # O(n^2)
+ while (len(self.pointList)>0):
+ #dbg.write('pass %s\n' % len(self.pointList));
+ last_point = sortedPatchList.patches[-1].stitches[-1]
+ #dbg.write('last_point now %s\n' % last_point)
+ def distance_from_last_point(p):
+ return (p-last_point).length()
+ nearestPoint = linear_min(self.pointList, distance_from_last_point)
+ takePatchStartingAtPoint(nearestPoint)
+
+ # install the initial result
+ self.patches = sortedPatchList.patches
+
+ if (1):
+ # Then hill-climb.
+ #dbg.write("len(self.patches) = %d\n" % len(self.patches))
+ count = 0
+ successStr = ""
+ while (count < 100):
+ i = random.randint(0, len(self.patches)-1)
+ j = random.randint(0, len(self.patches)-1)
+ successStr += self.try_swap(i,j)
+
+ count += 1
+ # tidy up at end as best we can
+ for i in range(len(self.patches)):
+ successStr += self.try_reverse(i)
+
+ #dbg.write("success: %s\n" % successStr)
class EmbroideryObject:
- def __init__(self, patchList, row_spacing_px):
- self.patchList = patchList
- self.row_spacing_px = row_spacing_px
-
-
- def make_preamble_stitch(self, lastp, nextp):
- def fromPolar(r, phi):
- x = r * math.cos(phi)
- y = r * math.sin(phi)
- return (x, y)
-
- def toPolar(x, y):
- r = math.sqrt(x ** 2 + y ** 2)
- if r == 0:
- phi = 0
- elif y == 0:
- phi = 0 if x > 0 else math.pi
- else:
- phi = cmp(y, 0) * math.acos(x / r)
- return (r, phi)
-
- v = nextp - lastp
- (r, phi) = toPolar(v.x, v.y)
-
- PREAMBLE_MAX_DIST = 0.5 * pixels_per_millimeter # 1/2mm
- if r < PREAMBLE_MAX_DIST:
- # nextp is close enough to lastp, so we don't generate
- # extra points in between, but just use nextp
- return nextp
- r = PREAMBLE_MAX_DIST
- (x, y) = fromPolar(r, phi)
- return PyEmb.Point(x, y) + lastp
-
- def emit_file(self, filename, output_format, collapse_len_px, add_preamble):
- emb = PyEmb.Embroidery()
- lastStitch = None
- lastColor = None
- for patch in self.patchList.patches:
- jumpStitch = True
- for stitch in patch.stitches:
- if lastStitch and lastColor == patch.color:
- c = math.sqrt((stitch.x - lastStitch.x) ** 2 + (stitch.y - lastStitch.y) ** 2)
- #dbg.write("stitch length: %f (%d/%d -> %d/%d)\n" % (c, lastStitch.x, lastStitch.y, stitch.x, stitch.y))
-
- if c == 0:
- # filter out duplicate successive stitches
- jumpStitch = False
- continue
-
- if jumpStitch:
- # consider collapsing jump stich, if it is pretty short
- if c < collapse_len_px:
- #dbg.write("... collapsed\n")
- jumpStitch = False
-
- #dbg.write("stitch color %s\n" % patch.color)
-
- newStitch = PyEmb.Point(stitch.x, -stitch.y)
- newStitch.color = patch.color
- newStitch.jumpStitch = jumpStitch
- emb.addStitch(newStitch)
-
- if jumpStitch and add_preamble != "0":
- locs = [ newStitch ]
- i = 0
- nextp = PyEmb.Point(patch.stitches[i].x, -patch.stitches[i].y)
-
- try:
- for j in xrange(1, 4):
- if locs[-1] == nextp:
- i += 1
- nextp = PyEmb.Point(patch.stitches[i].x, -patch.stitches[i].y)
- locs.append(self.make_preamble_stitch(locs[-1], nextp))
- except IndexError:
- # happens when the patch is very short and we increment i beyond the number of stitches
- pass
- #dbg.write("preamble locations: %s\n" % locs)
-
- for j in add_preamble[1:]:
- try:
- stitch = deepcopy(locs[int(j)])
- stitch.color = patch.color
- stitch.jumpStitch = False
- emb.addStitch(stitch)
- except IndexError:
- pass
-
- jumpStitch = False
- lastStitch = newStitch
- lastColor = patch.color
-
- dx, dy = emb.translate_to_origin()
- emb.scale(1.0/pixels_per_millimeter)
-
- fp = open(filename, "wb")
-
- if output_format == "melco":
- fp.write(emb.export_melco(dbg))
- elif output_format == "csv":
- fp.write(emb.export_csv(dbg))
- elif output_format == "gcode":
- fp.write(emb.export_gcode(dbg))
- fp.close()
- emb.scale(pixels_per_millimeter)
- emb.translate(dx, dy)
- return emb
-
- def emit_inkscape(self, parent, emb):
- emb.scale((1, -1));
- for color, path in emb.export_paths(dbg):
- 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':"1",
- 'fill': 'none' }),
- 'd':simplepath.formatPath(path),
- })
-
- def bbox(self):
- x = []
- y = []
- for patch in self.patchList.patches:
- for stitch in patch.stitches:
- x.append(stitch.x)
- y.append(stitch.y)
- return (min(x), min(y), max(x), max(y))
+ def __init__(self, patchList, row_spacing_px):
+ self.patchList = patchList
+ self.row_spacing_px = row_spacing_px
+
+
+ def make_preamble_stitch(self, lastp, nextp):
+ def fromPolar(r, phi):
+ x = r * math.cos(phi)
+ y = r * math.sin(phi)
+ return (x, y)
+
+ def toPolar(x, y):
+ r = math.sqrt(x ** 2 + y ** 2)
+ if r == 0:
+ phi = 0
+ elif y == 0:
+ phi = 0 if x > 0 else math.pi
+ else:
+ phi = cmp(y, 0) * math.acos(x / r)
+ return (r, phi)
+
+ v = nextp - lastp
+ (r, phi) = toPolar(v.x, v.y)
+
+ PREAMBLE_MAX_DIST = 0.5 * pixels_per_millimeter # 1/2mm
+ if r < PREAMBLE_MAX_DIST:
+ # nextp is close enough to lastp, so we don't generate
+ # extra points in between, but just use nextp
+ return nextp
+ r = PREAMBLE_MAX_DIST
+ (x, y) = fromPolar(r, phi)
+ return PyEmb.Point(x, y) + lastp
+
+ def emit_file(self, filename, output_format, collapse_len_px, add_preamble):
+ emb = PyEmb.Embroidery()
+ lastStitch = None
+ lastColor = None
+ for patch in self.patchList.patches:
+ jumpStitch = True
+ for stitch in patch.stitches:
+ if lastStitch and lastColor == patch.color:
+ c = math.sqrt((stitch.x - lastStitch.x) ** 2 + (stitch.y - lastStitch.y) ** 2)
+ #dbg.write("stitch length: %f (%d/%d -> %d/%d)\n" % (c, lastStitch.x, lastStitch.y, stitch.x, stitch.y))
+
+ if c == 0:
+ # filter out duplicate successive stitches
+ jumpStitch = False
+ continue
+
+ if jumpStitch:
+ # consider collapsing jump stich, if it is pretty short
+ if c < collapse_len_px:
+ #dbg.write("... collapsed\n")
+ jumpStitch = False
+
+ #dbg.write("stitch color %s\n" % patch.color)
+
+ newStitch = PyEmb.Point(stitch.x, -stitch.y)
+ newStitch.color = patch.color
+ newStitch.jumpStitch = jumpStitch
+ emb.addStitch(newStitch)
+
+ if jumpStitch and add_preamble != "0":
+ locs = [ newStitch ]
+ i = 0
+ nextp = PyEmb.Point(patch.stitches[i].x, -patch.stitches[i].y)
+
+ try:
+ for j in xrange(1, 4):
+ if locs[-1] == nextp:
+ i += 1
+ nextp = PyEmb.Point(patch.stitches[i].x, -patch.stitches[i].y)
+ locs.append(self.make_preamble_stitch(locs[-1], nextp))
+ except IndexError:
+ # happens when the patch is very short and we increment i beyond the number of stitches
+ pass
+ #dbg.write("preamble locations: %s\n" % locs)
+
+ for j in add_preamble[1:]:
+ try:
+ stitch = deepcopy(locs[int(j)])
+ stitch.color = patch.color
+ stitch.jumpStitch = False
+ emb.addStitch(stitch)
+ except IndexError:
+ pass
+
+ jumpStitch = False
+ lastStitch = newStitch
+ lastColor = patch.color
+
+ dx, dy = emb.translate_to_origin()
+ emb.scale(1.0/pixels_per_millimeter)
+
+ fp = open(filename, "wb")
+
+ if output_format == "melco":
+ fp.write(emb.export_melco(dbg))
+ elif output_format == "csv":
+ fp.write(emb.export_csv(dbg))
+ elif output_format == "gcode":
+ fp.write(emb.export_gcode(dbg))
+ fp.close()
+ emb.scale(pixels_per_millimeter)
+ emb.translate(dx, dy)
+ return emb
+
+ def emit_inkscape(self, parent, emb):
+ emb.scale((1, -1));
+ for color, path in emb.export_paths(dbg):
+ 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':"1",
+ 'fill': 'none' }),
+ 'd':simplepath.formatPath(path),
+ })
+
+ def bbox(self):
+ x = []
+ y = []
+ for patch in self.patchList.patches:
+ for stitch in patch.stitches:
+ x.append(stitch.x)
+ y.append(stitch.y)
+ return (min(x), min(y), max(x), max(y))
class SortOrder:
- def __init__(self, threadcolor, layer, preserve_layers):
- self.threadcolor = threadcolor
- if (preserve_layers):
- #dbg.write("preserve_layers is true: %s %s\n" % (layer, threadcolor));
- self.sorttuple = (layer, threadcolor)
- else:
- #dbg.write("preserve_layers is false:\n");
- self.sorttuple = (threadcolor,)
-
- def __cmp__(self, other):
- return cmp(self.sorttuple, other.sorttuple)
-
- def __repr__(self):
- return "sort %s color %s" % (self.sorttuple, self.threadcolor)
+ def __init__(self, threadcolor, layer, preserve_layers):
+ self.threadcolor = threadcolor
+ if (preserve_layers):
+ #dbg.write("preserve_layers is true: %s %s\n" % (layer, threadcolor));
+ self.sorttuple = (layer, threadcolor)
+ else:
+ #dbg.write("preserve_layers is false:\n");
+ self.sorttuple = (threadcolor,)
+
+ def __cmp__(self, other):
+ return cmp(self.sorttuple, other.sorttuple)
+
+ def __repr__(self):
+ return "sort %s color %s" % (self.sorttuple, self.threadcolor)
class Embroider(inkex.Effect):
- def __init__(self, *args, **kwargs):
- #dbg.write("args: %s\n" % repr(sys.argv))
- inkex.Effect.__init__(self)
- self.stacking_order_counter = 0
- self.OptionParser.add_option("-r", "--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)")
- 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)")
- 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)")
- self.OptionParser.add_option("-c", "--collapse_len_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")
- self.OptionParser.add_option("-o", "--preserve_layers",
- action="store", type="choice",
- choices=["true","false"],
- dest="preserve_layers", default="false",
- help="Sort by stacking order instead of color")
- self.OptionParser.add_option("-H", "--hatch_filled_paths",
- action="store", type="choice",
- choices=["true","false"],
- dest="hatch_filled_paths", default="false",
- help="Use hatching lines instead of equally-spaced lines to fill paths")
- 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")
- self.OptionParser.add_option("-p", "--add_preamble",
- action="store", type="choice",
- choices=["0","010","01010","01210","012101210"],
- dest="add_preamble", default="0",
- help="Add preamble")
- self.OptionParser.add_option("-O", "--output_format",
- action="store", type="choice",
- choices=["melco", "csv", "gcode"],
- dest="output_format", default="melco",
- help="File output format")
- self.OptionParser.add_option("-F", "--filename",
- action="store", type="string",
- dest="filename", default="embroider-output.exp",
- help="Name (and possibly path) of output file")
- self.patches = []
- self.layer_cache = {}
-
- def get_sort_order(self, threadcolor, node):
- #print >> sys.stderr, "node", node.get("id"), self.layer_cache.get(node.get("id"))
- return SortOrder(threadcolor, self.layer_cache.get(node.get("id")), self.options.preserve_layers=="true")
-
- def process_one_path(self, node, shpath, threadcolor, sortorder, angle):
- #self.add_shapely_geo_to_svg(shpath.boundary, color="#c0c000")
-
- hatching = get_boolean_param(node, "hatching", self.hatching)
- row_spacing_px = get_float_param(node, "row_spacing", self.row_spacing_px)
- max_stitch_len_px = get_float_param(node, "max_stitch_length", self.max_stitch_len_px)
-
- rows_of_segments = self.intersect_region_with_grating(shpath, row_spacing_px, angle)
- segments = self.visit_segments_one_by_one(rows_of_segments)
-
- def small_stitches(patch, beg, end):
- vector = (end-beg)
- patch.addStitch(beg)
- old_dist = vector.length()
- if (old_dist < max_stitch_len_px):
- patch.addStitch(end)
- return
- one_stitch = vector.mul(1.0 / old_dist * max_stitch_len_px * random.random())
- beg = beg + one_stitch
- while (True):
- vector = (end-beg)
- dist = vector.length()
- assert(old_dist==None or dist<old_dist)
- old_dist = dist
- patch.addStitch(beg)
- if (dist < max_stitch_len_px):
- patch.addStitch(end)
- return
-
- one_stitch = vector.mul(1.0/dist*max_stitch_len_px)
- beg = beg + one_stitch
-
- swap = False
- patch = Patch(color=threadcolor,sortorder=sortorder)
- for (beg,end) in segments:
- if (swap):
- (beg,end)=(end,beg)
- if not hatching:
- swap = not swap
- small_stitches(patch, PyEmb.Point(*beg),PyEmb.Point(*end))
- return [patch]
-
- def intersect_region_with_grating(self, shpath, row_spacing_px, angle):
- #dbg.write("bounds = %s\n" % str(shpath.bounds))
- rotated_shpath = affinity.rotate(shpath, angle, use_radians = True)
- bbox = rotated_shpath.bounds
- delta = row_spacing_px * 50 # *2 should be enough but isn't. TODO: find out why, and if this always works.
- bbox = affinity.rotate(shgeo.LinearRing(((bbox[0] - delta, bbox[1] - delta), (bbox[2] + delta, bbox[1] - delta), (bbox[2] + delta, bbox[3] + delta), (bbox[0] - delta, bbox[3] + delta))), -angle, use_radians = True).coords
-
- p0 = PyEmb.Point(bbox[0][0], bbox[0][1])
- p1 = PyEmb.Point(bbox[1][0], bbox[1][1])
- p2 = PyEmb.Point(bbox[3][0], bbox[3][1])
- count = (p2 - p0).length() / row_spacing_px
- p_inc = (p2 - p0).mul(1 / count)
- count += 2
-
- rows = []
- steps = 0
- while (steps < count):
- try:
- steps += 1
- p0 += p_inc
- p1 += p_inc
- endpoints = [p0.as_tuple(), p1.as_tuple()]
- shline = shgeo.LineString(endpoints)
- res = shline.intersection(shpath)
- if (isinstance(res, shgeo.MultiLineString)):
- runs = map(shapelyLineSegmentToPyTuple, res.geoms)
- else:
- runs = [shapelyLineSegmentToPyTuple(res)]
- if self.hatching and len(rows) > 0:
- rows.append([(rows[-1][0][1], runs[0][0])])
- rows.append(runs)
- except Exception, ex:
- dbg.write("--------------\n")
- dbg.write("%s\n" % ex)
- dbg.write("%s\n" % shline)
- dbg.write("%s\n" % shpath)
- dbg.write("==============\n")
- continue
- return rows
-
- def visit_segments_one_by_one(self, rows):
- def pull_runs(rows):
- new_rows = []
- run = []
- for r in rows:
- (first,rest) = (r[0], r[1:])
- run.append(first)
- if (len(rest)>0):
- new_rows.append(rest)
- return (run, new_rows)
-
- linearized_runs = []
- count = 0
- while (len(rows) > 0):
- (one_run,rows) = pull_runs(rows)
- linearized_runs.extend(one_run)
-
- rows = rows[::-1]
- count += 1
- if (count>100): raise "kablooey"
- return linearized_runs
-
- def handle_node(self, node):
- if (node.tag == inkex.addNS('g', 'svg')):
- #dbg.write("%s\n"%str((id, etree.tostring(node, pretty_print=True))))
- #dbg.write("not a path; recursing:\n")
- for child in node.iter(self.svgpath):
- self.handle_node(child)
- return
-
- #dbg.write("Node: %s\n"%str((id, etree.tostring(node, pretty_print=True))))
-
- israw = parse_boolean(node.get('embroider_raw'))
- if (israw):
- self.patchList.patches.extend(self.path_to_patch_list(node))
- else:
- if (self.get_style(node, "fill")!=None):
- self.patchList.patches.extend(self.filled_region_to_patchlist(node))
- if (self.get_style(node, "stroke")!=None):
- self.patchList.patches.extend(self.path_to_patch_list(node))
-
- def get_style(self, node, style_name):
- style = simplestyle.parseStyle(node.get("style"))
- if (style_name not in style):
- return None
- value = style[style_name]
- if (value==None or value=="none"):
- return None
- return value
-
- def cache_layers(self):
- self.layer_cache = {}
-
- layer_tag = inkex.addNS("g", "svg")
- group_attr = inkex.addNS('groupmode', 'inkscape')
-
-
- def is_layer(node):
- return node.tag == layer_tag and node.get(group_attr) == "layer"
-
- def process(node, layer=0):
- if is_layer(node):
- layer += 1
- else:
- self.layer_cache[node.get("id")] = layer
-
- for child in node:
- layer = process(child, layer)
-
- return layer
-
- process(self.document.getroot())
-
- def effect(self):
- if self.options.preserve_layers == "true":
- self.cache_layers()
- #print >> sys.stderr, "cached stacking order:", self.stacking_order
-
- self.row_spacing_px = self.options.row_spacing_mm * pixels_per_millimeter
- self.zigzag_spacing_px = self.options.zigzag_spacing_mm * pixels_per_millimeter
- self.max_stitch_len_px = self.options.max_stitch_len_mm*pixels_per_millimeter
- self.running_stitch_len_px = self.options.running_stitch_len_mm*pixels_per_millimeter
- self.collapse_len_px = self.options.collapse_len_mm*pixels_per_millimeter
- self.hatching = self.options.hatch_filled_paths == "true"
-
- self.svgpath = inkex.addNS('path', 'svg')
- self.patchList = PatchList([])
- for node in self.selected.itervalues():
- self.handle_node(node)
-
- if not self.patchList:
- inkex.errormsg("No paths selected.")
+ def __init__(self, *args, **kwargs):
+ #dbg.write("args: %s\n" % repr(sys.argv))
+ inkex.Effect.__init__(self)
+ self.stacking_order_counter = 0
+ self.OptionParser.add_option("-r", "--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)")
+ 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)")
+ 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)")
+ self.OptionParser.add_option("-c", "--collapse_len_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")
+ self.OptionParser.add_option("-o", "--preserve_layers",
+ action="store", type="choice",
+ choices=["true","false"],
+ dest="preserve_layers", default="false",
+ help="Sort by stacking order instead of color")
+ self.OptionParser.add_option("-H", "--hatch_filled_paths",
+ action="store", type="choice",
+ choices=["true","false"],
+ dest="hatch_filled_paths", default="false",
+ help="Use hatching lines instead of equally-spaced lines to fill paths")
+ 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")
+ self.OptionParser.add_option("-p", "--add_preamble",
+ action="store", type="choice",
+ choices=["0","010","01010","01210","012101210"],
+ dest="add_preamble", default="0",
+ help="Add preamble")
+ self.OptionParser.add_option("-O", "--output_format",
+ action="store", type="choice",
+ choices=["melco", "csv", "gcode"],
+ dest="output_format", default="melco",
+ help="File output format")
+ self.OptionParser.add_option("-F", "--filename",
+ action="store", type="string",
+ dest="filename", default="embroider-output.exp",
+ help="Name (and possibly path) of output file")
+ self.patches = []
+ self.layer_cache = {}
+
+ def get_sort_order(self, threadcolor, node):
+ #print >> sys.stderr, "node", node.get("id"), self.layer_cache.get(node.get("id"))
+ return SortOrder(threadcolor, self.layer_cache.get(node.get("id")), self.options.preserve_layers=="true")
+
+ def process_one_path(self, node, shpath, threadcolor, sortorder, angle):
+ #self.add_shapely_geo_to_svg(shpath.boundary, color="#c0c000")
+
+ hatching = get_boolean_param(node, "hatching", self.hatching)
+ row_spacing_px = get_float_param(node, "row_spacing", self.row_spacing_px)
+ max_stitch_len_px = get_float_param(node, "max_stitch_length", self.max_stitch_len_px)
+
+ rows_of_segments = self.intersect_region_with_grating(shpath, row_spacing_px, angle)
+ segments = self.visit_segments_one_by_one(rows_of_segments)
+
+ def small_stitches(patch, beg, end):
+ vector = (end-beg)
+ patch.addStitch(beg)
+ old_dist = vector.length()
+ if (old_dist < max_stitch_len_px):
+ patch.addStitch(end)
+ return
+ one_stitch = vector.mul(1.0 / old_dist * max_stitch_len_px * random.random())
+ beg = beg + one_stitch
+ while (True):
+ vector = (end-beg)
+ dist = vector.length()
+ assert(old_dist==None or dist<old_dist)
+ old_dist = dist
+ patch.addStitch(beg)
+ if (dist < max_stitch_len_px):
+ patch.addStitch(end)
return
- self.patchList = self.patchList.tsp_by_color()
- #dbg.write("patch count: %d\n" % len(self.patchList.patches))
-
- if self.options.hide_layers:
- self.hide_layers()
-
- eo = EmbroideryObject(self.patchList, self.row_spacing_px)
- emb = eo.emit_file(self.options.filename, self.options.output_format,
- self.collapse_len_px, self.options.add_preamble)
-
- new_layer = inkex.etree.SubElement(self.document.getroot(),
- 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')
- eo.emit_inkscape(new_layer, emb)
-
- def emit_inkscape_bbox(self, parent, eo):
- (x0, y0, x1, y1) = eo.bbox()
- new_path = []
- new_path.append(['M', (x0,y0)])
- new_path.append(['L', (x1,y0)])
- new_path.append(['L', (x1,y1)])
- new_path.append(['L', (x0,y1)])
- new_path.append(['L', (x0,y0)])
- inkex.etree.SubElement(parent,
- inkex.addNS('path', 'svg'),
- { 'style':simplestyle.formatStyle(
- { 'stroke': '#ff00ff',
- 'stroke-width':str(1),
- 'fill': 'none' }),
- 'd':simplepath.formatPath(new_path),
- })
-
- def hide_layers(self):
- for g in self.document.getroot().findall(inkex.addNS("g","svg")):
- if g.get(inkex.addNS("groupmode", "inkscape")) == "layer":
- g.set("style", "display:none")
-
- def path_to_patch_list(self, node):
- threadcolor = simplestyle.parseStyle(node.get("style"))["stroke"]
- stroke_width_str = simplestyle.parseStyle(node.get("style"))["stroke-width"]
- if (stroke_width_str.endswith("px")):
- # don't really know how we should be doing unit conversions.
- # but let's hope px are kind of like pts?
- stroke_width_str = stroke_width_str[:-2]
- stroke_width = float(stroke_width_str)
- #dbg.write("stroke_width is <%s>\n" % repr(stroke_width))
- #dbg.flush()
-
- running_stitch_len_px = get_float_param(node, "stitch_length", self.running_stitch_len_px)
- zigzag_spacing_px = get_float_param(node, "zigzag_spacing", self.zigzag_spacing_px)
- repeats = get_int_param(node, "repeats", 1)
-
- sortorder = self.get_sort_order(threadcolor, node)
- paths = flatten(parse_path(node), self.options.flat)
-
- # regularize the points lists.
- # (If we're parsing beziers, there will be a list of multi-point
- # subarrays.)
-
- patches = []
-
- for path in paths:
- path = [PyEmb.Point(x, y) for x, y in path]
- if (stroke_width <= STROKE_MIN):
- #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, sortorder)
- else:
- patch = self.stroke_points(path, zigzag_spacing_px*0.5, stroke_width, repeats, threadcolor, sortorder)
- patches.extend(patch)
-
- return patches
-
- def stroke_points(self, emb_point_list, zigzag_spacing_px, stroke_width, repeats, threadcolor, sortorder):
- patch = Patch(color=threadcolor, sortorder=sortorder)
- p0 = emb_point_list[0]
- rho = 0.0
- fact = 1
-
- for repeat in xrange(repeats):
- if repeat % 2 == 0:
- order = range(1, len(emb_point_list))
- else:
- order = range(-2, -len(emb_point_list) - 1, -1)
-
- for segi in order:
- p1 = emb_point_list[segi]
-
- # how far we have to go along segment
- seg_len = (p1 - p0).length()
- if (seg_len == 0):
- continue
-
- # vector pointing along segment
- along = (p1 - p0).unit()
- # vector pointing to edge of stroke width
- perp = along.rotate_left().mul(stroke_width*0.5)
-
- # 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)
- rho += zigzag_spacing_px
- fact = -fact
-
- p0 = p1
- rho -= seg_len
-
- return [patch]
-
- def filled_region_to_patchlist(self, node):
- angle = math.radians(float(get_float_param(node,'angle',0)))
- paths = flatten(parse_path(node), self.options.flat)
- shapelyPolygon = cspToShapelyPolygon(paths)
- threadcolor = simplestyle.parseStyle(node.get("style"))["fill"]
- sortorder = self.get_sort_order(threadcolor, node)
- return self.process_one_path(
- node,
- shapelyPolygon,
- threadcolor,
- sortorder,
- angle)
-
- #TODO def make_stroked_patch(self, node):
+ one_stitch = vector.mul(1.0/dist*max_stitch_len_px)
+ beg = beg + one_stitch
+
+ swap = False
+ patch = Patch(color=threadcolor,sortorder=sortorder)
+ for (beg,end) in segments:
+ if (swap):
+ (beg,end)=(end,beg)
+ if not hatching:
+ swap = not swap
+ small_stitches(patch, PyEmb.Point(*beg),PyEmb.Point(*end))
+ return [patch]
+
+ def intersect_region_with_grating(self, shpath, row_spacing_px, angle):
+ #dbg.write("bounds = %s\n" % str(shpath.bounds))
+ rotated_shpath = affinity.rotate(shpath, angle, use_radians = True)
+ bbox = rotated_shpath.bounds
+ delta = row_spacing_px * 50 # *2 should be enough but isn't. TODO: find out why, and if this always works.
+ bbox = affinity.rotate(shgeo.LinearRing(((bbox[0] - delta, bbox[1] - delta), (bbox[2] + delta, bbox[1] - delta), (bbox[2] + delta, bbox[3] + delta), (bbox[0] - delta, bbox[3] + delta))), -angle, use_radians = True).coords
+
+ p0 = PyEmb.Point(bbox[0][0], bbox[0][1])
+ p1 = PyEmb.Point(bbox[1][0], bbox[1][1])
+ p2 = PyEmb.Point(bbox[3][0], bbox[3][1])
+ count = (p2 - p0).length() / row_spacing_px
+ p_inc = (p2 - p0).mul(1 / count)
+ count += 2
+
+ rows = []
+ steps = 0
+ while (steps < count):
+ try:
+ steps += 1
+ p0 += p_inc
+ p1 += p_inc
+ endpoints = [p0.as_tuple(), p1.as_tuple()]
+ shline = shgeo.LineString(endpoints)
+ res = shline.intersection(shpath)
+ if (isinstance(res, shgeo.MultiLineString)):
+ runs = map(shapelyLineSegmentToPyTuple, res.geoms)
+ else:
+ runs = [shapelyLineSegmentToPyTuple(res)]
+ if self.hatching and len(rows) > 0:
+ rows.append([(rows[-1][0][1], runs[0][0])])
+ rows.append(runs)
+ except Exception, ex:
+ dbg.write("--------------\n")
+ dbg.write("%s\n" % ex)
+ dbg.write("%s\n" % shline)
+ dbg.write("%s\n" % shpath)
+ dbg.write("==============\n")
+ continue
+ return rows
+
+ def visit_segments_one_by_one(self, rows):
+ def pull_runs(rows):
+ new_rows = []
+ run = []
+ for r in rows:
+ (first,rest) = (r[0], r[1:])
+ run.append(first)
+ if (len(rest)>0):
+ new_rows.append(rest)
+ return (run, new_rows)
+
+ linearized_runs = []
+ count = 0
+ while (len(rows) > 0):
+ (one_run,rows) = pull_runs(rows)
+ linearized_runs.extend(one_run)
+
+ rows = rows[::-1]
+ count += 1
+ if (count>100): raise "kablooey"
+ return linearized_runs
+
+ def handle_node(self, node):
+ if (node.tag == inkex.addNS('g', 'svg')):
+ #dbg.write("%s\n"%str((id, etree.tostring(node, pretty_print=True))))
+ #dbg.write("not a path; recursing:\n")
+ for child in node.iter(self.svgpath):
+ self.handle_node(child)
+ return
+
+ #dbg.write("Node: %s\n"%str((id, etree.tostring(node, pretty_print=True))))
+
+ israw = parse_boolean(node.get('embroider_raw'))
+ if (israw):
+ self.patchList.patches.extend(self.path_to_patch_list(node))
+ else:
+ if (self.get_style(node, "fill")!=None):
+ self.patchList.patches.extend(self.filled_region_to_patchlist(node))
+ if (self.get_style(node, "stroke")!=None):
+ self.patchList.patches.extend(self.path_to_patch_list(node))
+
+ def get_style(self, node, style_name):
+ style = simplestyle.parseStyle(node.get("style"))
+ if (style_name not in style):
+ return None
+ value = style[style_name]
+ if (value==None or value=="none"):
+ return None
+ return value
+
+ def cache_layers(self):
+ self.layer_cache = {}
+
+ layer_tag = inkex.addNS("g", "svg")
+ group_attr = inkex.addNS('groupmode', 'inkscape')
+
+
+ def is_layer(node):
+ return node.tag == layer_tag and node.get(group_attr) == "layer"
+
+ def process(node, layer=0):
+ if is_layer(node):
+ layer += 1
+ else:
+ self.layer_cache[node.get("id")] = layer
+
+ for child in node:
+ layer = process(child, layer)
+
+ return layer
+
+ process(self.document.getroot())
+
+ def effect(self):
+ if self.options.preserve_layers == "true":
+ self.cache_layers()
+ #print >> sys.stderr, "cached stacking order:", self.stacking_order
+
+ self.row_spacing_px = self.options.row_spacing_mm * pixels_per_millimeter
+ self.zigzag_spacing_px = self.options.zigzag_spacing_mm * pixels_per_millimeter
+ self.max_stitch_len_px = self.options.max_stitch_len_mm*pixels_per_millimeter
+ self.running_stitch_len_px = self.options.running_stitch_len_mm*pixels_per_millimeter
+ self.collapse_len_px = self.options.collapse_len_mm*pixels_per_millimeter
+ self.hatching = self.options.hatch_filled_paths == "true"
+
+ self.svgpath = inkex.addNS('path', 'svg')
+ self.patchList = PatchList([])
+ for node in self.selected.itervalues():
+ self.handle_node(node)
+
+ if not self.patchList:
+ inkex.errormsg("No paths selected.")
+ return
+
+ self.patchList = self.patchList.tsp_by_color()
+ #dbg.write("patch count: %d\n" % len(self.patchList.patches))
+
+ if self.options.hide_layers:
+ self.hide_layers()
+
+ eo = EmbroideryObject(self.patchList, self.row_spacing_px)
+ emb = eo.emit_file(self.options.filename, self.options.output_format,
+ self.collapse_len_px, self.options.add_preamble)
+
+ new_layer = inkex.etree.SubElement(self.document.getroot(),
+ 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')
+ eo.emit_inkscape(new_layer, emb)
+
+ def emit_inkscape_bbox(self, parent, eo):
+ (x0, y0, x1, y1) = eo.bbox()
+ new_path = []
+ new_path.append(['M', (x0,y0)])
+ new_path.append(['L', (x1,y0)])
+ new_path.append(['L', (x1,y1)])
+ new_path.append(['L', (x0,y1)])
+ new_path.append(['L', (x0,y0)])
+ inkex.etree.SubElement(parent,
+ inkex.addNS('path', 'svg'),
+ { 'style':simplestyle.formatStyle(
+ { 'stroke': '#ff00ff',
+ 'stroke-width':str(1),
+ 'fill': 'none' }),
+ 'd':simplepath.formatPath(new_path),
+ })
+
+ def hide_layers(self):
+ for g in self.document.getroot().findall(inkex.addNS("g","svg")):
+ if g.get(inkex.addNS("groupmode", "inkscape")) == "layer":
+ g.set("style", "display:none")
+
+ def path_to_patch_list(self, node):
+ threadcolor = simplestyle.parseStyle(node.get("style"))["stroke"]
+ stroke_width_str = simplestyle.parseStyle(node.get("style"))["stroke-width"]
+ if (stroke_width_str.endswith("px")):
+ # don't really know how we should be doing unit conversions.
+ # but let's hope px are kind of like pts?
+ stroke_width_str = stroke_width_str[:-2]
+ stroke_width = float(stroke_width_str)
+ #dbg.write("stroke_width is <%s>\n" % repr(stroke_width))
+ #dbg.flush()
+
+ running_stitch_len_px = get_float_param(node, "stitch_length", self.running_stitch_len_px)
+ zigzag_spacing_px = get_float_param(node, "zigzag_spacing", self.zigzag_spacing_px)
+ repeats = get_int_param(node, "repeats", 1)
+
+ sortorder = self.get_sort_order(threadcolor, node)
+ paths = flatten(parse_path(node), self.options.flat)
+
+ # regularize the points lists.
+ # (If we're parsing beziers, there will be a list of multi-point
+ # subarrays.)
+
+ patches = []
+
+ for path in paths:
+ path = [PyEmb.Point(x, y) for x, y in path]
+ if (stroke_width <= STROKE_MIN):
+ #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, sortorder)
+ else:
+ patch = self.stroke_points(path, zigzag_spacing_px*0.5, stroke_width, repeats, threadcolor, sortorder)
+ patches.extend(patch)
+
+ return patches
+
+ def stroke_points(self, emb_point_list, zigzag_spacing_px, stroke_width, repeats, threadcolor, sortorder):
+ patch = Patch(color=threadcolor, sortorder=sortorder)
+ p0 = emb_point_list[0]
+ rho = 0.0
+ fact = 1
+
+ for repeat in xrange(repeats):
+ if repeat % 2 == 0:
+ order = range(1, len(emb_point_list))
+ else:
+ order = range(-2, -len(emb_point_list) - 1, -1)
+
+ for segi in order:
+ p1 = emb_point_list[segi]
+
+ # how far we have to go along segment
+ seg_len = (p1 - p0).length()
+ if (seg_len == 0):
+ continue
+
+ # vector pointing along segment
+ along = (p1 - p0).unit()
+ # vector pointing to edge of stroke width
+ perp = along.rotate_left().mul(stroke_width*0.5)
+
+ # 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)
+ rho += zigzag_spacing_px
+ fact = -fact
+
+ p0 = p1
+ rho -= seg_len
+
+ return [patch]
+
+ def filled_region_to_patchlist(self, node):
+ angle = math.radians(float(get_float_param(node,'angle',0)))
+ paths = flatten(parse_path(node), self.options.flat)
+ shapelyPolygon = cspToShapelyPolygon(paths)
+ threadcolor = simplestyle.parseStyle(node.get("style"))["fill"]
+ sortorder = self.get_sort_order(threadcolor, node)
+ return self.process_one_path(
+ node,
+ shapelyPolygon,
+ threadcolor,
+ sortorder,
+ angle)
+
+ #TODO def make_stroked_patch(self, node):
if __name__ == '__main__':
- sys.setrecursionlimit(100000);
- e = Embroider()
- e.affect()
- #dbg.write("aaaand, I'm done. seeeya!\n")
- dbg.flush()
+ sys.setrecursionlimit(100000);
+ e = Embroider()
+ e.affect()
+ #dbg.write("aaaand, I'm done. seeeya!\n")
+ dbg.flush()
dbg.close()