diff --git a/inkstitch/__init__.py b/inkstitch/__init__.py index 2e7a55f6d..45eed3a66 100644 --- a/inkstitch/__init__.py +++ b/inkstitch/__init__.py @@ -161,16 +161,17 @@ def get_stroke_scale(node): class Stitch(Point): - def __init__(self, x, y, color=None, jump=False, stop=False, trim=False): + def __init__(self, x, y, color=None, jump=False, stop=False, trim=False, no_ties=False): self.x = x self.y = y self.color = color self.jump = jump self.trim = trim self.stop = stop + self.no_ties = no_ties def __repr__(self): - return "Stitch(%s, %s, %s, %s, %s, %s)" % (self.x, self.y, self.color, "JUMP" if self.jump else "", "TRIM" if self.trim else "", "STOP" if self.stop else "") + return "Stitch(%s, %s, %s, %s, %s, %s, %s)" % (self.x, self.y, self.color, "JUMP" if self.jump else " ", "TRIM" if self.trim else " ", "STOP" if self.stop else " ", "NO TIES" if self.no_ties else " ") def make_thread(color): diff --git a/inkstitch/elements/element.py b/inkstitch/elements/element.py index 7a029eacd..cfca37822 100644 --- a/inkstitch/elements/element.py +++ b/inkstitch/elements/element.py @@ -14,11 +14,12 @@ from cspsubdiv import cspsubdiv class Patch: """A raw collection of stitches with attached instructions.""" - def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False): + def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, stitch_as_is=False): self.color = color self.stitches = stitches or [] self.trim_after = trim_after self.stop_after = stop_after + self.stitch_as_is = stitch_as_is def __add__(self, other): if isinstance(other, Patch): @@ -203,25 +204,18 @@ class EmbroideryElement(object): # apply the combined transform to this node's path simpletransform.applyTransformToPath(transform, path) - return path + def strip_control_points(self, subpath): + return [point for control_before, point, control_after in subpath] + def flatten(self, path): """approximate a path containing beziers with a series of points""" path = deepcopy(path) - cspsubdiv(path, 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 + return [self.strip_control_points(subpath) for subpath in path] @property @param('trim_after', diff --git a/inkstitch/elements/stroke.py b/inkstitch/elements/stroke.py index b0e7d23fb..0ce3fa67b 100644 --- a/inkstitch/elements/stroke.py +++ b/inkstitch/elements/stroke.py @@ -37,7 +37,17 @@ class Stroke(EmbroideryElement): @property def paths(self): - return self.flatten(self.parse_path()) + path = self.parse_path() + + if self.manual_stitch_mode: + return [self.strip_control_points(subpath) for subpath in path] + else: + return self.flatten(path) + + @property + @param('manual_stitch', _('Manual stitch placement'), tooltip=_("Stitch every node in the path. Stitch length and zig-zag spacing are ignored."), type='boolean', default=False) + def manual_stitch_mode(self): + return self.get_boolean_param('manual_stitch') def is_running_stitch(self): # stroke width <= 0.5 pixels is deprecated in favor of dashed lines @@ -99,7 +109,9 @@ class Stroke(EmbroideryElement): for path in self.paths: path = [Point(x, y) for x, y in path] - if self.is_running_stitch(): + if self.manual_stitch_mode: + patch = Patch(color=self.color, stitches=path, stitch_as_is=True) + elif self.is_running_stitch(): patch = self.stroke_points(path, self.running_stitch_length, stroke_width=0.0) else: patch = self.stroke_points(path, self.zigzag_spacing / 2.0, stroke_width=self.stroke_width) diff --git a/inkstitch/stitch_plan/stitch_plan.py b/inkstitch/stitch_plan/stitch_plan.py index 3a7b8f181..82584bbc5 100644 --- a/inkstitch/stitch_plan/stitch_plan.py +++ b/inkstitch/stitch_plan/stitch_plan.py @@ -36,6 +36,7 @@ def patches_to_stitch_plan(patches, collapse_len=3.0 * PIXELS_PER_MM): if color_block.last_stitch: if (patch.stitches[0] - color_block.last_stitch).length() > collapse_len: color_block.add_stitch(patch.stitches[0].x, patch.stitches[0].y, jump=True) + else: # add a color change color_block.add_stitch(color_block.last_stitch.x, color_block.last_stitch.y, stop=True) @@ -43,7 +44,7 @@ def patches_to_stitch_plan(patches, collapse_len=3.0 * PIXELS_PER_MM): color_block.color = patch.color color_block.filter_duplicate_stitches() - color_block.add_stitches(patch.stitches) + color_block.add_stitches(patch.stitches, no_ties=patch.stitch_as_is) if patch.trim_after: # a trim needs to be followed by a jump to the next stitch, so @@ -75,6 +76,9 @@ class StitchPlan(object): def __len__(self): return len(self.color_blocks) + def __repr__(self): + return "StitchPlan(%s)" % ", ".join(repr(cb) for cb in self.color_blocks) + @property def num_colors(self): """Number of unique colors in the stitch plan.""" @@ -109,6 +113,9 @@ class ColorBlock(object): def __iter__(self): return iter(self.stitches) + def __repr__(self): + return "ColorBlock(%s, %s)" % (self.color, self.stitches) + def has_color(self): return self._color is not None @@ -159,10 +166,14 @@ class ColorBlock(object): stitches = [self.stitches[0]] for stitch in self.stitches[1:]: - l = (stitch - stitches[-1]).length() - if l <= 0.1: - # duplicate stitch, skip this one - continue + if stitches[-1].jump or stitch.stop or stitch.trim: + # Don't consider jumps, stops, or trims as candidates for filtering + pass + else: + l = (stitch - stitches[-1]).length() + if l <= 0.1: + # duplicate stitch, skip this one + continue stitches.append(stitch) @@ -172,16 +183,16 @@ class ColorBlock(object): if isinstance(args[0], Stitch): self.stitches.append(args[0]) elif isinstance(args[0], Point): - self.stitches.append(Stitch(args[0].x, args[0].y)) + self.stitches.append(Stitch(args[0].x, args[0].y, *args[1:], **kwargs)) else: self.stitches.append(Stitch(*args, **kwargs)) - def add_stitches(self, stitches): + def add_stitches(self, stitches, *args, **kwargs): for stitch in stitches: if isinstance(stitch, (Stitch, Point)): - self.add_stitch(stitch) + self.add_stitch(stitch, *args, **kwargs) else: - self.add_stitch(*stitch) + self.add_stitch(*(list(stitch) + args), **kwargs) def replace_stitches(self, stitches): self.stitches = stitches diff --git a/inkstitch/stitch_plan/ties.py b/inkstitch/stitch_plan/ties.py index 9c688e6ba..1207ea517 100644 --- a/inkstitch/stitch_plan/ties.py +++ b/inkstitch/stitch_plan/ties.py @@ -4,6 +4,11 @@ from .. import Stitch from copy import deepcopy def add_tie(stitches, tie_path): + if stitches[-1].no_ties: + # It's from a manual stitch block, so don't add tie stitches. The user + # will add them if they want them. + return + tie_path = cut_path(tie_path, 0.6) tie_stitches = running_stitch(tie_path, 0.3) tie_stitches = [Stitch(stitch.x, stitch.y) for stitch in tie_stitches] diff --git a/inkstitch/svg.py b/inkstitch/svg.py index d9258f196..66806d7a3 100644 --- a/inkstitch/svg.py +++ b/inkstitch/svg.py @@ -1,5 +1,5 @@ import simpletransform, simplestyle, inkex -from . import _, get_viewbox_transform, cache, SVG_GROUP_TAG, INKSCAPE_LABEL, INKSCAPE_GROUPMODE, SVG_POLYLINE_TAG +from . import _, get_viewbox_transform, cache, SVG_GROUP_TAG, INKSCAPE_LABEL, INKSCAPE_GROUPMODE, SVG_PATH_TAG def color_block_to_point_lists(color_block): point_lists = [[]] @@ -27,18 +27,21 @@ def get_correction_transform(svg): return transform -def color_block_to_polylines(color_block, svg): +def color_block_to_paths(color_block, svg): polylines = [] + # We could emit just a single path with one subpath per point list, but + # emitting multiple paths makes it easier for the user to manipulate them. for point_list in color_block_to_point_lists(color_block): color = color_block.color.visible_on_white.to_hex_str() polylines.append(inkex.etree.Element( - SVG_POLYLINE_TAG, + SVG_PATH_TAG, {'style': simplestyle.formatStyle( {'stroke': color, 'stroke-width': "0.4", 'fill': 'none'}), - 'points': " ".join(",".join(str(coord) for coord in point) for point in point_list), - 'transform': get_correction_transform(svg) + 'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list), + 'transform': get_correction_transform(svg), + 'embroider_manual_stitch': 'true' })) return polylines @@ -63,6 +66,6 @@ def render_stitch_plan(svg, stitch_plan): SVG_GROUP_TAG, {'id': '__color_block_%d__' % i, INKSCAPE_LABEL: "color block %d" % (i + 1)}) - group.extend(color_block_to_polylines(color_block, svg)) + group.extend(color_block_to_paths(color_block, svg)) svg.append(layer) diff --git a/messages.po b/messages.po index 92630bb06..aa2a05239 100644 --- a/messages.po +++ b/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2018-03-30 20:26-0400\n" +"POT-Creation-Date: 2018-04-02 22:11-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -272,6 +272,14 @@ msgstr "" msgid "Repeats" msgstr "" +msgid "Manual stitch placement" +msgstr "" + +msgid "" +"Stitch every node in the path. Stitch length and zig-zag spacing are " +"ignored." +msgstr "" + msgid "" "Unable to autofill. This most often happens because your shape is made " "up of multiple sections that aren't connected."