summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Steel <george.steel@gmail.com>2022-11-24 21:14:26 -0500
committerGitHub <noreply@github.com>2022-11-24 21:14:26 -0500
commitac655910eb4d283e3c00b27260fd1d2dbecd903e (patch)
tree87134bfa73b0a5461b88584145f80d7e9c150d15
parentdf4c5cd59b0e391048c8fd2271893dbd49ec7cd3 (diff)
parent81168fd299c27b1f6a7c5ed62a3a5ca3ff95acbb (diff)
Merge pull request #1897 from inkstitch/george-steel/pull-comp-percent
Add proportional and asymmetric pull compensation and underlay inset options to satin columns, plus a param to swap the 2 rails.
-rw-r--r--.gitignore1
-rw-r--r--lib/elements/element.py27
-rw-r--r--lib/elements/satin_column.py173
-rw-r--r--lib/stitches/ripple_stitch.py2
-rw-r--r--lib/svg/tags.py5
5 files changed, 170 insertions, 38 deletions
diff --git a/.gitignore b/.gitignore
index a64cb5ed..e6dca05c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ locales/
/PROFILE
/profile_stats
/profile_stats.prof
+/.vscode
diff --git a/lib/elements/element.py b/lib/elements/element.py
index 75d22580..96949ca3 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -5,6 +5,7 @@
import sys
from copy import deepcopy
+import numpy as np
import inkex
from inkex import bezier
@@ -135,6 +136,32 @@ class EmbroideryElement(object):
return value
+ # returns 2 float values as a numpy array
+ # if a single number is given in the param, it will apply to both returned values.
+ # Not cached the cache will crash if the default is a numpy array.
+ # The ppoperty calling this will need to cache itself and can safely do so since it has no parameters
+ def get_split_float_param(self, param, default=(0, 0)):
+ default = np.array(default) # type coersion in case the default is a tuple
+
+ raw = self.get_param(param, "")
+ parts = raw.split()
+ try:
+ if len(parts) == 0:
+ return default
+ elif len(parts) == 1:
+ a = float(parts[0])
+ return np.array([a, a])
+ else:
+ a = float(parts[0])
+ b = float(parts[1])
+ return np.array([a, b])
+ except (TypeError, ValueError):
+ return default
+
+ # not cached
+ def get_split_mm_param_as_px(self, param, default):
+ return self.get_split_float_param(param, default) * PIXELS_PER_MM
+
def set_param(self, name, value):
param = INKSTITCH_ATTRIBS[name]
self.node.set(param, str(value))
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index 08f76bd5..e4d69361 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -5,6 +5,7 @@
from copy import deepcopy
from itertools import chain
+import numpy as np
from shapely import affinity as shaffinity
from shapely import geometry as shgeo
@@ -16,7 +17,7 @@ from ..i18n import _
from ..stitch_plan import StitchGroup
from ..svg import line_strings_to_csp, point_lists_to_csp
from ..utils import Point, cache, collapse_duplicate_point, cut
-from .element import EmbroideryElement, param
+from .element import EmbroideryElement, param, PIXELS_PER_MM
from .validation import ValidationError, ValidationWarning
@@ -124,8 +125,8 @@ class SatinColumn(EmbroideryElement):
@property
@param('zigzag_spacing_mm',
_('Zig-zag spacing (peak-to-peak)'),
- tooltip=_('Peak-to-peak distance between zig-zags.'),
- unit='mm',
+ tooltip=_('Peak-to-peak distance between zig-zags. This is double the mm/stitch measurement used by most mechanical machines.'),
+ unit='mm/cycle',
type='float',
default=0.4)
def zigzag_spacing(self):
@@ -134,18 +135,45 @@ class SatinColumn(EmbroideryElement):
@property
@param(
+ 'pull_compensation_percent',
+ _('Pull compensation percentage'),
+ tooltip=_('Additional pull compensation which varries as a percentage of stitch width. '
+ 'Two values separated by a space may be used for an aysmmetric effect.'),
+ unit='% (each side)',
+ type='float',
+ default=0)
+ @cache
+ def pull_compensation_percent(self):
+ # pull compensation as a percentage of the width
+ return self.get_split_float_param("pull_compensation_percent", (0, 0))
+
+ @property
+ @param(
'pull_compensation_mm',
_('Pull compensation'),
- tooltip=_('Satin stitches pull the fabric together, resulting in a column narrower than you draw in Inkscape. '
- 'This setting expands each pair of needle penetrations outward from the center of the satin column.'),
- unit='mm',
+ tooltip=_('Satin stitches pull the fabric together, resulting in a column narrower than you draw in Inkscape. '
+ 'This setting expands each pair of needle penetrations outward from the center of the satin column by a fixed length. '
+ 'Two values separated by a space may be used for an aysmmetric effect.'),
+ unit='mm (each side)',
type='float',
default=0)
- def pull_compensation(self):
+ @cache
+ def pull_compensation_px(self):
# In satin stitch, the stitches have a tendency to pull together and
# narrow the entire column. We can compensate for this by stitching
# wider than we desire the column to end up.
- return self.get_float_param("pull_compensation_mm", 0)
+ return self.get_split_mm_param_as_px("pull_compensation_mm", (0, 0))
+
+ @property
+ @param(
+ 'swap_satin_rails',
+ _('Swap rails'),
+ tooltip=_('Swaps the first and second rails of the satin column, '
+ 'affecting which side the thread finished on as well as any sided properties'),
+ type='boolean',
+ default='false')
+ def swap_rails(self):
+ return self.get_boolean_param('swap_satin_rails', False)
@property
@param('contour_underlay', _('Contour underlay'), type='toggle', group=_('Contour Underlay'))
@@ -161,15 +189,28 @@ class SatinColumn(EmbroideryElement):
@property
@param('contour_underlay_inset_mm',
- _('Contour underlay inset amount'),
- tooltip=_('Shrink the outline, to prevent the underlay from showing around the outside of the satin column.'),
- unit='mm',
+ _('Inset distance (fixed)'),
+ tooltip=_('Shrink the outline by a fixed length, to prevent the underlay from showing around the outside of the satin column.'),
group=_('Contour Underlay'),
- type='float',
- default=0.4)
- def contour_underlay_inset(self):
+ unit='mm (each side)', type='float', default=0.4,
+ sort_index=2)
+ @cache
+ def contour_underlay_inset_px(self):
# how far inside the edge of the column to stitch the underlay
- return self.get_float_param("contour_underlay_inset_mm", 0.4)
+ return self.get_split_mm_param_as_px("contour_underlay_inset_mm", (0.4, 0.4))
+
+ @property
+ @param('contour_underlay_inset_percent',
+ _('Inset distance (proportional)'),
+ tooltip=_('Shrink the outline by a proportion of the column width, '
+ 'to prevent the underlay from showing around the outside of the satin column.'),
+ group=_('Contour Underlay'),
+ unit='% (each side)', type='float', default=0,
+ sort_index=3)
+ @cache
+ def contour_underlay_inset_percent(self):
+ # how far inside the edge of the column to stitch the underlay
+ return self.get_split_float_param("contour_underlay_inset_percent", (0, 0))
@property
@param('center_walk_underlay', _('Center-walk underlay'), type='toggle', group=_('Center-Walk Underlay'))
@@ -184,11 +225,27 @@ class SatinColumn(EmbroideryElement):
return max(self.get_float_param("center_walk_underlay_stitch_length_mm", 1.5), 0.01)
@property
- @param('center_walk_underlay_repeats', _('Repeats'), group=_('Center-Walk Underlay'), type='int', default=2, sort_index=2)
+ @param('center_walk_underlay_repeats',
+ _('Repeats'),
+ tooltip=_('For an odd number of repeats, this will reverse the direction the satin column is stitched, '
+ 'causing stitching to both begin and end at the start point.'),
+ group=_('Center-Walk Underlay'),
+ type='int', default=2,
+ sort_index=2)
def center_walk_underlay_repeats(self):
return max(self.get_int_param("center_walk_underlay_repeats", 2), 1)
@property
+ @param('center_walk_underlay_position',
+ _('Position'),
+ tooltip=_('Position of underlay from between the rails. 0% is along the first rail, 50% is centered, 100% is along the second rail.'),
+ group=_('Center-Walk Underlay'),
+ type='float', unit='%', default=50,
+ sort_index=3)
+ def center_walk_underlay_position(self):
+ return min(100, max(0, self.get_float_param("center_walk_underlay_position", 50)))
+
+ @property
@param('zigzag_underlay', _('Zig-zag underlay'), type='toggle', group=_('Zig-zag Underlay'))
def zigzag_underlay(self):
return self.get_boolean_param("zigzag_underlay")
@@ -206,13 +263,13 @@ class SatinColumn(EmbroideryElement):
@property
@param('zigzag_underlay_inset_mm',
- _('Inset amount'),
+ _('Inset amount (fixed)'),
tooltip=_('default: half of contour underlay inset'),
- unit='mm',
+ unit='mm (each side)',
group=_('Zig-zag Underlay'),
type='float',
default="")
- def zigzag_underlay_inset(self):
+ def zigzag_underlay_inset_px(self):
# how far in from the edge of the satin the points in the zigzags
# should be
@@ -220,7 +277,22 @@ class SatinColumn(EmbroideryElement):
# doing both contour underlay and zigzag underlay, make sure the
# points of the zigzag fall outside the contour underlay but inside
# the edges of the satin column.
- return self.get_float_param("zigzag_underlay_inset_mm") or self.contour_underlay_inset / 2.0
+ default = self.contour_underlay_inset_px * 0.5 / PIXELS_PER_MM
+ x = self.get_split_mm_param_as_px("zigzag_underlay_inset_mm", default)
+ return x
+
+ @property
+ @param('zigzag_underlay_inset_percent',
+ _('Inset amount (proportional)'),
+ tooltip=_('default: half of contour underlay inset'),
+ unit='% (each side)',
+ group=_('Zig-zag Underlay'),
+ type='float',
+ default="")
+ @cache
+ def zigzag_underlay_inset_percent(self):
+ default = self.contour_underlay_inset_percent * 0.5
+ return self.get_split_float_param("zigzag_underlay_inset_percent", default)
@property
@param('zigzag_underlay_max_stitch_length_mm',
@@ -250,7 +322,11 @@ class SatinColumn(EmbroideryElement):
@cache
def rails(self):
"""The rails in order, as point lists"""
- return [subpath for i, subpath in enumerate(self.csp) if i in self.rail_indices]
+ rails = [subpath for i, subpath in enumerate(self.csp) if i in self.rail_indices]
+ if len(rails) == 2 and self.swap_rails:
+ return [rails[1], rails[0]]
+ else:
+ return rails
@property
@cache
@@ -314,7 +390,7 @@ class SatinColumn(EmbroideryElement):
# intersect with the rails even with floating point inaccuracy.
start = Point(*start)
end = Point(*end)
- start, end = self.offset_points(start, end, 0.01)
+ start, end = self.offset_points(start, end, (0.01, 0.01), (0, 0))
start = list(start)
end = list(end)
@@ -524,7 +600,7 @@ class SatinColumn(EmbroideryElement):
"""
# like in do_satin()
- points = list(chain.from_iterable(zip(*self.plot_points_on_rails(self.zigzag_spacing, 0))))
+ points = list(chain.from_iterable(zip(*self.plot_points_on_rails(self.zigzag_spacing))))
if isinstance(split_point, float):
index_of_closest_stitch = int(round(len(points) * split_point))
@@ -616,10 +692,10 @@ class SatinColumn(EmbroideryElement):
@cache
def center_line(self):
# similar technique to do_center_walk()
- center_walk, _ = self.plot_points_on_rails(self.zigzag_spacing, -100000)
+ center_walk, _ = self.plot_points_on_rails(self.zigzag_spacing, (0, 0), (-0.5, -0.5))
return shgeo.LineString(center_walk)
- def offset_points(self, pos1, pos2, offset_px):
+ def offset_points(self, pos1, pos2, offset_px, offset_proportional):
# Expand or contract two points about their midpoint. This is
# useful for pull compensation and insetting underlay.
@@ -630,14 +706,21 @@ class SatinColumn(EmbroideryElement):
# to offset in, so we have to just return the points
return pos1, pos2
+ # calculate the offset for each side
+ offset_a = offset_px[0] + (distance * offset_proportional[0])
+ offset_b = offset_px[1] + (distance * offset_proportional[1])
+ offset_total = offset_a + offset_b
+
# don't contract beyond the midpoint, or we'll start expanding
- if offset_px < -distance / 2.0:
- offset_px = -distance / 2.0
+ if offset_total < -distance:
+ scale = distance / offset_total
+ offset_a = offset_a * scale
+ offset_b = offset_b * scale
- pos1 = pos1 + (pos1 - pos2).unit() * offset_px
- pos2 = pos2 + (pos2 - pos1).unit() * offset_px
+ out1 = pos1 + (pos1 - pos2).unit() * offset_a
+ out2 = pos2 + (pos2 - pos1).unit() * offset_b
- return pos1, pos2
+ return out1, out2
def walk(self, path, start_pos, start_index, distance):
# Move <distance> pixels along <path>, which is a sequence of line
@@ -672,13 +755,13 @@ class SatinColumn(EmbroideryElement):
distance_remaining -= segment_length
pos = segment_end
- def plot_points_on_rails(self, spacing, offset):
+ def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0)):
# Take a section from each rail in turn, and plot out an equal number
# of points on both rails. Return the points plotted. The points will
# be contracted or expanded by offset using self.offset_points().
def add_pair(pos0, pos1):
- pos0, pos1 = self.offset_points(pos0, pos1, offset)
+ pos0, pos1 = self.offset_points(pos0, pos1, offset_px, offset_proportional)
points[0].append(pos0)
points[1].append(pos1)
@@ -758,7 +841,9 @@ class SatinColumn(EmbroideryElement):
def do_contour_underlay(self):
# "contour walk" underlay: do stitches up one side and down the
# other.
- forward, back = self.plot_points_on_rails(self.contour_underlay_stitch_length, -self.contour_underlay_inset)
+ forward, back = self.plot_points_on_rails(
+ self.contour_underlay_stitch_length,
+ -self.contour_underlay_inset_px, -self.contour_underlay_inset_percent/100)
stitches = (forward + list(reversed(back)))
if self._center_walk_is_odd():
stitches = (list(reversed(back)) + forward)
@@ -772,8 +857,11 @@ class SatinColumn(EmbroideryElement):
# Center walk underlay is just a running stitch down and back on the
# center line between the bezier curves.
+ inset_prop = -np.array([self.center_walk_underlay_position, 100-self.center_walk_underlay_position]) / 100
# Do it like contour underlay, but inset all the way to the center.
- forward, back = self.plot_points_on_rails(self.center_walk_underlay_stitch_length, -100000)
+ forward, back = self.plot_points_on_rails(
+ self.center_walk_underlay_stitch_length,
+ (0, 0), inset_prop)
stitches = []
for i in range(self.center_walk_underlay_repeats):
@@ -801,7 +889,8 @@ class SatinColumn(EmbroideryElement):
patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0,
- -self.zigzag_underlay_inset)
+ -self.zigzag_underlay_inset_px,
+ -self.zigzag_underlay_inset_percent/100)
if self._center_walk_is_odd():
sides = [list(reversed(sides[0])), list(reversed(sides[1]))]
@@ -836,7 +925,13 @@ class SatinColumn(EmbroideryElement):
# print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation
patch = StitchGroup(color=self.color)
- sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
+
+ # pull compensation is automatically converted from mm to pixels by get_float_param
+ sides = self.plot_points_on_rails(
+ self.zigzag_spacing,
+ self.pull_compensation_px,
+ self.pull_compensation_percent/100
+ )
if self.max_stitch_length:
return self.do_split_stitch(patch, sides)
@@ -866,7 +961,11 @@ class SatinColumn(EmbroideryElement):
patch = StitchGroup(color=self.color)
- sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
+ sides = self.plot_points_on_rails(
+ self.zigzag_spacing,
+ self.pull_compensation_px,
+ self.pull_compensation_percent/100
+ )
# "left" and "right" here are kind of arbitrary designations meaning
# a point from the first and second rail respectively
diff --git a/lib/stitches/ripple_stitch.py b/lib/stitches/ripple_stitch.py
index 6a0ef7f0..67362f12 100644
--- a/lib/stitches/ripple_stitch.py
+++ b/lib/stitches/ripple_stitch.py
@@ -74,7 +74,7 @@ def _get_satin_ripple_helper_lines(stroke):
length = stroke.grid_size or stroke.running_stitch_length
# use satin column points for satin like build ripple stitches
- rail_points = SatinColumn(stroke.node).plot_points_on_rails(length, 0)
+ rail_points = SatinColumn(stroke.node).plot_points_on_rails(length)
steps = _get_steps(stroke.get_line_count(), exponent=stroke.exponent, flip=stroke.flip_exponent)
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
index 6452cbf3..850256ee 100644
--- a/lib/svg/tags.py
+++ b/lib/svg/tags.py
@@ -104,19 +104,24 @@ inkstitch_attribs = [
'short_stitch_distance_mm',
'short_stitch_inset',
'running_stitch_length_mm',
+ 'swap_satin_rails',
'center_walk_underlay',
'center_walk_underlay_stitch_length_mm',
'center_walk_underlay_repeats',
+ 'center_walk_underlay_position',
'contour_underlay',
'contour_underlay_stitch_length_mm',
'contour_underlay_inset_mm',
+ 'contour_underlay_inset_percent',
'zigzag_underlay',
'zigzag_spacing_mm',
'zigzag_underlay_inset_mm',
+ 'zigzag_underlay_inset_percent',
'zigzag_underlay_spacing_mm',
'zigzag_underlay_max_stitch_length_mm',
'e_stitch',
'pull_compensation_mm',
+ 'pull_compensation_percent',
'stroke_first',
# stitch_plan
'invisible_layers',