diff options
Diffstat (limited to 'embroider.py')
| -rw-r--r-- | embroider.py | 520 |
1 files changed, 79 insertions, 441 deletions
diff --git a/embroider.py b/embroider.py index 25884316..feda368f 100644 --- a/embroider.py +++ b/embroider.py @@ -34,319 +34,8 @@ import shapely.ops import networkx from pprint import pformat -import PyEmb -from PyEmb import cache - -dbg = open("/tmp/embroider-debug.txt", "w") -PyEmb.dbg = dbg - -SVG_PATH_TAG = inkex.addNS('path', 'svg') -SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg') -SVG_DEFS_TAG = inkex.addNS('defs', 'svg') -SVG_GROUP_TAG = inkex.addNS('g', 'svg') - -EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG) - -# modern versions of Inkscape use 96 pixels per inch as per the CSS standard -PIXELS_PER_MM = 96 / 25.4 - -class Param(object): - def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, default=None, tooltip=None, sort_index=0): - self.name = name - self.description = description - self.unit = unit - self.values = values or [""] - self.type = type - self.group = group - self.inverse = inverse - self.default = default - self.tooltip = tooltip - self.sort_index = sort_index - - def __repr__(self): - return "Param(%s)" % vars(self) - -# Decorate a member function or property with information about -# the embroidery parameter it corresponds to -def param(*args, **kwargs): - p = Param(*args, **kwargs) - - def decorator(func): - func.param = p - return func - - return decorator - -# cribbed from inkscape-silhouette -def parse_length_with_units( str ): - - ''' - Parse an SVG value which may or may not have units attached - This version is greatly simplified in that it only allows: no units, - units of px, mm, and %. Everything else, it returns None for. - There is a more general routine to consider in scour.py if more - generality is ever needed. - ''' - - u = 'px' - s = str.strip() - if s[-2:] == 'px': - s = s[:-2] - elif s[-2:] == 'mm': - u = 'mm' - s = s[:-2] - elif s[-2:] == 'pt': - u = 'pt' - s = s[:-2] - elif s[-2:] == 'pc': - u = 'pc' - s = s[:-2] - elif s[-2:] == 'cm': - u = 'cm' - s = s[:-2] - elif s[-2:] == 'in': - u = 'in' - s = s[:-2] - elif s[-1:] == '%': - u = '%' - s = s[:-1] - try: - v = float( s ) - except: - raise ValueError("parseLengthWithUnits: unknown unit %s" % s) - - return v, u - - -def convert_length(length): - value, units = parse_length_with_units(length) - - if not units or units == "px": - return value - - if units == 'cm': - value *= 10 - units == 'mm' - - if units == 'mm': - value = value / 25.4 - units = 'in' - - if units == 'in': - # modern versions of Inkscape use CSS's 96 pixels per inch. When you - # open an old document, inkscape will add a viewbox for you. - return value * 96 - - raise ValueError("Unknown unit: %s" % units) - -@cache -def get_viewbox_transform(node): - # somewhat cribbed from inkscape-silhouette - - doc_width = convert_length(node.get('width')) - doc_height = convert_length(node.get('height')) - - viewbox = node.get('viewBox').strip().replace(',', ' ').split() - - dx = -float(viewbox[0]) - dy = -float(viewbox[1]) - transform = simpletransform.parseTransform("translate(%f, %f)" % (dx, dy)) - - try: - sx = doc_width / float(viewbox[2]) - sy = doc_height / float(viewbox[3]) - scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy)) - transform = simpletransform.composeTransform(transform, scale_transform) - except ZeroDivisionError: - pass - - return transform - -class EmbroideryElement(object): - def __init__(self, node, options=None): - self.node = node - self.options = options - - @property - def id(self): - return self.node.get('id') - - @classmethod - def get_params(cls): - params = [] - for attr in dir(cls): - prop = getattr(cls, attr) - if isinstance(prop, property): - # The 'param' attribute is set by the 'param' decorator defined above. - if hasattr(prop.fget, 'param'): - params.append(prop.fget.param) - - return params - - @cache - def get_param(self, param, default): - value = self.node.get("embroider_" + param, "").strip() - - if not value: - value = getattr(self.options, param, default) - - return value - - @cache - def get_boolean_param(self, param, default=None): - value = self.get_param(param, default) - - if isinstance(value, bool): - return value - else: - return value and (value.lower() in ('yes', 'y', 'true', 't', '1')) - - @cache - def get_float_param(self, param, default=None): - try: - value = float(self.get_param(param, default)) - except (TypeError, ValueError): - return default - - if param.endswith('_mm'): - value = value * PIXELS_PER_MM - - return value - - @cache - def get_int_param(self, param, default=None): - try: - value = int(self.get_param(param, default)) - except (TypeError, ValueError): - return default - - if param.endswith('_mm'): - value = int(value * PIXELS_PER_MM) - - return value - - def set_param(self, name, value): - self.node.set("embroider_%s" % name, str(value)) - - @cache - def get_style(self, style_name): - style = simplestyle.parseStyle(self.node.get("style")) - if (style_name not in style): - return None - value = style[style_name] - if value == 'none': - return None - return value - - @cache - def has_style(self, style_name): - style = simplestyle.parseStyle(self.node.get("style")) - return style_name in style - - @property - def path(self): - return cubicsuperpath.parsePath(self.node.get("d")) - - - @cache - def parse_path(self): - # A CSP is a "cubic superpath". - # - # A "path" is a sequence of strung-together bezier curves. - # - # A "superpath" is a collection of paths that are all in one object. - # - # The "cubic" bit in "cubic superpath" is because the bezier curves - # inkscape uses involve cubic polynomials. - # - # Each path is a collection of tuples, each of the form: - # - # (control_before, point, control_after) - # - # A bezier curve segment is defined by an endpoint, a control point, - # a second control point, and a final endpoint. A path is a bunch of - # bezier curves strung together. One could represent a path as a set - # of four-tuples, but there would be redundancy because the ending - # point of one bezier is the starting point of the next. Instead, a - # path is a set of 3-tuples as shown above, and one must construct - # each bezier curve by taking the appropriate endpoints and control - # points. Bleh. It should be noted that a straight segment is - # represented by having the control point on each end equal to that - # end's point. - # - # In a path, each element in the 3-tuple is itself a tuple of (x, y). - # Tuples all the way down. Hasn't anyone heard of using classes? - - path = self.path - - # start with the identity transform - transform = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] - - # combine this node's transform with all parent groups' transforms - transform = simpletransform.composeParents(self.node, transform) - - # add in the transform implied by the viewBox - viewbox_transform = get_viewbox_transform(self.node.getroottree().getroot()) - transform = simpletransform.composeTransform(viewbox_transform, transform) - - # apply the combined transform to this node's path - simpletransform.applyTransformToPath(transform, path) - - - return path - - def flatten(self, path): - """approximate a path containing beziers with a series of points""" - - path = deepcopy(path) - - cspsubdiv(path, getattr(self.options, "flat", 0.1)) - - flattened = [] - - for comp in path: - vertices = [] - for ctl in comp: - vertices.append((ctl[1][0], ctl[1][1])) - flattened.append(vertices) - - return flattened - - @property - @param('trim_after', - 'TRIM after', - tooltip='Trim thread after this object (for supported machines and file formats)', - type='boolean', - default=False, - sort_index=1000) - def trim_after(self): - return self.get_boolean_param('trim_after', False) - - @property - @param('stop_after', - 'STOP after', - tooltip='Add STOP instruction after this object (for supported machines and file formats)', - type='boolean', - default=False, - sort_index=1000) - def stop_after(self): - return self.get_boolean_param('stop_after', False) - - def to_patches(self, last_patch): - raise NotImplementedError("%s must implement to_patches()" % self.__class__.__name__) - - def embroider(self, last_patch): - patches = self.to_patches(last_patch) - - if patches: - patches[-1].trim_after = self.trim_after - patches[-1].stop_after = self.stop_after - - return patches - - def fatal(self, message): - print >> sys.stderr, "error:", message - sys.exit(1) +import inkstitch +from inkstitch import cache, dbg, param, EmbroideryElement, get_nodes, SVG_POLYLINE_TAG, SVG_GROUP_TAG, PIXELS_PER_MM, get_viewbox_transform class Fill(EmbroideryElement): @@ -359,7 +48,7 @@ class Fill(EmbroideryElement): return self.get_boolean_param('auto_fill', True) @property - @param('angle', 'Angle of lines of stitches', unit='deg', type='float') + @param('angle', 'Angle of lines of stitches', unit='deg', type='float', default=0) @cache def angle(self): return math.radians(self.get_float_param('angle', 0)) @@ -369,26 +58,26 @@ class Fill(EmbroideryElement): return self.get_style("fill") @property - @param('flip', 'Flip fill (start right-to-left)', type='boolean') + @param('flip', 'Flip fill (start right-to-left)', type='boolean', default=False) def flip(self): return self.get_boolean_param("flip", False) @property - @param('row_spacing_mm', 'Spacing between rows', unit='mm', type='float') + @param('row_spacing_mm', 'Spacing between rows', unit='mm', type='float', default=0.25) def row_spacing(self): - return self.get_float_param("row_spacing_mm") + return self.get_float_param("row_spacing_mm", 0.25) @property def end_row_spacing(self): return self.get_float_param("end_row_spacing_mm") @property - @param('max_stitch_length_mm', 'Maximum fill stitch length', unit='mm', type='float') + @param('max_stitch_length_mm', 'Maximum fill stitch length', unit='mm', type='float', default=3.0) def max_stitch_length(self): - return self.get_float_param("max_stitch_length_mm") + return self.get_float_param("max_stitch_length_mm", 3.0) @property - @param('staggers', 'Stagger rows this many times before repeating', type='int') + @param('staggers', 'Stagger rows this many times before repeating', type='int', default=4) def staggers(self): return self.get_int_param("staggers", 4) @@ -431,7 +120,7 @@ class Fill(EmbroideryElement): @cache def east(self, angle): # "east" is the name of the direction that is to the right along a row - return PyEmb.Point(1, 0).rotate(-angle) + return inkstitch.Point(1, 0).rotate(-angle) @cache def north(self, angle): @@ -460,15 +149,15 @@ class Fill(EmbroideryElement): # the max line length I'll need to intersect the whole shape is the diagonal (minx, miny, maxx, maxy) = self.shape.bounds - upper_left = PyEmb.Point(minx, miny) - lower_right = PyEmb.Point(maxx, maxy) + upper_left = inkstitch.Point(minx, miny) + lower_right = inkstitch.Point(maxx, maxy) length = (upper_left - lower_right).length() half_length = length / 2.0 # Now get a unit vector rotated to the requested angle. I use -angle # because shapely rotates clockwise, but my geometry textbooks taught # me to consider angles as counter-clockwise from the X axis. - direction = PyEmb.Point(1, 0).rotate(-angle) + direction = inkstitch.Point(1, 0).rotate(-angle) # and get a normal vector normal = direction.rotate(math.pi / 2) @@ -476,7 +165,7 @@ class Fill(EmbroideryElement): # 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 # a line segment in the grating. - center = PyEmb.Point((minx + maxx) / 2.0, (miny + maxy) / 2.0) + center = inkstitch.Point((minx + maxx) / 2.0, (miny + maxy) / 2.0) # I need to figure out how far I need to go along the normal to get to # the edge of the shape. To do that, I'll rotate the bounding box @@ -520,7 +209,7 @@ class Fill(EmbroideryElement): runs = [res.coords] if runs: - runs.sort(key=lambda seg: (PyEmb.Point(*seg[0]) - upper_left).length()) + runs.sort(key=lambda seg: (inkstitch.Point(*seg[0]) - upper_left).length()) if self.flip: runs.reverse() @@ -614,8 +303,8 @@ class Fill(EmbroideryElement): # abutting fill regions from pull_runs(). - beg = PyEmb.Point(*beg) - end = PyEmb.Point(*end) + beg = inkstitch.Point(*beg) + end = inkstitch.Point(*end) row_direction = (end - beg).unit() segment_length = (end - beg).length() @@ -701,14 +390,14 @@ class AutoFill(Fill): return False @property - @param('running_stitch_length_mm', 'Running stitch length (traversal between sections)', unit='mm', type='float') + @param('running_stitch_length_mm', 'Running stitch length (traversal between sections)', unit='mm', type='float', default=1.5) def running_stitch_length(self): - return self.get_float_param("running_stitch_length_mm") + return self.get_float_param("running_stitch_length_mm", 1.5) @property - @param('fill_underlay', 'Underlay', type='toggle', group='AutoFill Underlay') + @param('fill_underlay', 'Underlay', type='toggle', group='AutoFill Underlay', default=False) def fill_underlay(self): - return self.get_boolean_param("fill_underlay") + return self.get_boolean_param("fill_underlay", default=False) @property @param('fill_underlay_angle', 'Fill angle (default: fill angle + 90 deg)', unit='deg', group='AutoFill Underlay', type='float') @@ -816,8 +505,8 @@ class AutoFill(Fill): # If the start and endpoints are in the same row, I can't tell which row # I should treat it as being in. for i in xrange(len(nodes)): - row0 = self.row_num(PyEmb.Point(*nodes[0]), angle, row_spacing) - row1 = self.row_num(PyEmb.Point(*nodes[1]), angle, row_spacing) + row0 = self.row_num(inkstitch.Point(*nodes[0]), angle, row_spacing) + row1 = self.row_num(inkstitch.Point(*nodes[1]), angle, row_spacing) if row0 == row1: nodes = nodes[1:] + [nodes[0]] @@ -832,7 +521,7 @@ class AutoFill(Fill): else: edge_set = 1 - #print >> sys.stderr, outline_index, "es", edge_set, "rn", row_num, PyEmb.Point(*nodes[0]) * self.north(angle), PyEmb.Point(*nodes[1]) * self.north(angle) + #print >> sys.stderr, outline_index, "es", edge_set, "rn", row_num, inkstitch.Point(*nodes[0]) * self.north(angle), inkstitch.Point(*nodes[1]) * self.north(angle) # add an edge between each successive node for i, (node1, node2) in enumerate(zip(nodes, nodes[1:] + [nodes[0]])): @@ -840,8 +529,8 @@ class AutoFill(Fill): # duplicate edges contained in every other row (exactly half # will be duplicated) - row_num = min(self.row_num(PyEmb.Point(*node1), angle, row_spacing), - self.row_num(PyEmb.Point(*node2), angle, row_spacing)) + row_num = min(self.row_num(inkstitch.Point(*node1), angle, row_spacing), + self.row_num(inkstitch.Point(*node2), angle, row_spacing)) # duplicate every other edge around this outline if i % 2 == edge_set: @@ -1114,14 +803,14 @@ class AutoFill(Fill): print >> dbg, "connect_points:", outline_index, start, end, distance, stitches, direction dbg.flush() - patch.add_stitch(PyEmb.Point(*start)) + patch.add_stitch(inkstitch.Point(*start)) for i in xrange(stitches): pos = (pos + one_stitch) % self.outline_length - patch.add_stitch(PyEmb.Point(*outline.interpolate(pos).coords[0])) + patch.add_stitch(inkstitch.Point(*outline.interpolate(pos).coords[0])) - end = PyEmb.Point(*end) + end = inkstitch.Point(*end) if (end - patch.stitches[-1]).length() > 0.1 * PIXELS_PER_MM: patch.add_stitch(end) @@ -1132,10 +821,10 @@ class AutoFill(Fill): path = self.collapse_sequential_outline_edges(graph, path) patch = Patch(color=self.color) - #patch.add_stitch(PyEmb.Point(*path[0][0])) + #patch.add_stitch(inkstitch.Point(*path[0][0])) #for edge in path: - # patch.add_stitch(PyEmb.Point(*edge[1])) + # patch.add_stitch(inkstitch.Point(*edge[1])) for edge in path: if graph.has_edge(*edge, key="segment"): @@ -1177,7 +866,7 @@ class AutoFill(Fill): starting_point = None else: nearest_point = self.outline.interpolate(self.outline.project(shgeo.Point(last_patch.stitches[-1]))) - starting_point = PyEmb.Point(*nearest_point.coords[0]) + starting_point = inkstitch.Point(*nearest_point.coords[0]) if self.fill_underlay: patches.extend(self.do_auto_fill(self.fill_underlay_angle, self.fill_underlay_row_spacing, self.fill_underlay_max_stitch_length, starting_point)) @@ -1216,15 +905,15 @@ class Stroke(EmbroideryElement): return self.get_style("stroke-dasharray") is not None @property - @param('running_stitch_length_mm', 'Running stitch length', unit='mm', type='float') + @param('running_stitch_length_mm', 'Running stitch length', unit='mm', type='float', default=1.5) def running_stitch_length(self): - return self.get_float_param("running_stitch_length_mm") + return self.get_float_param("running_stitch_length_mm", 1.5) @property - @param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float') + @param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float', default=0.4) @cache def zigzag_spacing(self): - return self.get_float_param("zigzag_spacing_mm") + return self.get_float_param("zigzag_spacing_mm", 0.4) @property @param('repeats', 'Repeats', type='int', default="1") @@ -1292,7 +981,7 @@ class Stroke(EmbroideryElement): patches = [] for path in self.paths: - path = [PyEmb.Point(x, y) for x, y in path] + path = [inkstitch.Point(x, y) for x, y in path] if self.is_running_stitch(): patch = self.stroke_points(path, self.running_stitch_length, stroke_width=0.0) else: @@ -1317,10 +1006,10 @@ class SatinColumn(EmbroideryElement): return self.get_style("stroke") @property - @param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float') + @param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float', default=0.4) def zigzag_spacing(self): # peak-to-peak distance between zigzags - return self.get_float_param("zigzag_spacing_mm") + return self.get_float_param("zigzag_spacing_mm", 0.4) @property @param('pull_compensation_mm', 'Pull compensation', unit='mm', type='float') @@ -1338,13 +1027,12 @@ class SatinColumn(EmbroideryElement): return self.get_boolean_param("contour_underlay") @property - @param('contour_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Contour Underlay', type='float') + @param('contour_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Contour Underlay', type='float', default=1.5) def contour_underlay_stitch_length(self): - # use "contour_underlay_stitch_length", or, if not set, default to "stitch_length" - return self.get_float_param("contour_underlay_stitch_length_mm") or self.get_float_param("running_stitch_length_mm") + return self.get_float_param("contour_underlay_stitch_length_mm", 1.5) @property - @param('contour_underlay_inset_mm', 'Contour underlay inset amount', unit='mm', group='Contour Underlay', type='float') + @param('contour_underlay_inset_mm', 'Contour underlay inset amount', unit='mm', group='Contour Underlay', type='float', default=0.4) def contour_underlay_inset(self): # how far inside the edge of the column to stitch the underlay return self.get_float_param("contour_underlay_inset_mm", 0.4) @@ -1357,10 +1045,9 @@ class SatinColumn(EmbroideryElement): return self.get_boolean_param("center_walk_underlay") @property - @param('center_walk_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Center-Walk Underlay', type='float') + @param('center_walk_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Center-Walk Underlay', type='float', default=1.5) def center_walk_underlay_stitch_length(self): - # use "center_walk_underlay_stitch_length", or, if not set, default to "stitch_length" - return self.get_float_param("center_walk_underlay_stitch_length_mm") or self.get_float_param("running_stitch_length_mm") + return self.get_float_param("center_walk_underlay_stitch_length_mm", 1.5) @property @param('zigzag_underlay', 'Zig-zag underlay', type='toggle', group='Zig-zag Underlay') @@ -1368,10 +1055,9 @@ class SatinColumn(EmbroideryElement): return self.get_boolean_param("zigzag_underlay") @property - @param('zigzag_underlay_spacing_mm', 'Zig-Zag spacing (peak-to-peak)', unit='mm', group='Zig-zag Underlay', type='float') + @param('zigzag_underlay_spacing_mm', 'Zig-Zag spacing (peak-to-peak)', unit='mm', group='Zig-zag Underlay', type='float', default=3) def zigzag_underlay_spacing(self): - # peak-to-peak distance between zigzags in zigzag underlay - return self.get_float_param("zigzag_underlay_spacing_mm", 1) + return self.get_float_param("zigzag_underlay_spacing_mm", 3) @property @param('zigzag_underlay_inset_mm', 'Inset amount (default: half of contour underlay inset)', unit='mm', group='Zig-zag Underlay', type='float') @@ -1429,7 +1115,7 @@ class SatinColumn(EmbroideryElement): print >> dbg, [str(rail) for rail in rails], [str(rung) for rung in rungs] self.fatal("Expected %d linestrings, got %d" % (len(rungs.geoms) + 1, len(linestrings.geoms))) - paths = [[PyEmb.Point(*coord) for coord in ls.coords] for ls in linestrings.geoms] + paths = [[inkstitch.Point(*coord) for coord in ls.coords] for ls in linestrings.geoms] result.append(paths) return zip(*result) @@ -1453,7 +1139,7 @@ class SatinColumn(EmbroideryElement): # iterate over pairs of 3-tuples for prev, current in zip(path[:-1], path[1:]): flattened_segment = self.flatten([[prev, current]]) - flattened_segment = [PyEmb.Point(x, y) for x, y in flattened_segment[0]] + flattened_segment = [inkstitch.Point(x, y) for x, y in flattened_segment[0]] flattened_path.append(flattened_segment) paths.append(flattened_path) @@ -1762,7 +1448,7 @@ class Polyline(EmbroideryElement): patch = Patch(color=self.color) for stitch in self.stitches: - patch.add_stitch(PyEmb.Point(*stitch)) + patch.add_stitch(inkstitch.Point(*stitch)) return [patch] @@ -1792,25 +1478,6 @@ def detect_classes(node): return classes -def descendants(node): - - nodes = [] - element = EmbroideryElement(node) - - if element.has_style('display') and element.get_style('display') is None: - return [] - - if node.tag == SVG_DEFS_TAG: - return [] - - for child in node: - nodes.extend(descendants(child)) - - if node.tag in EMBROIDERABLE_TAGS: - nodes.append(node) - - return nodes - class Patch: def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False): self.color = color @@ -1872,7 +1539,7 @@ def process_trim(stitches, next_stitch): for i in xrange(3): pos += delta - stitches.append(PyEmb.Stitch(pos.x, pos.y, stitches[-1].color, jump=True)) + stitches.append(inkstitch.Stitch(pos.x, pos.y, stitches[-1].color, jump=True)) # first one should be TRIM instead of JUMP stitches[-3].jump = False @@ -1906,16 +1573,16 @@ def patches_to_stitches(patch_list, collapse_len_px=0): if stitches and last_color and last_color != patch.color: # add a color change - stitches.append(PyEmb.Stitch(last_stitch.x, last_stitch.y, last_color, stop=True)) + stitches.append(inkstitch.Stitch(last_stitch.x, last_stitch.y, last_color, stop=True)) if need_trim: process_trim(stitches, stitch) need_trim = False if jump_stitch: - stitches.append(PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump=True)) + stitches.append(inkstitch.Stitch(stitch.x, stitch.y, patch.color, jump=True)) - stitches.append(PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump=False)) + stitches.append(inkstitch.Stitch(stitch.x, stitch.y, patch.color, jump=False)) jump_stitch = False last_stitch = stitch @@ -1971,35 +1638,37 @@ def emit_inkscape(parent, stitches): 'transform': transform }) +def get_elements(effect): + elements = [] + nodes = get_nodes(effect) + + for node in nodes: + classes = detect_classes(node) + elements.extend(cls(node) for cls in classes) + + return elements + + +def elements_to_patches(elements): + patches = [] + for element in elements: + if patches: + last_patch = patches[-1] + else: + last_patch = None + + patches.extend(element.embroider(last_patch)) + + return patches class Embroider(inkex.Effect): def __init__(self, *args, **kwargs): 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)") - 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_length_mm", default=3.0, - help="max stitch length (mm)") - self.OptionParser.add_option("--running_stitch_len_mm", - action="store", type="float", - dest="running_stitch_length_mm", default=3.0, - help="running stitch length (mm)") self.OptionParser.add_option("-c", "--collapse_len_mm", action="store", type="float", dest="collapse_length_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("--hide_layers", action="store", type="choice", choices=["true", "false"], @@ -2021,15 +1690,7 @@ class Embroider(inkex.Effect): action="store", type="int", dest="max_backups", default=5, help="Max number of backups of output files to keep.") - self.patches = [] - - def handle_node(self, node): - print >> dbg, "handling node", node.get('id'), node.tag - nodes = descendants(node) - for node in nodes: - classes = detect_classes(node) - print >> dbg, "classes:", classes - self.elements.extend(cls(node, self.options) for cls in classes) + self.OptionParser.usage += "\n\nSeeing a 'no such option' message? Please restart Inkscape to fix." def get_output_path(self): if self.options.output_file: @@ -2073,22 +1734,7 @@ class Embroider(inkex.Effect): self.patch_list = [] - print >> dbg, "starting nodes: %s\n" % time.time() - dbg.flush() - - self.elements = [] - - if self.selected: - # be sure to visit selected nodes in the order they're stacked in - # the document - for node in self.document.getroot().iter(): - if node.get("id") in self.selected: - self.handle_node(node) - else: - self.handle_node(self.document.getroot()) - - print >> dbg, "finished nodes: %s" % time.time() - dbg.flush() + self.elements = get_elements(self) if not self.elements: if self.selected: @@ -2101,17 +1747,9 @@ class Embroider(inkex.Effect): if self.options.hide_layers: self.hide_layers() - patches = [] - for element in self.elements: - if patches: - last_patch = patches[-1] - else: - last_patch = None - - patches.extend(element.embroider(last_patch)) - + patches = elements_to_patches(self.elements) stitches = patches_to_stitches(patches, self.options.collapse_length_mm * PIXELS_PER_MM) - PyEmb.write_embroidery_file(self.get_output_path(), stitches) + inkstitch.write_embroidery_file(self.get_output_path(), stitches) new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {}) new_layer.set('id', self.uniqueId("embroidery")) |
