From 90e4cc2c8c401eb5ef4ffdc7c2931eed4f927d97 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 26 Oct 2016 15:04:15 -0400 Subject: [PATCH] big refactor to untangle embroidery from stitch generation --- PyEmb.py | 78 ++++++++++++++-------------- embroider.py | 144 +++++++++++++++++++++------------------------------ 2 files changed, 96 insertions(+), 126 deletions(-) diff --git a/PyEmb.py b/PyEmb.py index 44fcc7e46..18fd07fb0 100644 --- a/PyEmb.py +++ b/PyEmb.py @@ -4,6 +4,7 @@ import math import sys +from copy import deepcopy class Point: def __init__(self, x, y): @@ -58,20 +59,25 @@ class Point: def __cmp__(self, other): return cmp(self.as_tuple(), other.as_tuple()) -class Embroidery: - def __init__(self): - self.coords = [] +class Stitch(Point): + def __init__(self, x, y, color=None, jumpStitch=False): + Point.__init__(self, x, y) + self.color = color + self.jumpStitch = jumpStitch - def addStitch(self, coord): - if len(self.coords) == 0 or self.coords[-1] != coord: - self.coords.append(coord) +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.coords)==0): + if (len(self.stitches)==0): return - (maxx,maxy) = (self.coords[0].x,self.coords[0].y) - (minx,miny) = (self.coords[0].x,self.coords[0].y) - for p in self.coords: + (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) @@ -83,22 +89,22 @@ class Embroidery: return (minx, miny) def translate(self, dx, dy): - for p in self.coords: + 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.coords: + for p in self.stitches: p.x *= sc[0] p.y *= sc[1] - def export_ksm(self, dbg): + def export_ksm(self): str = "" self.pos = Point(0,0) lastColor = None - for stitch in self.coords: + for stitch in self.stitches: if (lastColor!=None and stitch.color!=lastColor): mode_byte = 0x99 #dbg.write("Color change!\n") @@ -121,13 +127,13 @@ class Embroidery: self.pos = stitch return str - def export_melco(self, dbg): + def export_melco(self): self.str = "" - self.pos = self.coords[0] - #dbg.write("stitch count: %d\n" % len(self.coords)) + self.pos = self.stitches[0] + #dbg.write("stitch count: %d\n" % len(self.stitches)) lastColor = None numColors = 0x0 - for stitch in self.coords[1:]: + for stitch in self.stitches[1:]: if (lastColor!=None and stitch.color!=lastColor): numColors += 1 # color change @@ -164,14 +170,14 @@ class Embroidery: self.pos = stitch return self.str - def export_csv(self, dbg): + 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.coords: + 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: @@ -188,10 +194,10 @@ class Embroidery: self.str += '"*","END","%f","%f"\n' % (lastStitch.x, lastStitch.y) return self.str - def export_gcode(self, dbg): + def export_gcode(self): ret = [] lastColor = None - for stitch in self.coords: + for stitch in self.stitches: if stitch.color != lastColor: ret.append('M0 ;MSG, Color change; prepare for %s\n' % stitch.color) lastColor = stitch.color @@ -199,24 +205,16 @@ class Embroidery: ret.append('M0 ;MSG, EMBROIDER stitch\n') return ''.join(ret) - def export_paths(self, dbg): - paths = [] - lastColor = None - lastStitch = None - for stitch in self.coords: - if stitch.jumpStitch: - if lastColor == stitch.color: - paths.append([None, []]) - if lastStitch is not None: - paths[-1][1].append(['M', lastStitch.as_tuple()]) - paths[-1][1].append(['L', stitch.as_tuple()]) - lastColor = None - if stitch.color != lastColor: - 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 - return paths + 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): diff --git a/embroider.py b/embroider.py index a472c837c..857d2be06 100644 --- a/embroider.py +++ b/embroider.py @@ -46,7 +46,7 @@ dbg = open("/tmp/embroider-debug.txt", "w") PyEmb.dbg = dbg #pixels_per_millimeter = 90.0 / 25.4 -#this actually makes each pixel worth one tenth of a millimeter +#this makes each pixel worth one tenth of a millimeter pixels_per_millimeter = 10 # a 0.5pt stroke becomes a straight line. @@ -189,80 +189,69 @@ class Patch: def reverse(self): return Patch(self.color, self.stitches[::-1]) -class EmbroideryObject: - def __init__(self, patch_list): - self.patch_list = patch_list +def patches_to_stitches(patch_list, collapse_len_px=0): + stitches = [] - def emit_file(self, filename, output_format, collapse_len_px): - emb = PyEmb.Embroidery() - lastStitch = None - lastColor = None - for patch in self.patch_list: - 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)) + lastStitch = None + lastColor = None + for patch in patch_list: + jumpStitch = True + for stitch in patch.stitches: + if lastStitch and lastColor == patch.color: + l = (stitch - lastStitch).length() + if l <= 0.1: + # filter out duplicate successive stitches + jumpStitch = False + continue - if c <= 0.1: - # filter out duplicate successive stitches + if jumpStitch: + # consider collapsing jump stitch, if it is pretty short + if l < collapse_len_px: + #dbg.write("... collapsed\n") 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) - #dbg.write("stitch color %s\n" % patch.color) + newStitch = PyEmb.Stitch(stitch.x, stitch.y, patch.color, jumpStitch) + stitches.append(newStitch) - newStitch = PyEmb.Point(stitch.x, -stitch.y) - newStitch.color = patch.color - newStitch.jumpStitch = jumpStitch - emb.addStitch(newStitch) + jumpStitch = False + lastStitch = stitch + lastColor = patch.color - jumpStitch = False - lastStitch = newStitch - lastColor = patch.color + return stitches - dx, dy = emb.translate_to_origin() - emb.scale(1.0/pixels_per_millimeter) +def stitches_to_paths(stitches): + paths = [] + lastColor = None + lastStitch = None + for stitch in stitches: + if stitch.jumpStitch: + if lastColor == stitch.color: + paths.append([None, []]) + if lastStitch is not None: + paths[-1][1].append(['M', lastStitch.as_tuple()]) + paths[-1][1].append(['L', stitch.as_tuple()]) + lastColor = None + if stitch.color != lastColor: + 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 + return paths - 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':"0.4", - 'fill': 'none' }), - 'd':simplepath.formatPath(path), - }) - - def bbox(self): - x = [] - y = [] - for patch in self.patch_list: - for stitch in patch.stitches: - x.append(stitch.x) - y.append(stitch.y) - return (min(x), min(y), max(x), max(y)) +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), + }) class Embroider(inkex.Effect): def __init__(self, *args, **kwargs): @@ -662,36 +651,19 @@ class Embroider(inkex.Effect): if self.options.hide_layers: self.hide_layers() - eo = EmbroideryObject(self.patch_list) - emb = eo.emit_file(self.get_output_path(), self.options.output_format, - self.collapse_len_px) + stitches = patches_to_stitches(self.patch_list, self.collapse_len_px) + emb = PyEmb.Embroidery(stitches, pixels_per_millimeter) + emb.export(self.get_output_path(), self.options.output_format) 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) + emit_inkscape(new_layer, stitches) sys.stdout = old_stdout - 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":