diff options
| author | George Steel <george.steel@gmail.com> | 2022-11-24 21:14:26 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-11-24 21:14:26 -0500 |
| commit | ac655910eb4d283e3c00b27260fd1d2dbecd903e (patch) | |
| tree | 87134bfa73b0a5461b88584145f80d7e9c150d15 | |
| parent | df4c5cd59b0e391048c8fd2271893dbd49ec7cd3 (diff) | |
| parent | 81168fd299c27b1f6a7c5ed62a3a5ca3ff95acbb (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-- | .gitignore | 1 | ||||
| -rw-r--r-- | lib/elements/element.py | 27 | ||||
| -rw-r--r-- | lib/elements/satin_column.py | 173 | ||||
| -rw-r--r-- | lib/stitches/ripple_stitch.py | 2 | ||||
| -rw-r--r-- | lib/svg/tags.py | 5 |
5 files changed, 170 insertions, 38 deletions
@@ -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', |
