kopia lustrzana https://github.com/inkstitch/inkstitch
Make pull-compensation and insets per-side
rodzic
e2ab19d92f
commit
08581d7eae
|
@ -22,3 +22,4 @@ locales/
|
|||
/PROFILE
|
||||
/profile_stats
|
||||
/profile_stats.prof
|
||||
/.vscode
|
||||
|
|
|
@ -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_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):
|
||||
param = INKSTITCH_ATTRIBS[name]
|
||||
self.node.set(param, str(value))
|
||||
|
|
|
@ -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 mechanichal machines.'),
|
||||
unit='mm/cycle',
|
||||
type='float',
|
||||
default=0.4)
|
||||
def zigzag_spacing(self):
|
||||
|
@ -136,39 +137,30 @@ class SatinColumn(EmbroideryElement):
|
|||
@param(
|
||||
'pull_compensation_percent',
|
||||
_('Pull compensation percentage'),
|
||||
tooltip=_('Additional pull compensation which varries as a percentage of stitch width.'),
|
||||
unit='%',
|
||||
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)
|
||||
def pull_compensation_percent(self):
|
||||
# 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
|
||||
@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 by a fixed length.'),
|
||||
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):
|
||||
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)
|
||||
|
||||
@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)))
|
||||
return self.get_lr_mm_param_as_px("pull_compensation_mm", (0, 0))
|
||||
|
||||
@property
|
||||
@param('contour_underlay', _('Contour underlay'), type='toggle', group=_('Contour Underlay'))
|
||||
|
@ -187,11 +179,11 @@ class SatinColumn(EmbroideryElement):
|
|||
_('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'),
|
||||
unit='mm', type='float', default=0.4,
|
||||
unit='mm (each side)', type='float', default=0.4,
|
||||
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
|
||||
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
|
||||
@param('contour_underlay_inset_percent',
|
||||
|
@ -199,22 +191,11 @@ class SatinColumn(EmbroideryElement):
|
|||
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='%', type='float', default=0,
|
||||
unit='% (each side)', type='float', default=0,
|
||||
sort_index=3)
|
||||
def contour_underlay_inset_percent(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_percent", 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)))
|
||||
return self.get_lr_float_param("contour_underlay_inset_percent", (0, 0))
|
||||
|
||||
@property
|
||||
@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)
|
||||
|
||||
@property
|
||||
@param('center_walk_underlay_balance',
|
||||
_('Balance'),
|
||||
tooltip=_('Position of underlay from between the rails (50% is centered), consistent with the Balance parameter for contour underlay.'),
|
||||
@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_balance(self):
|
||||
return min(100, max(0, self.get_float_param("center_walk_underlay_balance", 50)))
|
||||
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'))
|
||||
|
@ -267,13 +248,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
|
||||
|
||||
|
@ -281,7 +262,21 @@ 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_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
|
||||
@param('zigzag_underlay_max_stitch_length_mm',
|
||||
|
@ -375,7 +370,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)
|
||||
|
||||
|
@ -585,7 +580,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))
|
||||
|
@ -677,10 +672,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, 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
|
||||
# useful for pull compensation and insetting underlay.
|
||||
|
||||
|
@ -692,19 +687,18 @@ class SatinColumn(EmbroideryElement):
|
|||
return pos1, pos2
|
||||
|
||||
# calculate the offset for each side
|
||||
offset_total = offset_px + (offset_prop * distance)
|
||||
inv_offset_balance = 1 - offset_balance
|
||||
offset1 = offset_total * inv_offset_balance
|
||||
offset2 = offset_total * offset_balance
|
||||
offset_a = offset_px[0] + (distance * offset_prop[0])
|
||||
offset_b = offset_px[1] + (distance * offset_prop[1])
|
||||
offset_total = offset_a + offset_b
|
||||
|
||||
# don't contract beyond the midpoint, or we'll start expanding
|
||||
if offset1 < -distance * inv_offset_balance:
|
||||
offset1 = -distance * inv_offset_balance
|
||||
if offset2 < -distance * offset_balance:
|
||||
offset2 = -distance * offset_balance
|
||||
if offset_total < -distance:
|
||||
scale = distance / offset_total
|
||||
offset_a = offset_a * scale
|
||||
offset_b = offset_b * scale
|
||||
|
||||
out1 = pos1 + (pos1 - pos2).unit() * offset1
|
||||
out2 = pos2 + (pos2 - pos1).unit() * offset2
|
||||
out1 = pos1 + (pos1 - pos2).unit() * offset_a
|
||||
out2 = pos2 + (pos2 - pos1).unit() * offset_b
|
||||
|
||||
return out1, out2
|
||||
|
||||
|
@ -741,13 +735,13 @@ class SatinColumn(EmbroideryElement):
|
|||
distance_remaining -= segment_length
|
||||
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
|
||||
# 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_px, offset_prop, offset_balance)
|
||||
pos0, pos1 = self.offset_points(pos0, pos1, offset_px, offset_prop)
|
||||
points[0].append(pos0)
|
||||
points[1].append(pos1)
|
||||
|
||||
|
@ -829,8 +823,7 @@ class SatinColumn(EmbroideryElement):
|
|||
# other.
|
||||
forward, back = self.plot_points_on_rails(
|
||||
self.contour_underlay_stitch_length,
|
||||
-self.contour_underlay_inset, -self.contour_underlay_inset_percent/100,
|
||||
self.contour_underlay_inset_balance/100)
|
||||
-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)
|
||||
|
@ -844,10 +837,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,
|
||||
0, -1, self.center_walk_underlay_balance/100)
|
||||
(0, 0), inset_prop)
|
||||
|
||||
stitches = []
|
||||
for i in range(self.center_walk_underlay_repeats):
|
||||
|
@ -875,7 +869,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]))]
|
||||
|
@ -914,9 +909,8 @@ class SatinColumn(EmbroideryElement):
|
|||
# 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,
|
||||
self.pull_compensation_percent/100,
|
||||
self.pull_compensation_balance/100
|
||||
self.pull_compensation_px,
|
||||
self.pull_compensation_percent/100
|
||||
)
|
||||
|
||||
if self.max_stitch_length:
|
||||
|
@ -949,9 +943,8 @@ class SatinColumn(EmbroideryElement):
|
|||
|
||||
sides = self.plot_points_on_rails(
|
||||
self.zigzag_spacing,
|
||||
self.pull_compensation,
|
||||
self.pull_compensation_percent/100,
|
||||
self.pull_compensation_balance/100
|
||||
self.pull_compensation_px,
|
||||
self.pull_compensation_percent/100
|
||||
)
|
||||
|
||||
# "left" and "right" here are kind of arbitrary designations meaning
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -106,21 +106,20 @@ inkstitch_attribs = [
|
|||
'center_walk_underlay',
|
||||
'center_walk_underlay_stitch_length_mm',
|
||||
'center_walk_underlay_repeats',
|
||||
'center_walk_underlay_balance',
|
||||
'center_walk_underlay_position',
|
||||
'contour_underlay',
|
||||
'contour_underlay_stitch_length_mm',
|
||||
'contour_underlay_inset_mm',
|
||||
'contour_underlay_inset_percent',
|
||||
'contour_underlay_inset_balance',
|
||||
'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',
|
||||
'pull_compensation_balance',
|
||||
'stroke_first',
|
||||
# stitch_plan
|
||||
'invisible_layers',
|
||||
|
|
Ładowanie…
Reference in New Issue