kopia lustrzana https://github.com/inkstitch/inkstitch
Manual ripple pattern (#3256)
* ripple stitch: add manual pattern placement option * add flip copies option * rewrite adjust grid * more control for satin guided ripples: render at rungspull/3288/head
rodzic
d15b1f40c7
commit
dde0444ac2
|
|
@ -103,6 +103,18 @@ class Stroke(EmbroideryElement):
|
|||
def bean_stitch_repeats(self):
|
||||
return self.get_multiple_int_param("bean_stitch_repeats", "0")
|
||||
|
||||
@property
|
||||
@param('manual_pattern_placement',
|
||||
_('Manual stitch placement'),
|
||||
tooltip=_('No extra stitches will be added to the original ripple pattern '
|
||||
'and the running stitch length value will be ignored.'),
|
||||
type='boolean',
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
default=False,
|
||||
sort_index=3)
|
||||
def manual_pattern_placement(self):
|
||||
return self.get_boolean_param('manual_pattern_placement', False)
|
||||
|
||||
@property
|
||||
@param('running_stitch_length_mm',
|
||||
_('Running stitch length'),
|
||||
|
|
@ -222,6 +234,17 @@ class Stroke(EmbroideryElement):
|
|||
return
|
||||
return max(min_dist, 0.01)
|
||||
|
||||
@property
|
||||
@param('render_at_rungs',
|
||||
_('Render at rungs'),
|
||||
tooltip=_('Position satin guided pattern at rungs.'),
|
||||
type='boolean',
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
default=False,
|
||||
sort_index=9)
|
||||
def render_at_rungs(self):
|
||||
return self.get_boolean_param('render_at_rungs', False)
|
||||
|
||||
@property
|
||||
@param('staggers',
|
||||
_('Stagger lines this many times before repeating'),
|
||||
|
|
@ -260,6 +283,17 @@ class Stroke(EmbroideryElement):
|
|||
def skip_end(self):
|
||||
return abs(self.get_int_param("skip_end", 0))
|
||||
|
||||
@property
|
||||
@param('flip_copies',
|
||||
_('Flip every second line'),
|
||||
tooltip=_('Linear ripple: wether to flip the pattern every second line or not.'),
|
||||
type='boolean',
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
default=True,
|
||||
sort_index=12)
|
||||
def flip_copies(self):
|
||||
return self.get_boolean_param('flip_copies', True)
|
||||
|
||||
@property
|
||||
@param('exponent',
|
||||
_('Line distance exponent'),
|
||||
|
|
@ -267,7 +301,7 @@ class Stroke(EmbroideryElement):
|
|||
type='float',
|
||||
default=1,
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
sort_index=12)
|
||||
sort_index=13)
|
||||
@cache
|
||||
def exponent(self):
|
||||
return max(self.get_float_param("exponent", 1), 0.1)
|
||||
|
|
@ -279,7 +313,7 @@ class Stroke(EmbroideryElement):
|
|||
type='boolean',
|
||||
default=False,
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
sort_index=13)
|
||||
sort_index=14)
|
||||
@cache
|
||||
def flip_exponent(self):
|
||||
return self.get_boolean_param("flip_exponent", False)
|
||||
|
|
@ -291,7 +325,7 @@ class Stroke(EmbroideryElement):
|
|||
type='boolean',
|
||||
default=False,
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
sort_index=14)
|
||||
sort_index=15)
|
||||
@cache
|
||||
def reverse(self):
|
||||
return self.get_boolean_param("reverse", False)
|
||||
|
|
@ -313,7 +347,7 @@ class Stroke(EmbroideryElement):
|
|||
options=_reverse_rails_options,
|
||||
default='automatic',
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
sort_index=15)
|
||||
sort_index=16)
|
||||
def reverse_rails(self):
|
||||
return self.get_param('reverse_rails', 'automatic')
|
||||
|
||||
|
|
@ -339,7 +373,7 @@ class Stroke(EmbroideryElement):
|
|||
# 0: xy, 1: x, 2: y, 3: none
|
||||
options=["X Y", "X", "Y", _("None")],
|
||||
select_items=[('stroke_method', 'ripple_stitch')],
|
||||
sort_index=17)
|
||||
sort_index=18)
|
||||
def scale_axis(self):
|
||||
return self.get_int_param('scale_axis', 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,8 @@ def apply_stitches(line, max_stitch_length, num_staggers, row_spacing, row_num,
|
|||
points = np.array([line.interpolate(projection).coords[0] for projection in projections])
|
||||
|
||||
if len(points) < 2:
|
||||
return line
|
||||
coords = line.coords
|
||||
points = [coords[0], coords[-1]]
|
||||
|
||||
stitched_line = shgeo.LineString(points)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,20 +33,30 @@ def ripple_stitch(stroke):
|
|||
skip_start = _adjust_skip(stroke, num_lines, stroke.skip_start)
|
||||
skip_end = _adjust_skip(stroke, num_lines, stroke.skip_end)
|
||||
|
||||
lines = _get_ripple_lines(stroke, helper_lines, is_linear, skip_start, skip_end)
|
||||
lines = _get_ripple_lines(helper_lines, is_linear, skip_start, skip_end)
|
||||
stitches = _get_stitches(stroke, is_linear, lines, skip_start)
|
||||
|
||||
if stroke.reverse:
|
||||
stitches.reverse()
|
||||
|
||||
if stroke.grid_size != 0:
|
||||
stitches.extend(_do_grid(stroke, helper_lines, skip_start, skip_end, is_linear))
|
||||
if stitches and stroke.grid_size != 0:
|
||||
stitches.extend(_do_grid(stroke, helper_lines, skip_start, skip_end, is_linear, stitches[-1]))
|
||||
|
||||
return _repeat_coords(stitches, stroke.repeats)
|
||||
|
||||
|
||||
def _get_stitches(stroke, is_linear, lines, skip_start):
|
||||
if is_linear:
|
||||
if stroke.manual_pattern_placement:
|
||||
if stroke.flip_copies:
|
||||
stitches = []
|
||||
for i, line in enumerate(lines):
|
||||
if i % 2 == 0:
|
||||
stitches.extend(line[::-1])
|
||||
else:
|
||||
stitches.extend(line)
|
||||
return stitches
|
||||
return [point for line in lines for point in line]
|
||||
if is_linear and stroke.flip_copies:
|
||||
return _get_staggered_stitches(stroke, lines, skip_start)
|
||||
else:
|
||||
points = [point for line in lines for point in line]
|
||||
|
|
@ -69,7 +79,7 @@ def _get_staggered_stitches(stroke, lines, skip_start):
|
|||
for i, line in enumerate(lines):
|
||||
connector = []
|
||||
if i != 0 and stroke.join_style == 0:
|
||||
if i % 2 == 0:
|
||||
if i % 2 == 0 or not stroke.flip_copies:
|
||||
first_point = line[0]
|
||||
else:
|
||||
first_point = line[-1]
|
||||
|
|
@ -81,7 +91,7 @@ def _get_staggered_stitches(stroke, lines, skip_start):
|
|||
should_reverse = (i + skip_start) % 2 == 1
|
||||
|
||||
if enable_random_stitch_length or stroke.staggers == 0:
|
||||
if should_reverse:
|
||||
if should_reverse and stroke.flip_copies:
|
||||
line.reverse()
|
||||
points = running_stitch(line, stitch_length, tolerance, enable_random_stitch_length, length_sigma, prng.join_args(random_seed, i))
|
||||
stitched_line = connector + points
|
||||
|
|
@ -89,7 +99,7 @@ def _get_staggered_stitches(stroke, lines, skip_start):
|
|||
# uses the guided fill alforithm to stagger rows of stitches
|
||||
points = list(apply_stitches(LineString(line), stitch_length, stroke.staggers, 0.5, i, tolerance).coords)
|
||||
stitched_line = [InkstitchPoint(*point) for point in points]
|
||||
if should_reverse:
|
||||
if should_reverse and stroke.flip_copies:
|
||||
stitched_line.reverse()
|
||||
stitched_line = connector + stitched_line
|
||||
|
||||
|
|
@ -104,7 +114,7 @@ def _adjust_skip(stroke, num_lines, skip):
|
|||
return skip
|
||||
|
||||
|
||||
def _get_ripple_lines(stroke, helper_lines, is_linear, skip_start, skip_end):
|
||||
def _get_ripple_lines(helper_lines, is_linear, skip_start, skip_end):
|
||||
lines = []
|
||||
for point_num in range(skip_start, len(helper_lines[0]) - skip_end):
|
||||
row = []
|
||||
|
|
@ -158,9 +168,16 @@ def _get_helper_lines(stroke):
|
|||
if len(lines) > 1:
|
||||
return True, _get_satin_ripple_helper_lines(stroke)
|
||||
else:
|
||||
outline = LineString(even_running_stitch(line_string_to_point_list(lines[0]),
|
||||
stroke.grid_size or stroke.running_stitch_length,
|
||||
stroke.running_stitch_tolerance))
|
||||
if stroke.manual_pattern_placement:
|
||||
path = stroke.parse_path()
|
||||
path = [stroke.strip_control_points(subpath) for subpath in path][0]
|
||||
outline = LineString(path)
|
||||
else:
|
||||
outline = LineString(even_running_stitch(
|
||||
line_string_to_point_list(lines[0]),
|
||||
stroke.grid_size or stroke.running_stitch_length,
|
||||
stroke.running_stitch_tolerance)
|
||||
)
|
||||
|
||||
if stroke.is_closed:
|
||||
return False, _get_circular_ripple_helper_lines(stroke, outline)
|
||||
|
|
@ -189,13 +206,13 @@ def _get_satin_ripple_helper_lines(stroke):
|
|||
for step in steps:
|
||||
helper_lines[-1].append(InkstitchPoint.from_shapely_point(helper_line.interpolate(step, normalized=True)))
|
||||
|
||||
if stroke.join_style == 1:
|
||||
helper_lines = _converge_helper_line_points(helper_lines, True)
|
||||
if stroke.join_style == 1 or not stroke.flip_copies:
|
||||
helper_lines = _converge_helper_line_points(helper_lines, True, stroke.flip_copies)
|
||||
|
||||
return helper_lines
|
||||
|
||||
|
||||
def _converge_helper_line_points(helper_lines, point_edge=False):
|
||||
def _converge_helper_line_points(helper_lines, point_edge=False, flip_copies=True):
|
||||
num_lines = len(helper_lines)
|
||||
steps = _get_steps(num_lines)
|
||||
for i, line in enumerate(helper_lines):
|
||||
|
|
@ -203,7 +220,7 @@ def _converge_helper_line_points(helper_lines, point_edge=False):
|
|||
|
||||
points = []
|
||||
for j in range(len(line) - 1):
|
||||
if point_edge and j % 2 == 1:
|
||||
if point_edge and j % 2 == 1 and flip_copies:
|
||||
k = num_lines - 1 - i
|
||||
points.append(line[j] * (1 - steps[k]) + line[j + 1] * steps[k])
|
||||
else:
|
||||
|
|
@ -250,37 +267,36 @@ def _target_point_helper_lines(stroke, outline):
|
|||
return helper_lines
|
||||
|
||||
|
||||
def _adjust_helper_lines_for_grid(stroke, helper_lines, skip_start, skip_end, is_linear):
|
||||
num_lines = len(helper_lines[0])
|
||||
count = num_lines - skip_start - skip_end
|
||||
|
||||
if stroke.join_style == 0 and (stroke.reverse and count % 2 != 0):
|
||||
count += 1
|
||||
elif (stroke.join_style == 1 and ((stroke.reverse and (count + skip_start) % 2 != 0) or
|
||||
(not stroke.reverse and skip_start % 2 != 0))):
|
||||
count += 1
|
||||
|
||||
if not is_linear:
|
||||
count = 1
|
||||
if stroke.reverse:
|
||||
count = 0
|
||||
|
||||
if count % 2 != 0:
|
||||
helper_lines.reverse()
|
||||
return helper_lines
|
||||
def _adjust_helper_lines_for_grid(grid, last_stitch):
|
||||
# check which end of the grid is nearest to the last stitch
|
||||
# and adjust grid accordingly
|
||||
last_stitch = Point(last_stitch)
|
||||
distances = [
|
||||
Point(grid[0][0]).distance(last_stitch),
|
||||
Point(grid[0][-1]).distance(last_stitch),
|
||||
Point(grid[-1][0]).distance(last_stitch),
|
||||
Point(grid[-1][-1]).distance(last_stitch)
|
||||
]
|
||||
nearest = distances.index(min(distances))
|
||||
if nearest in [1, 3]:
|
||||
adjusted_grid = []
|
||||
for line in grid:
|
||||
adjusted_grid.append(line[::-1])
|
||||
grid = adjusted_grid
|
||||
if nearest in [2, 3]:
|
||||
grid.reverse()
|
||||
return grid
|
||||
|
||||
|
||||
def _do_grid(stroke, helper_lines, skip_start, skip_end, is_linear):
|
||||
helper_lines = _adjust_helper_lines_for_grid(stroke, helper_lines, skip_start, skip_end, is_linear)
|
||||
def _do_grid(stroke, helper_lines, skip_start, skip_end, is_linear, last_stitch):
|
||||
grid = []
|
||||
for i, helper in enumerate(helper_lines):
|
||||
end = len(helper) - skip_end
|
||||
points = helper[skip_start:end]
|
||||
if stroke.reverse:
|
||||
points.reverse()
|
||||
if len(helper_lines) - skip_start - skip_end % 2 != 0:
|
||||
if i % 2 != 0 and is_linear and not stroke.flip_copies and stroke.join_style == 0:
|
||||
points.reverse()
|
||||
grid.append(points)
|
||||
grid = _adjust_helper_lines_for_grid(grid, last_stitch)
|
||||
grid = _get_staggered_stitches(stroke, grid, 0)
|
||||
return grid
|
||||
|
||||
|
|
@ -299,14 +315,22 @@ def _get_guided_helper_lines(stroke, outline, max_distance):
|
|||
def _generate_guided_helper_lines(stroke, outline, max_distance, guide_line):
|
||||
# helper lines are generated by making copies of the outline along the guide line
|
||||
line_point_dict = defaultdict(list)
|
||||
outline = LineString(even_running_stitch(line_string_to_point_list(outline), max_distance, stroke.running_stitch_tolerance))
|
||||
if not stroke.manual_pattern_placement:
|
||||
outline = LineString(even_running_stitch(
|
||||
line_string_to_point_list(outline),
|
||||
max_distance,
|
||||
stroke.running_stitch_tolerance
|
||||
))
|
||||
|
||||
center = outline.centroid
|
||||
center = InkstitchPoint(center.x, center.y)
|
||||
|
||||
count = _get_guided_line_count(stroke, guide_line)
|
||||
if stroke.render_at_rungs:
|
||||
count = len(guide_line.coords)
|
||||
else:
|
||||
count = _get_guided_line_count(stroke, guide_line)
|
||||
outline_steps = _get_steps(count, exponent=stroke.exponent, flip=stroke.flip_exponent)
|
||||
|
||||
outline_steps = _get_steps(count, exponent=stroke.exponent, flip=stroke.flip_exponent)
|
||||
scale_steps = _get_steps(count, start=stroke.scale_start / 100.0, end=stroke.scale_end / 100.0)
|
||||
|
||||
start_point = InkstitchPoint(*(guide_line.coords[0]))
|
||||
|
|
@ -316,7 +340,11 @@ def _generate_guided_helper_lines(stroke, outline, max_distance, guide_line):
|
|||
for i in range(count):
|
||||
check_stop_flag()
|
||||
|
||||
guide_point = InkstitchPoint.from_shapely_point(guide_line.interpolate(outline_steps[i], normalized=True))
|
||||
if stroke.render_at_rungs:
|
||||
# Requires the guide line to be defined as manual stitch
|
||||
guide_point = InkstitchPoint(*guide_line.coords[i])
|
||||
else:
|
||||
guide_point = InkstitchPoint.from_shapely_point(guide_line.interpolate(outline_steps[i], normalized=True))
|
||||
translation = guide_point - start_point
|
||||
scaling = scale_steps[i]
|
||||
if stroke.rotate_ripples and previous_guide_point:
|
||||
|
|
@ -343,11 +371,20 @@ def _get_start_rotation(line):
|
|||
|
||||
def _generate_satin_guide_helper_lines(stroke, outline, guide_line):
|
||||
count = _get_guided_line_count(stroke, guide_line.center_line)
|
||||
spacing = guide_line.center_line.length / (count - 1)
|
||||
pairs = guide_line.plot_points_on_rails(spacing)
|
||||
|
||||
spacing = guide_line.center_line.length / max(1, count - 1)
|
||||
if stroke.render_at_rungs:
|
||||
sections = guide_line.flattened_sections
|
||||
pairs = []
|
||||
for (rail0, rail1) in sections:
|
||||
pairs.append((rail0[-1], rail1[-1]))
|
||||
pairs = pairs[:-1]
|
||||
else:
|
||||
pairs = guide_line.plot_points_on_rails(spacing)
|
||||
|
||||
point0 = pairs[0][0]
|
||||
point1 = pairs[0][1]
|
||||
|
||||
start_rotation = atan2(point1.y - point0.y, point1.x - point0.x)
|
||||
start_scale = (point1 - point0).length()
|
||||
outline_center = InkstitchPoint.from_shapely_point(outline.centroid)
|
||||
|
|
@ -411,6 +448,8 @@ def _get_steps(num_steps, start=0.0, end=1.0, exponent=1, flip=False):
|
|||
|
||||
|
||||
def _repeat_coords(coords, repeats):
|
||||
if not coords:
|
||||
return coords
|
||||
final_coords = []
|
||||
for i in range(repeats):
|
||||
if i % 2 == 1:
|
||||
|
|
|
|||
|
|
@ -118,8 +118,11 @@ inkstitch_attribs = [
|
|||
'cutwork_needle',
|
||||
'zigzag_width_mm',
|
||||
# ripples
|
||||
'manual_pattern_placement',
|
||||
'flip_copies',
|
||||
'line_count',
|
||||
'min_line_dist_mm',
|
||||
'render_at_rungs',
|
||||
'exponent',
|
||||
'flip_exponent',
|
||||
'skip_start',
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue