From 72b001e7c227131a01d3bf3ae8678b704127de77 Mon Sep 17 00:00:00 2001 From: Claudine Date: Sun, 6 Nov 2022 11:03:47 -0500 Subject: add random parameters to satin columns --- lib/elements/satin_column.py | 103 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 8 deletions(-) (limited to 'lib/elements/satin_column.py') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 772d1454..51ed43a4 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -2,16 +2,15 @@ # # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. - +import random from copy import deepcopy from itertools import chain +from inkex import paths from shapely import affinity as shaffinity from shapely import geometry as shgeo from shapely.ops import nearest_points -from inkex import paths - from ..i18n import _ from ..stitch_plan import StitchGroup from ..svg import line_strings_to_csp, point_lists_to_csp @@ -99,6 +98,46 @@ class SatinColumn(EmbroideryElement): def max_stitch_length(self): return self.get_float_param("max_stitch_length_mm") or None + @property + @param('random_split_factor', + _('Random Split Factor'), + tooltip=_('randomize position for split stitches.'), + type='int', unit="%", sort_index=70) + def random_split_factor(self): + return min(max(self.get_int_param("random_split_factor", 0), 0), 100) + + @property + @param('random_first_rail_factor_in', + _('First Rail Random Factor inside'), + tooltip=_('shorten stitch around first rail at most this percent.'), + type='int', unit="%", sort_index=60) + def random_first_rail_factor_in(self): + return min(max(self.get_int_param("random_first_rail_factor_in", 0), 0), 100) + + @property + @param('random_first_rail_factor_out', + _('First Rail Random Factor outside'), + tooltip=_('lengthen stitch around first rail at most this percent.'), + type='int', unit="%", sort_index=61) + def random_first_rail_factor_out(self): + return max(self.get_int_param("random_first_rail_factor_out", 0), 0) + + @property + @param('random_second_rail_factor_in', + _('Second Rail Random Factor inside'), + tooltip=_('shorten stitch around second rail at most this percent.'), + type='int', unit="%", sort_index=62) + def random_second_rail_factor_in(self): + return min(max(self.get_int_param("random_second_rail_factor_in", 0), 0), 100) + + @property + @param('random_second_rail_factor_out', + _('Second Rail Random Factor outside'), + tooltip=_('lengthen stitch around second rail at most this percent.'), + type='int', unit="%", sort_index=63) + def random_second_rail_factor_out(self): + return max(self.get_int_param("random_second_rail_factor_out", 0), 0) + @property @param('short_stitch_inset', _('Short stitch inset'), @@ -132,6 +171,15 @@ class SatinColumn(EmbroideryElement): # peak-to-peak distance between zigzags return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01) + @property + @param('random_zigzag_spacing', + _('Zig-zag spacing randomness(peak-to-peak)'), + tooltip=_('percentage of randomness of Peak-to-peak distance between zig-zags.'), + type='int', unit="%", sort_index=64) + def random_zigzag_spacing(self): + # peak-to-peak distance between zigzags + return max(self.get_int_param("random_zigzag_spacing", 0), 0) + @property @param( 'pull_compensation_percent', @@ -257,6 +305,10 @@ class SatinColumn(EmbroideryElement): def zigzag_underlay_max_stitch_length(self): return self.get_float_param("zigzag_underlay_max_stitch_length_mm") or None + @property + def use_seed(self): + return self.get_int_param("use_seed", 0) + @property @cache def shape(self): @@ -500,6 +552,22 @@ class SatinColumn(EmbroideryElement): for rung in self.rungs: point_lists.append(self.flatten_subpath(rung)) + # If originally there were only two subpaths (no rungs) with same number of rails, we may the rails may now + # have two rails with different number of points, and still no rungs, let's add one. + + if not self.rungs: + rails = [shgeo.LineString(reversed(self.flatten_subpath(rail))) for rail in self.rails] + rails.reverse() + path_list = rails + + rung_start = path_list[0].interpolate(0.1) + rung_end = path_list[1].interpolate(0.1) + rung = shgeo.LineString((rung_start, rung_end)) + # make it a bit bigger so that it definitely intersects + rung = shaffinity.scale(rung, 1.1, 1.1) + path_list.append(rung) + return (self._path_list_to_satins(path_list)) + return self._csp_to_satin(point_lists_to_csp(point_lists)) def apply_transform(self): @@ -789,8 +857,13 @@ class SatinColumn(EmbroideryElement): old_center = new_center if to_travel <= 0: - add_pair(pos0, pos1) - to_travel = spacing + + decalage0 = random.uniform(-self.random_first_rail_factor_in, self.random_first_rail_factor_out) / 100 + decalage1 = random.uniform(-self.random_second_rail_factor_in, self.random_second_rail_factor_out) / 100 + + add_pair(pos0 + (pos0 - pos1) * decalage0, pos1 + (pos1 - pos0) * decalage1) + + to_travel = spacing * (random.uniform(1, 1 + self.random_zigzag_spacing/100)) if to_travel > 0: add_pair(pos0, pos1) @@ -842,8 +915,7 @@ class SatinColumn(EmbroideryElement): patch = StitchGroup(color=self.color) - sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, - -self.zigzag_underlay_inset) + sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, -self.zigzag_underlay_inset) if self._center_walk_is_odd(): sides = [list(reversed(sides[0])), list(reversed(sides[1]))] @@ -953,7 +1025,12 @@ class SatinColumn(EmbroideryElement): split_count = count or int(-(-distance // max_stitch_length)) for i in range(split_count): line = shgeo.LineString((left, right)) - split_point = line.interpolate((i+1)/split_count, normalized=True) + + random_move = 0 + if self.random_split_factor and i != split_count-1: + random_move = random.uniform(-self.random_split_factor / 100, self.random_split_factor / 100) + + split_point = line.interpolate((i + 1 + random_move) / split_count, normalized=True) points.append(Point(split_point.x, split_point.y)) return [points, split_count] @@ -978,6 +1055,16 @@ class SatinColumn(EmbroideryElement): # beziers. The boundary points between beziers serve as "checkpoints", # allowing the user to control how the zigzags flow around corners. + # If no seed is defined, compute one randomly using time to seed, otherwise, use stored seed + + if self.use_seed == 0: + random.seed() + x = random.randint(1, 10000) + random.seed(x) + self.set_param("use_seed", x) + else: + random.seed(self.use_seed) + patch = StitchGroup(color=self.color) if self.center_walk_underlay: -- cgit v1.2.3 From c4c22cac4f3a0bf89856982167cf17089498b517 Mon Sep 17 00:00:00 2001 From: Claudine <88194877+claudinepeyrat06@users.noreply.github.com> Date: Sun, 27 Nov 2022 18:14:41 +0100 Subject: use split parameters --- lib/elements/satin_column.py | 73 ++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) (limited to 'lib/elements/satin_column.py') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 3a183ab0..2021a73f 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -2,11 +2,12 @@ # # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + import random from copy import deepcopy from itertools import chain -import numpy as np +import numpy as np from inkex import paths 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, PIXELS_PER_MM +from .element import PIXELS_PER_MM, EmbroideryElement, param from .validation import ValidationError, ValidationWarning @@ -108,36 +109,31 @@ class SatinColumn(EmbroideryElement): return min(max(self.get_int_param("random_split_factor", 0), 0), 100) @property - @param('random_first_rail_factor_in', - _('First Rail Random Factor inside'), - tooltip=_('shorten stitch around first rail at most this percent.'), - type='int', unit="%", sort_index=60) - def random_first_rail_factor_in(self): - return min(max(self.get_int_param("random_first_rail_factor_in", 0), 0), 100) - - @property - @param('random_first_rail_factor_out', - _('First Rail Random Factor outside'), - tooltip=_('lengthen stitch around first rail at most this percent.'), - type='int', unit="%", sort_index=61) - def random_first_rail_factor_out(self): - return max(self.get_int_param("random_first_rail_factor_out", 0), 0) + @param('random_zigzag_spacing', + _('Zig-zag spacing randomness(peak-to-peak)'), + tooltip=_('percentage of randomness of Peak-to-peak distance between zig-zags.'), + type='int', unit="%", sort_index=64) + def random_zigzag_spacing(self): + # peak-to-peak distance between zigzags + return max(self.get_int_param("random_zigzag_spacing", 0), 0) @property - @param('random_second_rail_factor_in', - _('Second Rail Random Factor inside'), - tooltip=_('shorten stitch around second rail at most this percent.'), - type='int', unit="%", sort_index=62) - def random_second_rail_factor_in(self): - return min(max(self.get_int_param("random_second_rail_factor_in", 0), 0), 100) + @param('random_width_decrease_percent', + _('Random percentage of satin width decrease'), + tooltip=_('shorten stitch across rails at most this percent.' + 'Two values separated by a space may be used for an aysmmetric effect.'), + type='int', unit="% (each side)", sort_index=60) + def random_width_decrease_percent(self): + return self.get_split_float_param("random_width_decrease_percent", (0, 0)) @property - @param('random_second_rail_factor_out', - _('Second Rail Random Factor outside'), - tooltip=_('lengthen stitch around second rail at most this percent.'), - type='int', unit="%", sort_index=63) - def random_second_rail_factor_out(self): - return max(self.get_int_param("random_second_rail_factor_out", 0), 0) + @param('random_width_increase_percent', + _('Random percentage of satin width increase'), + tooltip=_('lengthen stitch across rails at most this percent.' + 'Two values separated by a space may be used for an aysmmetric effect.'), + type='int', unit="% (each side)", sort_index=60) + def random_width_increase_percent(self): + return self.get_split_float_param("random_width_increase_percent", (0, 0)) @property @param('short_stitch_inset', @@ -172,15 +168,6 @@ class SatinColumn(EmbroideryElement): # peak-to-peak distance between zigzags return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01) - @property - @param('random_zigzag_spacing', - _('Zig-zag spacing randomness(peak-to-peak)'), - tooltip=_('percentage of randomness of Peak-to-peak distance between zig-zags.'), - type='int', unit="%", sort_index=64) - def random_zigzag_spacing(self): - # peak-to-peak distance between zigzags - return max(self.get_int_param("random_zigzag_spacing", 0), 0) - @property @param( 'pull_compensation_percent', @@ -604,7 +591,7 @@ class SatinColumn(EmbroideryElement): for rung in self.rungs: point_lists.append(self.flatten_subpath(rung)) - # If originally there were only two subpaths (no rungs) with same number of rails, we may the rails may now + # If originally there were only two subpaths (no rungs) with same number of rails, the rails may now # have two rails with different number of points, and still no rungs, let's add one. if not self.rungs: @@ -899,11 +886,9 @@ class SatinColumn(EmbroideryElement): if to_travel <= 0: - decalage0 = random.uniform(-self.random_first_rail_factor_in, self.random_first_rail_factor_out) / 100 - decalage1 = random.uniform(-self.random_second_rail_factor_in, self.random_second_rail_factor_out) / 100 - - add_pair(pos0 + (pos0 - pos1) * decalage0, pos1 + (pos1 - pos0) * decalage1) - + mismatch0 = random.uniform(-self.random_width_decrease_percent[0], self.random_width_increase_percent[0]) / 100 + mismatch1 = random.uniform(-self.random_width_decrease_percent[1], self.random_width_increase_percent[1]) / 100 + add_pair(pos0 + (pos0 - pos1) * mismatch0, pos1 + (pos1 - pos0) * mismatch1) to_travel = spacing * (random.uniform(1, 1 + self.random_zigzag_spacing/100)) if to_travel > 0: @@ -1080,10 +1065,10 @@ class SatinColumn(EmbroideryElement): points = [] distance = left.distance(right) split_count = count or int(-(-distance // max_stitch_length)) + random_move = 0 for i in range(split_count): line = shgeo.LineString((left, right)) - random_move = 0 if self.random_split_factor and i != split_count-1: random_move = random.uniform(-self.random_split_factor / 100, self.random_split_factor / 100) -- cgit v1.2.3 From e28ea888a9604052d6d7b94c8e29e34b74242994 Mon Sep 17 00:00:00 2001 From: George Steel Date: Mon, 26 Dec 2022 20:13:48 -0500 Subject: use random oracle for randomized satin columns and redo split stitches --- lib/elements/satin_column.py | 298 ++++++++++++++++++++++++------------------- 1 file changed, 164 insertions(+), 134 deletions(-) (limited to 'lib/elements/satin_column.py') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 4d3bbdc3..bfb716af 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -3,8 +3,8 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -import random from copy import deepcopy +import itertools from itertools import chain import numpy as np @@ -16,7 +16,8 @@ from shapely.ops import nearest_points from ..i18n import _ from ..stitch_plan import StitchGroup from ..svg import line_strings_to_csp, point_lists_to_csp -from ..utils import Point, cache, cut, cut_multiple +from ..utils import Point, cache, cut, cut_multiple, prng +from ..stitches import running_stitch from .element import EmbroideryElement, param, PIXELS_PER_MM from .validation import ValidationError, ValidationWarning @@ -85,48 +86,68 @@ class SatinColumn(EmbroideryElement): _('Maximum stitch length'), tooltip=_('Maximum stitch length for split stitches.'), type='float', unit="mm") - def max_stitch_length(self): + def max_stitch_length_px(self): return self.get_float_param("max_stitch_length_mm") or None - @property - @param('random_split_factor', - _('Random Split Factor'), - tooltip=_('randomize position for split stitches.'), - type='int', unit="%", sort_index=70) - def random_split_factor(self): - return min(max(self.get_int_param("random_split_factor", 0), 0), 100) - - @property - @param('random_zigzag_spacing', - _('Zig-zag spacing randomness(peak-to-peak)'), - tooltip=_('percentage of randomness of Peak-to-peak distance between zig-zags.'), - type='int', unit="%", sort_index=64) - def random_zigzag_spacing(self): - # peak-to-peak distance between zigzags - return max(self.get_int_param("random_zigzag_spacing", 0), 0) - @property @param('random_width_decrease_percent', _('Random percentage of satin width decrease'), - tooltip=_('shorten stitch across rails at most this percent.' + tooltip=_('shorten stitch across rails at most this percent. ' 'Two values separated by a space may be used for an aysmmetric effect.'), - type='int', unit="% (each side)", sort_index=60) + default=0, type='float', unit="% (each side)", sort_index=91) + @cache def random_width_decrease_percent(self): return self.get_split_float_param("random_width_decrease_percent", (0, 0)) @property @param('random_width_increase_percent', _('Random percentage of satin width increase'), - tooltip=_('lengthen stitch across rails at most this percent.' + tooltip=_('lengthen stitch across rails at most this percent. ' 'Two values separated by a space may be used for an aysmmetric effect.'), - type='int', unit="% (each side)", sort_index=60) + default=0, type='float', unit="% (each side)", sort_index=90) + @cache def random_width_increase_percent(self): return self.get_split_float_param("random_width_increase_percent", (0, 0)) + @property + @param('random_zigzag_spacing', + _('Random zig-zag spacing percentage increase'), + tooltip=_('Percentage of stitch length added randomply Peak-to-peak distance between zig-zags.'), + default=0, type='float', unit="%", sort_index=92) + def random_zigzag_spacing(self): + # peak-to-peak distance between zigzags + return max(self.get_float_param("random_zigzag_spacing", 0), 0) + + @property + @param('random_split_phase', + _('Random phase for split stitches'), + tooltip=_('Controls whether split stitches are centered or with a random phase (which may increase stitch count).'), + default=False, type='boolean', sort_index=95) + def random_split_phase(self): + return self.get_boolean_param('random_split_phase') + + @property + @param('min_random_split_length_mm', + _('Minimum length for random-phase split.'), + tooltip=_('Defaults to maximum stitch length. Smaller values allow for a transition between single-stitch and split-stitch.'), + default='', type='float', unit='mm', sort_index=96) + def min_random_split_length_px(self): + if self.max_stitch_length_px is None: + return None + return min(self.max_stitch_length_px, self.get_float_param('min_random_split_length_mm', self.max_stitch_length_px)) + + @property + @param('random_split_length_percent', + _('Random jitter split stitch'), + tooltip=_('randomizes split stitch length if random phase is emabled, stitch position if disabled.'), + type='float', unit="%", sort_index=97) + def random_split_length(self): + return min(max(self.get_float_param("random_split_length_percent", 0), 0), 100) / 100 + @property @param('short_stitch_inset', _('Short stitch inset'), - tooltip=_('Stitches in areas with high density will be shortened by this amount.'), + tooltip=_('Stitches in areas with high density will be inset by this amount.'), type='float', unit="%", default=15) def short_stitch_inset(self): @@ -135,7 +156,7 @@ class SatinColumn(EmbroideryElement): @property @param('short_stitch_distance_mm', _('Short stitch distance'), - tooltip=_('Do short stitches if the distance between stitches is smaller than this.'), + tooltip=_('Inset stitches if the distance between stitches is smaller than this.'), type='float', unit="mm", default=0.25) def short_stitch_distance(self): @@ -328,10 +349,6 @@ class SatinColumn(EmbroideryElement): def zigzag_underlay_max_stitch_length(self): return self.get_float_param("zigzag_underlay_max_stitch_length_mm") or None - @property - def use_seed(self): - return self.get_int_param("use_seed", 0) - @property @cache def shape(self): @@ -601,7 +618,7 @@ class SatinColumn(EmbroideryElement): """ # like in do_satin() - points = list(chain.from_iterable(zip(*self.plot_points_on_rails(self.zigzag_spacing)))) + points = list(chain.from_iterable(self.plot_points_on_rails(self.zigzag_spacing))) if isinstance(split_point, float): index_of_closest_stitch = int(round(len(points) * split_point)) @@ -689,7 +706,7 @@ class SatinColumn(EmbroideryElement): @cache def center_line(self): # similar technique to do_center_walk() - center_walk, _ = self.plot_points_on_rails(self.zigzag_spacing, (0, 0), (-0.5, -0.5)) + center_walk = [p[0] for p in 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_proportional): @@ -752,19 +769,22 @@ class SatinColumn(EmbroideryElement): distance_remaining -= segment_length pos = segment_end - def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0)): + def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0), use_random=False) -> list[tuple[Point, Point]]: # 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_proportional) - points[0].append(pos0) - points[1].append(pos1) + # pre-cache ramdomised parameters to avoid property calls in loop + if use_random: + seed = prng.joinArgs(self.random_seed, "satin-points") + offset_proportional_min = np.array(offset_proportional) - self.random_width_decrease_percent/100 + offset_range = (self.random_width_increase_percent + self.random_width_decrease_percent) / 100 + spacing_range = spacing * self.random_zigzag_spacing / 100 - points = [[], []] + pairs = [] to_travel = 0 + cycle = 0 for section0, section1 in self.flattened_sections: # Take one section at a time, delineated by the rungs. For each @@ -827,26 +847,32 @@ class SatinColumn(EmbroideryElement): old_center = new_center if to_travel <= 0: - - mismatch0 = random.uniform(-self.random_width_decrease_percent[0], self.random_width_increase_percent[0]) / 100 - mismatch1 = random.uniform(-self.random_width_decrease_percent[1], self.random_width_increase_percent[1]) / 100 - add_pair(pos0 + (pos0 - pos1) * mismatch0, pos1 + (pos1 - pos0) * mismatch1) - to_travel = spacing * (random.uniform(1, 1 + self.random_zigzag_spacing/100)) + if use_random: + roll = prng.uniformFloats(seed, cycle) + offset_prop = offset_proportional_min + roll[0:2] * offset_range + to_travel = spacing + roll[2] * spacing_range + else: + offset_prop = offset_proportional + to_travel = spacing + + a, b = self.offset_points(pos0, pos1, offset_px, offset_prop) + pairs.append((a, b)) + cycle += 1 if to_travel > 0: - add_pair(pos0, pos1) + pairs.append((pos0, pos1)) - return points + return pairs def do_contour_underlay(self): # "contour walk" underlay: do stitches up one side and down the # other. - forward, back = self.plot_points_on_rails( + pairs = 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))) + stitches = [p[0] for p in pairs] + [p[1] for p in reversed(pairs)] if self._center_walk_is_odd(): - stitches = (list(reversed(back)) + forward) + stitches = list(reversed(stitches)) return StitchGroup( color=self.color, @@ -860,16 +886,16 @@ class SatinColumn(EmbroideryElement): 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( + pairs = 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): if i % 2 == 0: - stitches += forward + stitches += [p[0] for p in pairs] else: - stitches += list(reversed(back)) + stitches += [p[1] for p in reversed(pairs)] return StitchGroup( color=self.color, @@ -889,27 +915,26 @@ class SatinColumn(EmbroideryElement): patch = StitchGroup(color=self.color) - sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, + pairs = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, -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]))] + pairs = list(reversed(pairs)) # This organizes the points in each side in the order that they'll be # visited. - sides = [sides[0][::2] + list(reversed(sides[0][1::2])), - sides[1][1::2] + list(reversed(sides[1][::2]))] + # take a points, from each side in turn, then go backed over the other points + points = [p[i % 2] for i, p in enumerate(pairs)] + list(reversed([p[i % 2] for i, p in enumerate(pairs, 1)])) - # This fancy bit of iterable magic just repeatedly takes a point - # from each side in turn. + max_len = self.zigzag_underlay_max_stitch_length last_point = None - for point in chain.from_iterable(zip(*sides)): - if last_point and self.zigzag_underlay_max_stitch_length: - if last_point.distance(point) > self.zigzag_underlay_max_stitch_length: - points, count = self._get_split_points(last_point, point, self.zigzag_underlay_max_stitch_length) - for point in points: - patch.add_stitch(point) + for point in points: + if last_point and max_len: + if last_point.distance(point) > max_len: + split_points = running_stitch.split_segment_even_dist(last_point, point, max_len) + for p in split_points: + patch.add_stitch(p) last_point = point patch.add_stitch(point) @@ -928,28 +953,47 @@ class SatinColumn(EmbroideryElement): patch = StitchGroup(color=self.color) # pull compensation is automatically converted from mm to pixels by get_float_param - sides = self.plot_points_on_rails( + pairs = self.plot_points_on_rails( self.zigzag_spacing, self.pull_compensation_px, - self.pull_compensation_percent/100 + self.pull_compensation_percent/100, + True, ) - if self.max_stitch_length: - return self.do_split_stitch(patch, sides) + max_stitch_length = self.max_stitch_length_px + length_sigma = self.random_split_length + random_phase = self.random_split_phase + min_split_length = self.min_random_split_length_px + seed = self.random_seed - # short stitches are not not included into the split stitch - # they would move the points in a maybe unwanted behaviour - if self.short_stitch_inset > 0: - self._do_short_stitches(sides) + short_pairs = self.inset_short_stitches_sawtooth(pairs) - # Like in zigzag_underlay(): take a point from each side in turn. - for point in chain.from_iterable(zip(*sides)): - patch.add_stitch(point) + last_point = None + last_short_point = None + last_count = None + for i, (a, b), (a_short, b_short) in zip(itertools.count(0), pairs, short_pairs): + if last_point is not None: + split_points, _ = self.get_split_points( + last_point, a, last_short_point, a_short, max_stitch_length, last_count, + length_sigma, random_phase, min_split_length, prng.joinArgs(seed, 'satin-split', 2*i)) + patch.add_stitches(split_points, ("satin_column", "satin_split_stitch")) + + patch.add_stitch(a_short) + patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) + + split_points, last_count = self.get_split_points( + a, b, a_short, b_short, max_stitch_length, None, + length_sigma, random_phase, min_split_length, prng.joinArgs(seed, 'satin-split', 2*i+1)) + patch.add_stitches(split_points, ("satin_column", "satin_split_stitch")) + + patch.add_stitch(b_short) + patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) + last_point = b + last_short_point = b_short if self._center_walk_is_odd(): patch.stitches = list(reversed(patch.stitches)) - patch.add_tags(("satin_column", "satin_column_edge")) return patch def do_e_stitch(self): @@ -962,15 +1006,16 @@ class SatinColumn(EmbroideryElement): patch = StitchGroup(color=self.color) - sides = self.plot_points_on_rails( + pairs = self.plot_points_on_rails( self.zigzag_spacing, self.pull_compensation_px, - self.pull_compensation_percent/100 + self.pull_compensation_percent/100, + self.random_width_decrease_percent.any() and self.random_width_increase_percent.any() and self.random_zigzag_spacing, ) # "left" and "right" here are kind of arbitrary designations meaning # a point from the first and second rail respectively - for left, right in zip(*sides): + for left, right in pairs: patch.add_stitch(left) patch.add_stitch(right) patch.add_stitch(left) @@ -981,54 +1026,49 @@ class SatinColumn(EmbroideryElement): patch.add_tags(("satin_column", "e_stitch")) return patch - def do_split_stitch(self, patch, sides): - # stitches exceeding the maximum stitch length will be divided into equal parts through additional stitches - for i, (left, right) in enumerate(zip(*sides)): - patch.add_stitch(left) - patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) - points, count = self._get_split_points(left, right, self.max_stitch_length) - for point in points: - patch.add_stitch(point) - patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch")) - patch.add_stitch(right) - patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) - # it is possible that the way back has a different length from the first - # but it looks ugly if the points differ too much - # so let's make sure they have at least the same amount of divisions - if not i+1 >= len(sides[0]): - points, count = self._get_split_points(right, sides[0][i+1], self.max_stitch_length, count) - for point in points: - patch.add_stitch(point) - patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch")) - if self._center_walk_is_odd(): - patch.stitches = list(reversed(patch.stitches)) - return patch - - def _get_split_points(self, left, right, max_stitch_length, count=None): - points = [] - distance = left.distance(right) - split_count = count or int(-(-distance // max_stitch_length)) - random_move = 0 - for i in range(split_count): - line = shgeo.LineString((left, right)) - - if self.random_split_factor and i != split_count-1: - random_move = random.uniform(-self.random_split_factor / 100, self.random_split_factor / 100) - - split_point = line.interpolate((i + 1 + random_move) / split_count, normalized=True) - points.append(Point(split_point.x, split_point.y)) - return [points, split_count] - - def _do_short_stitches(self, sides): - for i, (left, right) in enumerate(zip(*sides)): + def get_split_points(self, a, b, a_short, b_short, length, count=None, length_sigma=0.0, random_phase=False, min_split_length=None, seed=None): + if not length: + return ([], None) + if min_split_length is None: + min_split_length = length + distance = a.distance(b) + if distance <= min_split_length: + return ([], 1) + if random_phase: + points = running_stitch.split_segment_random_phase(a_short, b_short, length, length_sigma, seed) + return (points, None) + elif count is not None: + points = running_stitch.split_segment_even_n(a, b, count, length_sigma, seed) + return (points, count) + else: + points = running_stitch.split_segment_even_dist(a, b, length, length_sigma, seed) + return (points, len(points) + 1) + + def inset_short_stitches_sawtooth(self, pairs): + min_dist = self.short_stitch_distance + inset = min(self.short_stitch_inset, 0.5) + max_stitch_length = None if self.random_split_phase else self.max_stitch_length_px + if not min_dist or not inset: + return pairs + + shortened = [] + for i, (a, b) in enumerate(pairs): if i % 2 == 0: + shortened.append((a, b)) continue - if left.distance(sides[0][i-1]) < self.short_stitch_distance: - split_point = self._get_inset_point(left, right, self.short_stitch_inset) - sides[0][i] = Point(split_point.x, split_point.y) - if right.distance(sides[1][i-1]) < self.short_stitch_distance: - split_point = self._get_inset_point(right, left, self.short_stitch_inset) - sides[1][i] = Point(split_point.x, split_point.y) + dist = a.distance(b) + inset_px = inset * dist + if max_stitch_length and not self.random_split_phase: + # make sure inset is less than split etitch length + inset_px = min(inset_px, max_stitch_length / 3) + + offset_px = [0, 0] + if a.distance(pairs[i-1][0]) < min_dist: + offset_px[0] = -inset_px + if b.distance(pairs[i-1][0]) < min_dist: + offset_px[1] = -inset_px + shortened.append(self.offset_points(a, b, offset_px, (0, 0))) + return shortened def _get_inset_point(self, point1, point2, distance_fraction): return point1 * (1 - distance_fraction) + point2 * distance_fraction @@ -1040,16 +1080,6 @@ class SatinColumn(EmbroideryElement): # beziers. The boundary points between beziers serve as "checkpoints", # allowing the user to control how the zigzags flow around corners. - # If no seed is defined, compute one randomly using time to seed, otherwise, use stored seed - - if self.use_seed == 0: - random.seed() - x = random.randint(1, 10000) - random.seed(x) - self.set_param("use_seed", x) - else: - random.seed(self.use_seed) - patch = StitchGroup(color=self.color) if self.center_walk_underlay: -- cgit v1.2.3 From 26c24d98ed37f5fc63436201539ec68023fce154 Mon Sep 17 00:00:00 2001 From: George Steel Date: Mon, 26 Dec 2022 20:42:13 -0500 Subject: python 3.8 fixes --- lib/elements/satin_column.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/elements/satin_column.py') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index bfb716af..00b468c1 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -6,6 +6,7 @@ from copy import deepcopy import itertools from itertools import chain +import typing import numpy as np from inkex import paths @@ -769,7 +770,7 @@ class SatinColumn(EmbroideryElement): distance_remaining -= segment_length pos = segment_end - def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0), use_random=False) -> list[tuple[Point, Point]]: + def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0), use_random=False) -> typing.List[typing.Tuple[Point, Point]]: # 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(). -- cgit v1.2.3 From 9e88534c271664c06a6515509f24a221634d18b8 Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 27 Dec 2022 01:12:15 -0500 Subject: fix typos --- lib/elements/satin_column.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) (limited to 'lib/elements/satin_column.py') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 00b468c1..c5db1acc 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -97,8 +97,8 @@ class SatinColumn(EmbroideryElement): 'Two values separated by a space may be used for an aysmmetric effect.'), default=0, type='float', unit="% (each side)", sort_index=91) @cache - def random_width_decrease_percent(self): - return self.get_split_float_param("random_width_decrease_percent", (0, 0)) + def random_width_decrease(self): + return self.get_split_float_param("random_width_decrease_percent", (0, 0)) / 100 @property @param('random_width_increase_percent', @@ -107,23 +107,23 @@ class SatinColumn(EmbroideryElement): 'Two values separated by a space may be used for an aysmmetric effect.'), default=0, type='float', unit="% (each side)", sort_index=90) @cache - def random_width_increase_percent(self): - return self.get_split_float_param("random_width_increase_percent", (0, 0)) + def random_width_increase(self): + return self.get_split_float_param("random_width_increase_percent", (0, 0)) / 100 @property - @param('random_zigzag_spacing', + @param('random_zigzag_spacing_percent', _('Random zig-zag spacing percentage increase'), tooltip=_('Percentage of stitch length added randomply Peak-to-peak distance between zig-zags.'), default=0, type='float', unit="%", sort_index=92) def random_zigzag_spacing(self): # peak-to-peak distance between zigzags - return max(self.get_float_param("random_zigzag_spacing", 0), 0) + return max(self.get_float_param("random_zigzag_spacing_percent", 0), 0) / 100 @property @param('random_split_phase', _('Random phase for split stitches'), tooltip=_('Controls whether split stitches are centered or with a random phase (which may increase stitch count).'), - default=False, type='boolean', sort_index=95) + default=False, type='boolean', sort_index=96) def random_split_phase(self): return self.get_boolean_param('random_split_phase') @@ -131,19 +131,19 @@ class SatinColumn(EmbroideryElement): @param('min_random_split_length_mm', _('Minimum length for random-phase split.'), tooltip=_('Defaults to maximum stitch length. Smaller values allow for a transition between single-stitch and split-stitch.'), - default='', type='float', unit='mm', sort_index=96) + default='', type='float', unit='mm', sort_index=97) def min_random_split_length_px(self): if self.max_stitch_length_px is None: return None return min(self.max_stitch_length_px, self.get_float_param('min_random_split_length_mm', self.max_stitch_length_px)) @property - @param('random_split_length_percent', - _('Random jitter split stitch'), - tooltip=_('randomizes split stitch length if random phase is emabled, stitch position if disabled.'), - type='float', unit="%", sort_index=97) - def random_split_length(self): - return min(max(self.get_float_param("random_split_length_percent", 0), 0), 100) / 100 + @param('random_split_jitter_percent', + _('Random jitter for split stitches'), + tooltip=_('Randomizes split stitch length if random phase is enabled, stitch position if disabled.'), + default=0, type='float', unit="%", sort_index=95) + def random_split_jitter(self): + return min(max(self.get_float_param("random_split_jitter_percent", 0), 0), 100) / 100 @property @param('short_stitch_inset', @@ -770,7 +770,8 @@ class SatinColumn(EmbroideryElement): distance_remaining -= segment_length pos = segment_end - def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0), use_random=False) -> typing.List[typing.Tuple[Point, Point]]: + def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0), use_random=False + ) -> typing.List[typing.Tuple[Point, Point]]: # 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(). @@ -778,8 +779,8 @@ class SatinColumn(EmbroideryElement): # pre-cache ramdomised parameters to avoid property calls in loop if use_random: seed = prng.joinArgs(self.random_seed, "satin-points") - offset_proportional_min = np.array(offset_proportional) - self.random_width_decrease_percent/100 - offset_range = (self.random_width_increase_percent + self.random_width_decrease_percent) / 100 + offset_proportional_min = np.array(offset_proportional) - self.random_width_decrease + offset_range = (self.random_width_increase + self.random_width_decrease) spacing_range = spacing * self.random_zigzag_spacing / 100 pairs = [] @@ -962,7 +963,7 @@ class SatinColumn(EmbroideryElement): ) max_stitch_length = self.max_stitch_length_px - length_sigma = self.random_split_length + length_sigma = self.random_split_jitter random_phase = self.random_split_phase min_split_length = self.min_random_split_length_px seed = self.random_seed @@ -1011,7 +1012,7 @@ class SatinColumn(EmbroideryElement): self.zigzag_spacing, self.pull_compensation_px, self.pull_compensation_percent/100, - self.random_width_decrease_percent.any() and self.random_width_increase_percent.any() and self.random_zigzag_spacing, + self.random_width_decrease.any() and self.random_width_increase.any() and self.random_zigzag_spacing, ) # "left" and "right" here are kind of arbitrary designations meaning -- cgit v1.2.3 From 28e6898e2e9857444ad9a54e8a234e3de20bcf91 Mon Sep 17 00:00:00 2001 From: George Steel Date: Wed, 28 Dec 2022 19:38:55 -0500 Subject: fix length ramdomization --- lib/elements/satin_column.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/elements/satin_column.py') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index c5db1acc..e7922a9e 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -112,9 +112,9 @@ class SatinColumn(EmbroideryElement): @property @param('random_zigzag_spacing_percent', - _('Random zig-zag spacing percentage increase'), - tooltip=_('Percentage of stitch length added randomply Peak-to-peak distance between zig-zags.'), - default=0, type='float', unit="%", sort_index=92) + _('Random zig-zag spacing percentage'), + tooltip=_('Afount of random jitter added to stitch length.'), + default=0, type='float', unit="± %", sort_index=92) def random_zigzag_spacing(self): # peak-to-peak distance between zigzags return max(self.get_float_param("random_zigzag_spacing_percent", 0), 0) / 100 @@ -141,7 +141,7 @@ class SatinColumn(EmbroideryElement): @param('random_split_jitter_percent', _('Random jitter for split stitches'), tooltip=_('Randomizes split stitch length if random phase is enabled, stitch position if disabled.'), - default=0, type='float', unit="%", sort_index=95) + default=0, type='float', unit="± %", sort_index=95) def random_split_jitter(self): return min(max(self.get_float_param("random_split_jitter_percent", 0), 0), 100) / 100 @@ -781,7 +781,7 @@ class SatinColumn(EmbroideryElement): seed = prng.joinArgs(self.random_seed, "satin-points") offset_proportional_min = np.array(offset_proportional) - self.random_width_decrease offset_range = (self.random_width_increase + self.random_width_decrease) - spacing_range = spacing * self.random_zigzag_spacing / 100 + spacing_sigma = spacing * self.random_zigzag_spacing pairs = [] @@ -852,7 +852,7 @@ class SatinColumn(EmbroideryElement): if use_random: roll = prng.uniformFloats(seed, cycle) offset_prop = offset_proportional_min + roll[0:2] * offset_range - to_travel = spacing + roll[2] * spacing_range + to_travel = spacing + ((roll[2] - 0.5) * 2 * spacing_sigma) else: offset_prop = offset_proportional to_travel = spacing -- cgit v1.2.3 From 8d13e09bc41f9cfeaa0cd32cd75fa4da07afc03c Mon Sep 17 00:00:00 2001 From: George Steel Date: Sat, 7 Jan 2023 19:32:29 -0500 Subject: spelling --- lib/elements/satin_column.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/elements/satin_column.py') diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index e7922a9e..e4d3ba94 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -113,7 +113,7 @@ class SatinColumn(EmbroideryElement): @property @param('random_zigzag_spacing_percent', _('Random zig-zag spacing percentage'), - tooltip=_('Afount of random jitter added to stitch length.'), + tooltip=_('Amount of random jitter added to stitch length.'), default=0, type='float', unit="± %", sort_index=92) def random_zigzag_spacing(self): # peak-to-peak distance between zigzags -- cgit v1.2.3