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 ""