Make pull-compensation and insets per-side

pull/1897/head
George Steel 2022-11-17 01:51:33 -05:00
rodzic e2ab19d92f
commit 08581d7eae
5 zmienionych plików z 97 dodań i 77 usunięć

1
.gitignore vendored
Wyświetl plik

@ -22,3 +22,4 @@ locales/
/PROFILE /PROFILE
/profile_stats /profile_stats
/profile_stats.prof /profile_stats.prof
/.vscode

Wyświetl plik

@ -5,6 +5,7 @@
import sys import sys
from copy import deepcopy from copy import deepcopy
import numpy as np
import inkex import inkex
from inkex import bezier from inkex import bezier
@ -135,6 +136,32 @@ class EmbroideryElement(object):
return value 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_lr_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_lr_mm_param_as_px(self, param, default):
return self.get_lr_float_param(param, default) * PIXELS_PER_MM
def set_param(self, name, value): def set_param(self, name, value):
param = INKSTITCH_ATTRIBS[name] param = INKSTITCH_ATTRIBS[name]
self.node.set(param, str(value)) self.node.set(param, str(value))

Wyświetl plik

@ -5,6 +5,7 @@
from copy import deepcopy from copy import deepcopy
from itertools import chain from itertools import chain
import numpy as np
from shapely import affinity as shaffinity from shapely import affinity as shaffinity
from shapely import geometry as shgeo from shapely import geometry as shgeo
@ -16,7 +17,7 @@ from ..i18n import _
from ..stitch_plan import StitchGroup from ..stitch_plan import StitchGroup
from ..svg import line_strings_to_csp, point_lists_to_csp from ..svg import line_strings_to_csp, point_lists_to_csp
from ..utils import Point, cache, collapse_duplicate_point, cut 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 from .validation import ValidationError, ValidationWarning
@ -124,8 +125,8 @@ class SatinColumn(EmbroideryElement):
@property @property
@param('zigzag_spacing_mm', @param('zigzag_spacing_mm',
_('Zig-zag spacing (peak-to-peak)'), _('Zig-zag spacing (peak-to-peak)'),
tooltip=_('Peak-to-peak distance between zig-zags.'), tooltip=_('Peak-to-peak distance between zig-zags. This is double the mm/stitch measurement used by most mechanichal machines.'),
unit='mm', unit='mm/cycle',
type='float', type='float',
default=0.4) default=0.4)
def zigzag_spacing(self): def zigzag_spacing(self):
@ -136,39 +137,30 @@ class SatinColumn(EmbroideryElement):
@param( @param(
'pull_compensation_percent', 'pull_compensation_percent',
_('Pull compensation percentage'), _('Pull compensation percentage'),
tooltip=_('Additional pull compensation which varries as a percentage of stitch width.'), tooltip=_('Additional pull compensation which varries as a percentage of stitch width. '
unit='%', 'Two values separated by a space may be used for an aysmmetric effect.'),
unit='% (each side)',
type='float', type='float',
default=0) default=0)
def pull_compensation_percent(self): def pull_compensation_percent(self):
# pull compensation as a percentage of the width # pull compensation as a percentage of the width
return self.get_float_param("pull_compensation_percent", 0) return self.get_lr_float_param("pull_compensation_percent", (0, 0))
@property @property
@param( @param(
'pull_compensation_mm', 'pull_compensation_mm',
_('Pull compensation'), _('Pull compensation'),
tooltip=_('Satin stitches pull the fabric together, resulting in a column narrower than you draw in Inkscape. ' 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.'), 'This setting expands each pair of needle penetrations outward from the center of the satin column by a fixed length. '
unit='mm', 'Two values separated by a space may be used for an aysmmetric effect.'),
unit='mm (each side)',
type='float', type='float',
default=0) default=0)
def pull_compensation(self): def pull_compensation_px(self):
# In satin stitch, the stitches have a tendency to pull together and # In satin stitch, the stitches have a tendency to pull together and
# narrow the entire column. We can compensate for this by stitching # narrow the entire column. We can compensate for this by stitching
# wider than we desire the column to end up. # wider than we desire the column to end up.
return self.get_float_param("pull_compensation_mm", 0) return self.get_lr_mm_param_as_px("pull_compensation_mm", (0, 0))
@property
@param(
'pull_compensation_balance',
_('Pull compensation balance'),
tooltip=_('Percentage of pull compensation which applies to second rail (50% is centered).'),
unit='%',
type='float',
default=50)
def pull_compensation_balance(self):
return min(100, max(0, self.get_float_param("pull_compensation_balance", 50)))
@property @property
@param('contour_underlay', _('Contour underlay'), type='toggle', group=_('Contour Underlay')) @param('contour_underlay', _('Contour underlay'), type='toggle', group=_('Contour Underlay'))
@ -187,11 +179,11 @@ class SatinColumn(EmbroideryElement):
_('Inset distance (fixed)'), _('Inset distance (fixed)'),
tooltip=_('Shrink the outline by a fixed length, to prevent the underlay from showing around the outside of the satin column.'), tooltip=_('Shrink the outline by a fixed length, to prevent the underlay from showing around the outside of the satin column.'),
group=_('Contour Underlay'), group=_('Contour Underlay'),
unit='mm', type='float', default=0.4, unit='mm (each side)', type='float', default=0.4,
sort_index=2) sort_index=2)
def contour_underlay_inset(self): def contour_underlay_inset_px(self):
# how far inside the edge of the column to stitch the underlay # 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_lr_mm_param_as_px("contour_underlay_inset_mm", (0.4, 0.4))
@property @property
@param('contour_underlay_inset_percent', @param('contour_underlay_inset_percent',
@ -199,22 +191,11 @@ class SatinColumn(EmbroideryElement):
tooltip=_('Shrink the outline by a proportion of the column width, ' tooltip=_('Shrink the outline by a proportion of the column width, '
'to prevent the underlay from showing around the outside of the satin column.'), 'to prevent the underlay from showing around the outside of the satin column.'),
group=_('Contour Underlay'), group=_('Contour Underlay'),
unit='%', type='float', default=0, unit='% (each side)', type='float', default=0,
sort_index=3) sort_index=3)
def contour_underlay_inset_percent(self): def contour_underlay_inset_percent(self):
# how far inside the edge of the column to stitch the underlay # how far inside the edge of the column to stitch the underlay
return min(100, max(0, self.get_float_param("contour_underlay_inset_percent", 0))) return self.get_lr_float_param("contour_underlay_inset_percent", (0, 0))
@property
@param('contour_underlay_inset_balance',
_('Inset balance'),
tooltip=_('Proportion of the inset which applies to second rail (50% is centered). This can be useful for asymmetric designs.'),
group=_('Contour Underlay'),
unit='%', type='float', default=50,
sort_index=4)
def contour_underlay_inset_balance(self):
# how far inside the edge of the column to stitch the underlay
return min(100, max(0, self.get_float_param("contour_underlay_inset_balance", 50)))
@property @property
@param('center_walk_underlay', _('Center-walk underlay'), type='toggle', group=_('Center-Walk Underlay')) @param('center_walk_underlay', _('Center-walk underlay'), type='toggle', group=_('Center-Walk Underlay'))
@ -240,14 +221,14 @@ class SatinColumn(EmbroideryElement):
return max(self.get_int_param("center_walk_underlay_repeats", 2), 1) return max(self.get_int_param("center_walk_underlay_repeats", 2), 1)
@property @property
@param('center_walk_underlay_balance', @param('center_walk_underlay_position',
_('Balance'), _('Position'),
tooltip=_('Position of underlay from between the rails (50% is centered), consistent with the Balance parameter for contour underlay.'), 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'), group=_('Center-Walk Underlay'),
type='float', unit='%', default=50, type='float', unit='%', default=50,
sort_index=3) sort_index=3)
def center_walk_underlay_balance(self): def center_walk_underlay_position(self):
return min(100, max(0, self.get_float_param("center_walk_underlay_balance", 50))) return min(100, max(0, self.get_float_param("center_walk_underlay_position", 50)))
@property @property
@param('zigzag_underlay', _('Zig-zag underlay'), type='toggle', group=_('Zig-zag Underlay')) @param('zigzag_underlay', _('Zig-zag underlay'), type='toggle', group=_('Zig-zag Underlay'))
@ -267,13 +248,13 @@ class SatinColumn(EmbroideryElement):
@property @property
@param('zigzag_underlay_inset_mm', @param('zigzag_underlay_inset_mm',
_('Inset amount'), _('Inset amount (fixed)'),
tooltip=_('default: half of contour underlay inset'), tooltip=_('default: half of contour underlay inset'),
unit='mm', unit='mm (each side)',
group=_('Zig-zag Underlay'), group=_('Zig-zag Underlay'),
type='float', type='float',
default="") 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 # how far in from the edge of the satin the points in the zigzags
# should be # should be
@ -281,7 +262,21 @@ class SatinColumn(EmbroideryElement):
# doing both contour underlay and zigzag underlay, make sure the # doing both contour underlay and zigzag underlay, make sure the
# points of the zigzag fall outside the contour underlay but inside # points of the zigzag fall outside the contour underlay but inside
# the edges of the satin column. # 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_lr_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="")
def zigzag_underlay_inset_percent(self):
default = self.contour_underlay_inset_percent * 0.5
return self.get_lr_float_param("zigzag_underlay_inset_percent", default)
@property @property
@param('zigzag_underlay_max_stitch_length_mm', @param('zigzag_underlay_max_stitch_length_mm',
@ -375,7 +370,7 @@ class SatinColumn(EmbroideryElement):
# intersect with the rails even with floating point inaccuracy. # intersect with the rails even with floating point inaccuracy.
start = Point(*start) start = Point(*start)
end = Point(*end) 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) start = list(start)
end = list(end) end = list(end)
@ -585,7 +580,7 @@ class SatinColumn(EmbroideryElement):
""" """
# like in do_satin() # 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): if isinstance(split_point, float):
index_of_closest_stitch = int(round(len(points) * split_point)) index_of_closest_stitch = int(round(len(points) * split_point))
@ -677,10 +672,10 @@ class SatinColumn(EmbroideryElement):
@cache @cache
def center_line(self): def center_line(self):
# similar technique to do_center_walk() # 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) return shgeo.LineString(center_walk)
def offset_points(self, pos1, pos2, offset_px, offset_prop=0, offset_balance=0.5): def offset_points(self, pos1, pos2, offset_px, offset_prop):
# Expand or contract two points about their midpoint. This is # Expand or contract two points about their midpoint. This is
# useful for pull compensation and insetting underlay. # useful for pull compensation and insetting underlay.
@ -692,19 +687,18 @@ class SatinColumn(EmbroideryElement):
return pos1, pos2 return pos1, pos2
# calculate the offset for each side # calculate the offset for each side
offset_total = offset_px + (offset_prop * distance) offset_a = offset_px[0] + (distance * offset_prop[0])
inv_offset_balance = 1 - offset_balance offset_b = offset_px[1] + (distance * offset_prop[1])
offset1 = offset_total * inv_offset_balance offset_total = offset_a + offset_b
offset2 = offset_total * offset_balance
# don't contract beyond the midpoint, or we'll start expanding # don't contract beyond the midpoint, or we'll start expanding
if offset1 < -distance * inv_offset_balance: if offset_total < -distance:
offset1 = -distance * inv_offset_balance scale = distance / offset_total
if offset2 < -distance * offset_balance: offset_a = offset_a * scale
offset2 = -distance * offset_balance offset_b = offset_b * scale
out1 = pos1 + (pos1 - pos2).unit() * offset1 out1 = pos1 + (pos1 - pos2).unit() * offset_a
out2 = pos2 + (pos2 - pos1).unit() * offset2 out2 = pos2 + (pos2 - pos1).unit() * offset_b
return out1, out2 return out1, out2
@ -741,13 +735,13 @@ class SatinColumn(EmbroideryElement):
distance_remaining -= segment_length distance_remaining -= segment_length
pos = segment_end pos = segment_end
def plot_points_on_rails(self, spacing, offset_px=0, offset_prop=0, offset_balance=0.5): def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_prop=(0, 0)):
# Take a section from each rail in turn, and plot out an equal number # 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 # of points on both rails. Return the points plotted. The points will
# be contracted or expanded by offset using self.offset_points(). # be contracted or expanded by offset using self.offset_points().
def add_pair(pos0, pos1): def add_pair(pos0, pos1):
pos0, pos1 = self.offset_points(pos0, pos1, offset_px, offset_prop, offset_balance) pos0, pos1 = self.offset_points(pos0, pos1, offset_px, offset_prop)
points[0].append(pos0) points[0].append(pos0)
points[1].append(pos1) points[1].append(pos1)
@ -829,8 +823,7 @@ class SatinColumn(EmbroideryElement):
# other. # other.
forward, back = self.plot_points_on_rails( forward, back = self.plot_points_on_rails(
self.contour_underlay_stitch_length, self.contour_underlay_stitch_length,
-self.contour_underlay_inset, -self.contour_underlay_inset_percent/100, -self.contour_underlay_inset_px, -self.contour_underlay_inset_percent/100)
self.contour_underlay_inset_balance/100)
stitches = (forward + list(reversed(back))) stitches = (forward + list(reversed(back)))
if self._center_walk_is_odd(): if self._center_walk_is_odd():
stitches = (list(reversed(back)) + forward) stitches = (list(reversed(back)) + forward)
@ -844,10 +837,11 @@ class SatinColumn(EmbroideryElement):
# Center walk underlay is just a running stitch down and back on the # Center walk underlay is just a running stitch down and back on the
# center line between the bezier curves. # 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. # Do it like contour underlay, but inset all the way to the center.
forward, back = self.plot_points_on_rails( forward, back = self.plot_points_on_rails(
self.center_walk_underlay_stitch_length, self.center_walk_underlay_stitch_length,
0, -1, self.center_walk_underlay_balance/100) (0, 0), inset_prop)
stitches = [] stitches = []
for i in range(self.center_walk_underlay_repeats): for i in range(self.center_walk_underlay_repeats):
@ -875,7 +869,8 @@ class SatinColumn(EmbroideryElement):
patch = StitchGroup(color=self.color) patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, 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(): if self._center_walk_is_odd():
sides = [list(reversed(sides[0])), list(reversed(sides[1]))] sides = [list(reversed(sides[0])), list(reversed(sides[1]))]
@ -914,9 +909,8 @@ class SatinColumn(EmbroideryElement):
# pull compensation is automatically converted from mm to pixels by get_float_param # pull compensation is automatically converted from mm to pixels by get_float_param
sides = self.plot_points_on_rails( sides = self.plot_points_on_rails(
self.zigzag_spacing, self.zigzag_spacing,
self.pull_compensation, self.pull_compensation_px,
self.pull_compensation_percent/100, self.pull_compensation_percent/100
self.pull_compensation_balance/100
) )
if self.max_stitch_length: if self.max_stitch_length:
@ -949,9 +943,8 @@ class SatinColumn(EmbroideryElement):
sides = self.plot_points_on_rails( sides = self.plot_points_on_rails(
self.zigzag_spacing, self.zigzag_spacing,
self.pull_compensation, self.pull_compensation_px,
self.pull_compensation_percent/100, self.pull_compensation_percent/100
self.pull_compensation_balance/100
) )
# "left" and "right" here are kind of arbitrary designations meaning # "left" and "right" here are kind of arbitrary designations meaning

Wyświetl plik

@ -74,7 +74,7 @@ def _get_satin_ripple_helper_lines(stroke):
length = stroke.grid_size or stroke.running_stitch_length length = stroke.grid_size or stroke.running_stitch_length
# use satin column points for satin like build ripple stitches # 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) steps = _get_steps(stroke.get_line_count(), exponent=stroke.exponent, flip=stroke.flip_exponent)

Wyświetl plik

@ -106,21 +106,20 @@ inkstitch_attribs = [
'center_walk_underlay', 'center_walk_underlay',
'center_walk_underlay_stitch_length_mm', 'center_walk_underlay_stitch_length_mm',
'center_walk_underlay_repeats', 'center_walk_underlay_repeats',
'center_walk_underlay_balance', 'center_walk_underlay_position',
'contour_underlay', 'contour_underlay',
'contour_underlay_stitch_length_mm', 'contour_underlay_stitch_length_mm',
'contour_underlay_inset_mm', 'contour_underlay_inset_mm',
'contour_underlay_inset_percent', 'contour_underlay_inset_percent',
'contour_underlay_inset_balance',
'zigzag_underlay', 'zigzag_underlay',
'zigzag_spacing_mm', 'zigzag_spacing_mm',
'zigzag_underlay_inset_mm', 'zigzag_underlay_inset_mm',
'zigzag_underlay_inset_percent',
'zigzag_underlay_spacing_mm', 'zigzag_underlay_spacing_mm',
'zigzag_underlay_max_stitch_length_mm', 'zigzag_underlay_max_stitch_length_mm',
'e_stitch', 'e_stitch',
'pull_compensation_mm', 'pull_compensation_mm',
'pull_compensation_percent', 'pull_compensation_percent',
'pull_compensation_balance',
'stroke_first', 'stroke_first',
# stitch_plan # stitch_plan
'invisible_layers', 'invisible_layers',