diff --git a/embroider.inx b/embroider.inx index a139ad785..74217b73e 100644 --- a/embroider.inx +++ b/embroider.inx @@ -4,7 +4,7 @@ jonh.embroider embroider.py inkex.py - 0.0 + _gui-description="Jump stitches smaller than this will be treated as normal stitches.">3.0 true <_option value="csv">Comma Separated Values Format(.CSV) diff --git a/embroider.py b/embroider.py index 265cbe0f6..eb822bae7 100644 --- a/embroider.py +++ b/embroider.py @@ -36,6 +36,8 @@ from pprint import pformat import inkstitch from inkstitch import _, cache, dbg, param, EmbroideryElement, get_nodes, SVG_POLYLINE_TAG, SVG_GROUP_TAG, PIXELS_PER_MM, get_viewbox_transform +from inkstitch.stitches import running_stitch +from inkstitch.utils import cut_path class Fill(EmbroideryElement): element_name = _("Fill") @@ -934,7 +936,6 @@ class Stroke(EmbroideryElement): return self.dashed or self.width <= 0.5 def stroke_points(self, emb_point_list, zigzag_spacing, stroke_width): - patch = Patch(color=self.color) p0 = emb_point_list[0] rho = 0.0 side = 1 @@ -1556,7 +1557,62 @@ def process_trim(stitches, next_stitch): stitches[-3].trim = True -def patches_to_stitches(patch_list, collapse_len_px=0): +def add_tie(stitches, tie_path): + color = tie_path[0].color + + tie_path = cut_path(tie_path, 0.6) + tie_stitches = running_stitch(tie_path, 0.3) + tie_stitches = [inkstitch.Stitch(*stitch, color=color) for stitch in tie_stitches] + + stitches.extend(tie_stitches[1:]) + stitches.extend(list(reversed(tie_stitches))[1:]) + + +def add_tie_off(stitches): + if not stitches: + return + + add_tie(stitches, list(reversed(stitches))) + + +def add_tie_in(stitches, upcoming_stitches): + if not upcoming_stitches: + return + + add_tie(stitches, upcoming_stitches) + + +def add_ties(original_stitches): + """Add tie-off before and after trims, jumps, and color changes.""" + + # we're going to copy most stitches over, adding tie in/off as needed + stitches = [] + + need_tie_in = True + + for i, stitch in enumerate(original_stitches): + is_special = stitch.trim or stitch.jump or stitch.stop + + if is_special and not need_tie_in: + add_tie_off(stitches) + stitches.append(stitch) + need_tie_in = True + elif need_tie_in and not is_special: + stitches.append(stitch) + add_tie_in(stitches, original_stitches[i:]) + need_tie_in = False + else: + stitches.append(stitch) + + # add tie-off at the end if we ended on a normal stitch + if not is_special: + add_tie_off(stitches) + + # overwrite the stitch plan with our new one that contains ties + original_stitches[:] = stitches + + +def patches_to_stitches(patch_list, collapse_len_px=3.0): stitches = [] last_stitch = None @@ -1604,6 +1660,8 @@ def patches_to_stitches(patch_list, collapse_len_px=0): if patch.stop_after: process_stop_after(stitches) + add_ties(stitches) + return stitches def stitches_to_polylines(stitches): @@ -1677,7 +1735,7 @@ class Embroider(inkex.Effect): inkex.Effect.__init__(self) self.OptionParser.add_option("-c", "--collapse_len_mm", action="store", type="float", - dest="collapse_length_mm", default=0.0, + dest="collapse_length_mm", default=3.0, help="max collapse length (mm)") self.OptionParser.add_option("--hide_layers", action="store", type="choice", diff --git a/inkstitch.py b/inkstitch/__init__.py similarity index 100% rename from inkstitch.py rename to inkstitch/__init__.py diff --git a/inkstitch/stitches/__init__.py b/inkstitch/stitches/__init__.py new file mode 100644 index 000000000..7959ef628 --- /dev/null +++ b/inkstitch/stitches/__init__.py @@ -0,0 +1 @@ +from running_stitch import * diff --git a/inkstitch/stitches/running_stitch.py b/inkstitch/stitches/running_stitch.py new file mode 100644 index 000000000..3b1663dd1 --- /dev/null +++ b/inkstitch/stitches/running_stitch.py @@ -0,0 +1,62 @@ +""" Utility functions to produce running stitches. """ + + +def running_stitch(points, stitch_length): + """Generate running stitch along a path. + + Given a path and a stitch length, walk along the path in increments of the + stitch length. If sharp corners are encountered, an extra stitch will be + added at the corner to avoid rounding the corner. The starting and ending + point are always stitched. + + The path is described by a set of line segments, each connected to the next. + The line segments are described by a sequence of points. + """ + + if len(points) < 2: + return [] + + output = [points[0]] + segment_start = points[0] + last_segment_direction = None + + # This tracks the distance we've travelled along the current segment so + # far. Each time we make a stitch, we add the stitch_length to this + # value. If we fall off the end of the current segment, we carry over + # the remainder to the next segment. + distance = 0.0 + + for segment_end in points[1:]: + segment = segment_end - segment_start + segment_length = segment.length() + segment_direction = segment.unit() + + # corner detection + if last_segment_direction: + cos_angle_between = segment_direction * last_segment_direction + + # This checks whether the corner is sharper than 45 degrees. + if cos_angle_between < 0.5: + # Only add the corner point if it's more than 0.1mm away to + # avoid a double-stitch. + if (segment_start - output[-1]).length() > 0.1: + # add a stitch at the corner + output.append(segment_start) + + # next stitch needs to be stitch_length along this segment + distance = stitch_length + + while distance < segment_length: + output.append(segment_start + distance * segment_direction) + distance += stitch_length + + # prepare for the next segment + segment_start = segment_end + last_segment_direction = segment_direction + distance -= segment_length + + # stitch the last point unless we're already almos there + if (segment_start - points[-1]).length() > 0.1: + output.append(segment_start) + + return output diff --git a/inkstitch/utils/__init__.py b/inkstitch/utils/__init__.py new file mode 100644 index 000000000..f0d5783bf --- /dev/null +++ b/inkstitch/utils/__init__.py @@ -0,0 +1 @@ +from geometry import * diff --git a/inkstitch/utils/geometry.py b/inkstitch/utils/geometry.py new file mode 100644 index 000000000..8b6225549 --- /dev/null +++ b/inkstitch/utils/geometry.py @@ -0,0 +1,41 @@ +from .. import Point as InkstitchPoint +from shapely.geometry import LineString, Point as ShapelyPoint + +def cut(line, distance): + """ Cuts a LineString in two at a distance from its starting point. + + This is an example in the Shapely documentation. + """ + if distance <= 0.0 or distance >= line.length: + return [LineString(line)] + coords = list(line.coords) + for i, p in enumerate(coords): + pd = line.project(ShapelyPoint(p)) + if pd == distance: + return [ + LineString(coords[:i+1]), + LineString(coords[i:])] + if pd > distance: + cp = line.interpolate(distance) + return [ + LineString(coords[:i] + [(cp.x, cp.y)]), + LineString([(cp.x, cp.y)] + coords[i:])] + + +def cut_path(points, length): + """Return a subsection of at the start of the path that is length units long. + + Given a path denoted by a set of points, walk along it until we've travelled + the specified length and return a new path up to that point. + + If the original path isn't that long, just return it as is. + """ + + if len(points) < 2: + return points + + path = LineString(points) + subpath, rest = cut(path, length) + + return [InkstitchPoint(*point) for point in subpath.coords] + diff --git a/messages.po b/messages.po deleted file mode 100644 index c0653e4ed..000000000 --- a/messages.po +++ /dev/null @@ -1,267 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-24 20:39-0500\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -msgid "Fill" -msgstr "" - -msgid "Manually routed fill stitching" -msgstr "" - -msgid "Angle of lines of stitches" -msgstr "" - -msgid "Flip fill (start right-to-left)" -msgstr "" - -msgid "Spacing between rows" -msgstr "" - -msgid "Maximum fill stitch length" -msgstr "" - -msgid "Stagger rows this many times before repeating" -msgstr "" - -msgid "Auto-Fill" -msgstr "" - -msgid "Automatically routed fill stitching" -msgstr "" - -msgid "Running stitch length (traversal between sections)" -msgstr "" - -msgid "Underlay" -msgstr "" - -msgid "AutoFill Underlay" -msgstr "" - -msgid "Fill angle (default: fill angle + 90 deg)" -msgstr "" - -msgid "Row spacing (default: 3x fill row spacing)" -msgstr "" - -msgid "Max stitch length" -msgstr "" - -msgid "" -"Unable to autofill. This most often happens because your shape is made up " -"of multiple sections that aren't connected." -msgstr "" - -msgid "" -"Unexpected error while generating fill stitches. Please send your SVG file " -"to lexelby@github." -msgstr "" - -msgid "Satin stitch along paths" -msgstr "" - -msgid "Running stitch length" -msgstr "" - -msgid "Zig-zag spacing (peak-to-peak)" -msgstr "" - -msgid "Repeats" -msgstr "" - -msgid "Satin Column" -msgstr "" - -msgid "Custom satin column" -msgstr "" - -msgid "Pull compensation" -msgstr "" - -msgid "Contour underlay" -msgstr "" - -msgid "Contour Underlay" -msgstr "" - -msgid "Stitch length" -msgstr "" - -msgid "Contour underlay inset amount" -msgstr "" - -msgid "Center-walk underlay" -msgstr "" - -msgid "Center-Walk Underlay" -msgstr "" - -msgid "Zig-zag underlay" -msgstr "" - -msgid "Zig-zag Underlay" -msgstr "" - -msgid "Zig-Zag spacing (peak-to-peak)" -msgstr "" - -msgid "Inset amount (default: half of contour underlay inset)" -msgstr "" - -msgid "" -"One or more rails crosses itself, and this is not allowed. Please split " -"into multiple satin columns." -msgstr "" - -msgid "satin column: One or more of the rungs doesn't intersect both rails." -msgstr "" - -msgid "Each rail should intersect both rungs once." -msgstr "" - -msgid "" -"satin column: One or more of the rungs intersects the rails more than once." -msgstr "" - -#, python-format -msgid "satin column: object %s has a fill (but should not)" -msgstr "" - -#, python-format -msgid "" -"satin column: object %(id)s has two paths with an unequal number of points " -"(%(length1)d and %(length2)d)" -msgstr "" - -msgid "" -"\n" -"\n" -"Seeing a 'no such option' message? Please restart Inkscape to fix." -msgstr "" - -msgid "No embroiderable paths selected." -msgstr "" - -msgid "No embroiderable paths found in document." -msgstr "" - -msgid "" -"Tip: use Path -> Object to Path to convert non-paths before embroidering." -msgstr "" - -msgid "Embroidery" -msgstr "" - -msgid "These settings will be applied to 1 object." -msgstr "" - -#, python-format -msgid "These settings will be applied to %d objects." -msgstr "" - -msgid "" -"Some settings had different values across objects. Select a value from the " -"dropdown or enter a new one." -msgstr "" - -#, python-format -msgid "Disabling this tab will disable the following %d tabs." -msgstr "" - -msgid "Disabling this tab will disable the following tab." -msgstr "" - -#, python-format -msgid "Enabling this tab will disable %s and vice-versa." -msgstr "" - -msgid "Inkscape objects" -msgstr "" - -msgid "Embroidery Params" -msgstr "" - -msgid "Presets" -msgstr "" - -msgid "Load" -msgstr "" - -msgid "Add" -msgstr "" - -msgid "Overwrite" -msgstr "" - -msgid "Delete" -msgstr "" - -msgid "Cancel" -msgstr "" - -msgid "Use Last Settings" -msgstr "" - -msgid "Apply and Quit" -msgstr "" - -msgid "Preview" -msgstr "" - -msgid "Internal Error" -msgstr "" - -msgid "Please enter or select a preset name first." -msgstr "" - -msgid "Preset" -msgstr "" - -#, python-format -msgid "Preset \"%s\" not found." -msgstr "" - -#, python-format -msgid "" -"Preset \"%s\" already exists. Please use another name or press \"Overwrite\"" -msgstr "" - -msgid "Embroidery Simulation" -msgstr "" - -#, python-format -msgid "parseLengthWithUnits: unknown unit %s" -msgstr "" - -#, python-format -msgid "Unknown unit: %s" -msgstr "" - -msgid "TRIM after" -msgstr "" - -msgid "Trim thread after this object (for supported machines and file formats)" -msgstr "" - -msgid "STOP after" -msgstr "" - -msgid "" -"Add STOP instruction after this object (for supported machines and file " -"formats)" -msgstr ""