summaryrefslogtreecommitdiff
path: root/lib/elements
diff options
context:
space:
mode:
Diffstat (limited to 'lib/elements')
-rw-r--r--lib/elements/fill_stitch.py92
-rw-r--r--lib/elements/satin_column.py18
-rw-r--r--lib/elements/stroke.py58
3 files changed, 133 insertions, 35 deletions
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py
index cb1d5225..56b9888d 100644
--- a/lib/elements/fill_stitch.py
+++ b/lib/elements/fill_stitch.py
@@ -374,7 +374,7 @@ class FillStitch(EmbroideryElement):
tooltip=_('The last stitch in each row is quite close to the first stitch in the next row. '
'Skipping it decreases stitch count and density.'),
type='boolean',
- sort_index=26,
+ sort_index=30,
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'linear_gradient_fill'),
@@ -390,7 +390,7 @@ class FillStitch(EmbroideryElement):
tooltip=_('The flip option can help you with routing your stitch path. '
'When you enable flip, stitching goes from right-to-left instead of left-to-right.'),
type='boolean',
- sort_index=27,
+ sort_index=31,
select_items=[('fill_method', 'legacy_fill')],
default=False)
def flip(self):
@@ -402,7 +402,7 @@ class FillStitch(EmbroideryElement):
_('Reverse fill'),
tooltip=_('Reverses fill path.'),
type='boolean',
- sort_index=28,
+ sort_index=32,
select_items=[('fill_method', 'legacy_fill')],
default=False)
def reverse(self):
@@ -415,7 +415,7 @@ class FillStitch(EmbroideryElement):
tooltip=_('If this option is disabled, the ending point will only be used to define a general direction for '
'stitch routing. When enabled the last section will end at the defined spot.'),
type='boolean',
- sort_index=30,
+ sort_index=33,
select_items=[('fill_method', 'linear_gradient_fill')],
default=False
)
@@ -431,7 +431,7 @@ class FillStitch(EmbroideryElement):
type='boolean',
default=True,
select_items=[('fill_method', 'auto_fill'), ('fill_method', 'guided_fill'), ('fill_method', 'circular_fill')],
- sort_index=30)
+ sort_index=40)
def underpath(self):
return self.get_boolean_param('underpath', True)
@@ -449,7 +449,7 @@ class FillStitch(EmbroideryElement):
('fill_method', 'circular_fill'),
('fill_method', 'linear_gradient_fill'),
('fill_method', 'tartan_fill')],
- sort_index=31)
+ sort_index=41)
def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 2.5), 0.01)
@@ -461,11 +461,41 @@ class FillStitch(EmbroideryElement):
unit='mm',
type='float',
default=0.1,
- sort_index=32)
+ sort_index=43)
def running_stitch_tolerance(self):
return max(self.get_float_param("running_stitch_tolerance_mm", 0.2), 0.01)
@property
+ @param('enable_random_stitches',
+ _('Randomize stitches'),
+ tooltip=_('Randomize stitch length and phase instead of dividing evenly or staggering. '
+ 'This is recommended for closely-spaced curved fills to avoid Moiré artefacts.'),
+ type='boolean',
+ select_items=[('fill_method', 'auto_fill'),
+ ('fill_method', 'contour_fill'),
+ ('fill_method', 'guided_fill'),
+ ('fill_method', 'circular_fill')],
+ default=False,
+ sort_index=44)
+ def enable_random_stitches(self):
+ return self.get_boolean_param('enable_random_stitches', False)
+
+ @property
+ @param('random_stitch_length_jitter_percent',
+ _('Random stitch length jitter'),
+ tooltip=_('Amount to vary the length of each stitch by when randomizing.'),
+ unit='± %',
+ type='float',
+ select_items=[('fill_method', 'auto_fill'),
+ ('fill_method', 'contour_fill'),
+ ('fill_method', 'guided_fill'),
+ ('fill_method', 'circular_fill')],
+ default=10,
+ sort_index=46)
+ def random_stitch_length_jitter(self):
+ return max(self.get_float_param("random_stitch_length_jitter_percent", 10), 0.0) / 100.0
+
+ @property
@param('repeats',
_('Repeats'),
tooltip=_('Defines how many times to run down and back along the path.'),
@@ -473,7 +503,7 @@ class FillStitch(EmbroideryElement):
default="1",
select_items=[('fill_method', 'meander_fill'),
('fill_method', 'circular_fill')],
- sort_index=33)
+ sort_index=50)
def repeats(self):
return max(1, self.get_int_param("repeats", 1))
@@ -489,7 +519,7 @@ class FillStitch(EmbroideryElement):
('fill_method', 'circular_fill'),
('fill_method', 'tartan_fill')],
default=0,
- sort_index=34)
+ sort_index=51)
def bean_stitch_repeats(self):
return self.get_multiple_int_param("bean_stitch_repeats", "0")
@@ -501,7 +531,7 @@ class FillStitch(EmbroideryElement):
type='float',
select_items=[('fill_method', 'meander_fill')],
default=0,
- sort_index=35)
+ sort_index=60)
@cache
def zigzag_spacing(self):
return self.get_float_param("zigzag_spacing_mm", 0)
@@ -514,7 +544,7 @@ class FillStitch(EmbroideryElement):
type='float',
select_items=[('fill_method', 'meander_fill')],
default=3,
- sort_index=36)
+ sort_index=61)
@cache
def zigzag_width(self):
return self.get_float_param("zigzag_width_mm", 3)
@@ -527,7 +557,7 @@ class FillStitch(EmbroideryElement):
type='int',
default="2",
select_items=[('fill_method', 'tartan_fill')],
- sort_index=35
+ sort_index=62
)
def rows_per_thread(self):
return max(1, self.get_int_param("rows_per_thread", 2))
@@ -540,7 +570,7 @@ class FillStitch(EmbroideryElement):
type='int',
default=0,
select_items=[('fill_method', 'tartan_fill')],
- sort_index=36)
+ sort_index=63)
def herringbone_width(self):
return self.get_float_param('herringbone_width_mm', 0)
@@ -648,7 +678,11 @@ class FillStitch(EmbroideryElement):
@param('random_seed',
_('Random seed'),
tooltip=_('Use a specific seed for randomized attributes. Uses the element ID if empty.'),
- select_items=[('fill_method', 'meander_fill')],
+ select_items=[('fill_method', 'auto_fill'),
+ ('fill_method', 'contour_fill'),
+ ('fill_method', 'guided_fill'),
+ ('fill_method', 'circular_fill'),
+ ('fill_method', 'meander_fill')],
type='random_seed',
default='',
sort_index=100)
@@ -963,7 +997,10 @@ class FillStitch(EmbroideryElement):
self.skip_last,
starting_point,
ending_point,
- self.underpath
+ self.underpath,
+ self.enable_random_stitches,
+ self.random_stitch_length_jitter,
+ self.random_seed,
)
)
return [stitch_group]
@@ -986,21 +1023,30 @@ class FillStitch(EmbroideryElement):
self.running_stitch_tolerance,
self.smoothness,
starting_point,
- self.avoid_self_crossing
+ self.avoid_self_crossing,
+ self.enable_random_stitches,
+ self.random_stitch_length_jitter,
+ self.random_seed
)
elif self.contour_strategy == 1:
stitches = contour_fill.single_spiral(
tree,
self.max_stitch_length,
self.running_stitch_tolerance,
- starting_point
+ starting_point,
+ self.enable_random_stitches,
+ self.random_stitch_length_jitter,
+ self.random_seed
)
elif self.contour_strategy == 2:
stitches = contour_fill.double_spiral(
tree,
self.max_stitch_length,
self.running_stitch_tolerance,
- starting_point
+ starting_point,
+ self.enable_random_stitches,
+ self.random_stitch_length_jitter,
+ self.random_seed
)
stitch_group = StitchGroup(
@@ -1038,7 +1084,10 @@ class FillStitch(EmbroideryElement):
starting_point,
ending_point,
self.underpath,
- self.guided_fill_strategy
+ self.guided_fill_strategy,
+ self.enable_random_stitches,
+ self.random_stitch_length_jitter,
+ self.random_seed,
)
)
return [stitch_group]
@@ -1089,7 +1138,10 @@ class FillStitch(EmbroideryElement):
starting_point,
ending_point,
self.underpath,
- target
+ target,
+ self.enable_random_stitches,
+ self.random_stitch_length_jitter,
+ self.random_seed,
)
stitch_group = StitchGroup(
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index a33afb8b..9cf7bc73 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -372,10 +372,11 @@ class SatinColumn(EmbroideryElement):
unit='mm',
group=_('Contour Underlay'),
type='float',
+ default=0.2,
)
def contour_underlay_stitch_tolerance(self):
- tolerance = self.get_float_param("contour_underlay_stitch_tolerance_mm", self.contour_underlay_stitch_length)
- return max(tolerance, 0.01)
+ tolerance = self.get_float_param("contour_underlay_stitch_tolerance_mm", 0.2)
+ return max(tolerance, 0.01 * PIXELS_PER_MM) # sanity check to prevent crash from excessively-small values
@property
@param('contour_underlay_inset_mm',
@@ -428,11 +429,12 @@ class SatinColumn(EmbroideryElement):
),
unit='mm',
group=_('Center-Walk Underlay'),
- type='float'
+ type='float',
+ default=0.2
)
def center_walk_underlay_stitch_tolerance(self):
- tolerance = self.get_float_param("center_walk_underlay_stitch_tolerance_mm", self.contour_underlay_stitch_length)
- return max(tolerance, 0.01)
+ tolerance = self.get_float_param("center_walk_underlay_stitch_tolerance_mm", 0.2)
+ return max(tolerance, 0.01 * PIXELS_PER_MM)
@property
@param('center_walk_underlay_repeats',
@@ -1171,12 +1173,12 @@ class SatinColumn(EmbroideryElement):
self.contour_underlay_stitch_tolerance,
-self.contour_underlay_inset_px, -self.contour_underlay_inset_percent/100)
- first_side = running_stitch.running_stitch(
+ first_side = running_stitch.even_running_stitch(
[points[0] for points in pairs],
self.contour_underlay_stitch_length,
self.contour_underlay_stitch_tolerance
)
- second_side = running_stitch.running_stitch(
+ second_side = running_stitch.even_running_stitch(
[points[1] for points in pairs],
self.contour_underlay_stitch_length,
self.contour_underlay_stitch_tolerance
@@ -1209,7 +1211,7 @@ class SatinColumn(EmbroideryElement):
(0, 0), inset_prop)
points = [points[0] for points in pairs]
- stitches = running_stitch.running_stitch(points, self.center_walk_underlay_stitch_length, self.center_walk_underlay_stitch_tolerance)
+ stitches = running_stitch.even_running_stitch(points, self.center_walk_underlay_stitch_length, self.center_walk_underlay_stitch_tolerance)
for i in range(self.center_walk_underlay_repeats - 1):
if i % 2 == 0:
diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py
index 2ce02dbd..0bb18ce8 100644
--- a/lib/elements/stroke.py
+++ b/lib/elements/stroke.py
@@ -13,8 +13,7 @@ from ..i18n import _
from ..marker import get_marker_elements
from ..stitch_plan import StitchGroup
from ..stitches.ripple_stitch import ripple_stitch
-from ..stitches.running_stitch import (bean_stitch, running_stitch,
- zigzag_stitch)
+from ..stitches.running_stitch import (bean_stitch, running_stitch, zigzag_stitch)
from ..svg import get_node_transform, parse_length_with_units
from ..svg.clip import get_clip_path
from ..threads import ThreadColor
@@ -131,6 +130,30 @@ class Stroke(EmbroideryElement):
return max(self.get_float_param("running_stitch_tolerance_mm", 0.2), 0.01)
@property
+ @param('enable_random_stitches',
+ _('Randomize stitches'),
+ tooltip=_('Randomize stitch length and phase instead of dividing evenly or staggering. '
+ 'This is recommended for closely-spaced curved fills to avoid Moiré artefacts.'),
+ type='boolean',
+ select_items=[('stroke_method', 'running_stitch'), ('stroke_method', 'ripple_stitch')],
+ default=False,
+ sort_index=5)
+ def enable_random_stitches(self):
+ return self.get_boolean_param('enable_random_stitches', False)
+
+ @property
+ @param('random_stitch_length_jitter_percent',
+ _('Random stitch length jitter'),
+ tooltip=_('Amount to vary the length of each stitch by when randomizing.'),
+ unit='± %',
+ type='float',
+ select_items=[('stroke_method', 'running_stitch'), ('stroke_method', 'ripple_stitch')],
+ default=10,
+ sort_index=6)
+ def random_stitch_length_jitter(self):
+ return max(self.get_float_param("random_stitch_length_jitter_percent", 10), 0.0) / 100
+
+ @property
@param('max_stitch_length_mm',
_('Max stitch length'),
tooltip=_('Split stitches longer than this.'),
@@ -203,10 +226,11 @@ class Stroke(EmbroideryElement):
_('Stagger lines this many times before repeating'),
tooltip=_('Length of the cycle by which successive stitch lines are staggered. '
'Fractional values are allowed and can have less visible diagonals than integer values. '
+ 'A value of 0 (default) disables staggering and instead stitches evenly.'
'For linear ripples only.'),
type='int',
select_items=[('stroke_method', 'ripple_stitch')],
- default=1,
+ default=0,
sort_index=9)
def staggers(self):
return self.get_float_param("staggers", 1)
@@ -368,6 +392,24 @@ class Stroke(EmbroideryElement):
return self.get_int_param('join_style', 0)
@property
+ @param('random_seed',
+ _('Random seed'),
+ tooltip=_('Use a specific seed for randomized attributes. Uses the element ID if empty.'),
+ select_items=[('stroke_method', 'running_stitch'),
+ ('stroke_method', 'ripple_stitch')],
+ type='random_seed',
+ default='',
+ sort_index=100)
+ @cache
+ def random_seed(self) -> str:
+ seed = self.get_param('random_seed', '')
+ if not seed:
+ seed = self.node.get_id() or ''
+ # TODO(#1696): When inplementing grouped clones, join this with the IDs of any shadow roots,
+ # letting each instance without a specified seed get a different default.
+ return seed
+
+ @property
@cache
def is_closed(self):
# returns true if the outline of a single line stroke is a closed shape
@@ -443,13 +485,14 @@ class Stroke(EmbroideryElement):
# `self.zigzag_spacing` is the length for a zig and a zag
# together (a V shape). Start with running stitch at half
# that length:
- stitch_group = self.running_stitch(path, zigzag_spacing / 2.0, self.running_stitch_tolerance)
+ stitch_group = self.running_stitch(path, zigzag_spacing / 2.0, self.running_stitch_tolerance, False, 0, "")
stitch_group.stitches = zigzag_stitch(stitch_group.stitches, zigzag_spacing, stroke_width, pull_compensation)
return stitch_group
- def running_stitch(self, path, stitch_length, tolerance):
- stitches = running_stitch(path, stitch_length, tolerance)
+ def running_stitch(self, path, stitch_length, tolerance, enable_random, random_sigma, random_seed):
+ # running stitch with repeats
+ stitches = running_stitch(path, stitch_length, tolerance, enable_random, random_sigma, random_seed)
repeated_stitches = []
# go back and forth along the path as specified by self.repeats
@@ -529,7 +572,8 @@ class Stroke(EmbroideryElement):
stitch_group = self.simple_satin(path, self.zigzag_spacing, self.stroke_width, self.pull_compensation)
# running stitch
else:
- stitch_group = self.running_stitch(path, self.running_stitch_length, self.running_stitch_tolerance)
+ stitch_group = self.running_stitch(path, self.running_stitch_length, self.running_stitch_tolerance,
+ self.enable_random_stitches, self.random_stitch_length_jitter, self.random_seed)
# bean stitch
if any(self.bean_stitch_repeats):
stitch_group.stitches = self.do_bean_repeats(stitch_group.stitches)