Tie-in and tie-off (#100)

* turn inkstitch.py into a module

* add running stitch library function

* tie-in and tie-off

* remove temporary testing code
pull/114/head
Lex Neva 2018-02-27 19:43:15 -05:00 zatwierdzone przez GitHub
rodzic e55b8d79f9
commit 88b4ff3e66
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 167 dodań i 271 usunięć

Wyświetl plik

@ -4,7 +4,7 @@
<id>jonh.embroider</id>
<dependency type="executable" location="extensions">embroider.py</dependency>
<dependency type="executable" location="extensions">inkex.py</dependency>
<param name="collapse_len_mm" type="float" min="0.0" max="10.0" _gui-text="Maximum collapse length (mm)">0.0</param>
<param name="collapse_len_mm" type="float" min="0.0" max="10.0" _gui-text="Collapse length (mm)"> _gui-description="Jump stitches smaller than this will be treated as normal stitches.">3.0</param>
<param name="hide_layers" type="boolean" _gui-text="Hide other layers" description="Hide all other top-level layers when the embroidery layer is generated, in order to make stitching discernable.">true</param>
<param name="output_format" type="optiongroup" _gui-text="Output file format" appearance="minimal">
<_option value="csv">Comma Separated Values Format(.CSV)</_option>

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1 @@
from running_stitch import *

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1 @@
from geometry import *

Wyświetl plik

@ -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]

Wyświetl plik

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""