Improve satin guided ripple stitch and add stitch grid first option (#3436)

* ripple stitch: add stitch grid first option
* introduce an anchor line to fine tune satin guided ripples
lexelby/development-extensions
Kaalleen 2025-01-26 07:37:21 +01:00 zatwierdzone przez GitHub
rodzic c08e17b1f8
commit 160ef32d43
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
8 zmienionych plików z 217 dodań i 28 usunięć

Wyświetl plik

@ -236,16 +236,23 @@ class Stroke(EmbroideryElement):
return
return max(min_dist, 0.01)
_satin_guided_pattern_options = [
ParamOption('default', _('Line count / Minimum line distance')),
ParamOption('render_at_rungs', _('Render at rungs')),
ParamOption('adaptive', _('Adaptive + minimum line distance')),
]
@property
@param('render_at_rungs',
_('Render at rungs'),
tooltip=_('Position satin guided pattern at rungs.'),
type='boolean',
@param('satin_guide_pattern_position',
_('Pattern position'),
tooltip=_('Pattern position for satin guided ripples.'),
type='combo',
options=_satin_guided_pattern_options,
default='default',
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)
def satin_guide_pattern_position(self):
return self.get_param('satin_guide_pattern_position', 'line_count')
@property
@param('staggers',
@ -257,7 +264,7 @@ class Stroke(EmbroideryElement):
type='int',
select_items=[('stroke_method', 'ripple_stitch')],
default=0,
sort_index=9)
sort_index=15)
def staggers(self):
return self.get_float_param("staggers", 1)
@ -268,7 +275,7 @@ class Stroke(EmbroideryElement):
type='int',
default=0,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=10)
sort_index=16)
@cache
def skip_start(self):
return abs(self.get_int_param("skip_start", 0))
@ -280,7 +287,7 @@ class Stroke(EmbroideryElement):
type='int',
default=0,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=11)
sort_index=17)
@cache
def skip_end(self):
return abs(self.get_int_param("skip_end", 0))
@ -292,7 +299,7 @@ class Stroke(EmbroideryElement):
type='boolean',
select_items=[('stroke_method', 'ripple_stitch')],
default=True,
sort_index=12)
sort_index=18)
def flip_copies(self):
return self.get_boolean_param('flip_copies', True)
@ -303,7 +310,7 @@ class Stroke(EmbroideryElement):
type='float',
default=1,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=13)
sort_index=19)
@cache
def exponent(self):
return max(self.get_float_param("exponent", 1), 0.1)
@ -315,7 +322,7 @@ class Stroke(EmbroideryElement):
type='boolean',
default=False,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=14)
sort_index=20)
@cache
def flip_exponent(self):
return self.get_boolean_param("flip_exponent", False)
@ -327,7 +334,7 @@ class Stroke(EmbroideryElement):
type='boolean',
default=False,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=15)
sort_index=21)
@cache
def reverse(self):
return self.get_boolean_param("reverse", False)
@ -349,7 +356,7 @@ class Stroke(EmbroideryElement):
options=_reverse_rails_options,
default='automatic',
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=16)
sort_index=22)
def reverse_rails(self):
return self.get_param('reverse_rails', 'automatic')
@ -361,11 +368,23 @@ class Stroke(EmbroideryElement):
default=0,
unit='mm',
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=16)
sort_index=23)
@cache
def grid_size(self):
return abs(self.get_float_param("grid_size_mm", 0))
@property
@param('grid_first',
_('Stitch grid first'),
tooltip=_('Reverse the stitch paths, so that the grid will be stitched first'),
type='boolean',
default=False,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=24)
@cache
def grid_first(self):
return self.get_boolean_param("grid_first", False)
@property
@param('scale_axis',
_('Scale axis'),
@ -375,7 +394,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=18)
sort_index=25)
def scale_axis(self):
return self.get_int_param('scale_axis', 0)
@ -387,7 +406,7 @@ class Stroke(EmbroideryElement):
unit='%',
default=100,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=18)
sort_index=26)
def scale_start(self):
return self.get_float_param('scale_start', 100.0)
@ -399,7 +418,7 @@ class Stroke(EmbroideryElement):
unit='%',
default=0.0,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=19)
sort_index=27)
def scale_end(self):
return self.get_float_param('scale_end', 0.0)
@ -410,7 +429,7 @@ class Stroke(EmbroideryElement):
type='boolean',
default=True,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=20)
sort_index=30)
@cache
def rotate_ripples(self):
return self.get_boolean_param("rotate_ripples", True)
@ -423,7 +442,7 @@ class Stroke(EmbroideryElement):
default=0,
options=(_("flat"), _("point")),
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=21)
sort_index=31)
@cache
def join_style(self):
return self.get_int_param('join_style', 0)
@ -651,6 +670,16 @@ class Stroke(EmbroideryElement):
return guide_lines['satin'][0]
return guide_lines['stroke'][0]
@cache
def get_anchor_line(self):
anchor_lines = get_marker_elements(self.node, "anchor-line", False, True, False)
# No or empty guide line
if not anchor_lines or not anchor_lines['stroke']:
return None
# ignore multiple anchor lines
return anchor_lines['stroke'][0].geoms[0]
def _representative_point(self):
# if we just take the center of a line string we could end up on some point far away from the actual line
try:

Wyświetl plik

@ -58,6 +58,7 @@ from .remove_embroidery_settings import RemoveEmbroiderySettings
from .reorder import Reorder
from .satin_multicolor import SatinMulticolor
from .select_elements import SelectElements
from .selection_to_anchor_line import SelectionToAnchorLine
from .selection_to_guide_line import SelectionToGuideLine
from .selection_to_pattern import SelectionToPattern
from .simulator import Simulator
@ -128,6 +129,7 @@ __all__ = extensions = [About,
Reorder,
SatinMulticolor,
SelectElements,
SelectionToAnchorLine,
SelectionToGuideLine,
SelectionToPattern,
Simulator,

Wyświetl plik

@ -0,0 +1,26 @@
# Authors: see git history
#
# Copyright (c) 2021 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import inkex
from ..i18n import _
from ..marker import set_marker
from ..svg.tags import EMBROIDERABLE_TAGS
from .base import InkstitchExtension
class SelectionToAnchorLine(InkstitchExtension):
def effect(self):
if not self.get_elements():
return
if not self.svg.selected:
inkex.errormsg(_("Please select at least one object to be marked as a anchor line."))
return
for pattern in self.get_nodes():
if pattern.tag in EMBROIDERABLE_TAGS:
set_marker(pattern, 'start', 'anchor-line')

Wyświetl plik

@ -12,7 +12,7 @@ from shapely import geometry as shgeo
from .svg.tags import EMBROIDERABLE_TAGS
from .utils import cache, get_bundled_dir
MARKER = ['pattern', 'guide-line']
MARKER = ['anchor-line', 'pattern', 'guide-line']
def ensure_marker(svg, marker):

Wyświetl plik

@ -41,6 +41,8 @@ def ripple_stitch(stroke):
if stitches and stroke.grid_size != 0:
stitches.extend(_do_grid(stroke, helper_lines, skip_start, skip_end, is_linear, stitches[-1]))
if stroke.grid_first:
stitches = stitches[::-1]
return _repeat_coords(stitches, stroke.repeats)
@ -306,7 +308,7 @@ def _get_guided_helper_lines(stroke, outline, max_distance):
guide_line = stroke.get_guide_line()
if isinstance(guide_line, SatinColumn):
# satin type guide line
return _generate_satin_guide_helper_lines(stroke, outline, guide_line)
return generate_satin_guide_helper_lines(stroke, outline, guide_line)
else:
# simple guide line
return _generate_guided_helper_lines(stroke, outline, max_distance, guide_line.geoms[0])
@ -325,7 +327,7 @@ def _generate_guided_helper_lines(stroke, outline, max_distance, guide_line):
center = outline.centroid
center = InkstitchPoint(center.x, center.y)
if stroke.render_at_rungs:
if stroke.satin_guide_pattern_position == "render_at_rungs":
count = len(guide_line.coords)
else:
count = _get_guided_line_count(stroke, guide_line)
@ -340,7 +342,7 @@ def _generate_guided_helper_lines(stroke, outline, max_distance, guide_line):
for i in range(count):
check_stop_flag()
if stroke.render_at_rungs:
if stroke.satin_guide_pattern_position == "render_at_rungs":
# Requires the guide line to be defined as manual stitch
guide_point = InkstitchPoint(*guide_line.coords[i])
else:
@ -369,11 +371,38 @@ def _get_start_rotation(line):
return atan2(point1.y - point0.y, point1.x - point0.x)
def _generate_satin_guide_helper_lines(stroke, outline, guide_line):
def generate_satin_guide_helper_lines(stroke, outline, guide_line):
anchor_line = stroke.get_anchor_line()
if anchor_line:
# position, rotation and scale defined by anchor line
outline0 = InkstitchPoint(*anchor_line.coords[0])
outline1 = InkstitchPoint(*anchor_line.coords[-1])
else:
# position rotation and scale defined by line end points
outline_coords = outline.coords
outline0 = InkstitchPoint(*outline_coords[0])
outline1 = InkstitchPoint(*outline_coords[-1])
if outline0 == outline1:
return _generate_simple_satin_guide_helper_lines(stroke, outline, guide_line)
outline_width = (outline1 - outline0).length()
outline_rotation = atan2(outline1.y - outline0.y, outline1.x - outline0.x)
if stroke.satin_guide_pattern_position == "adaptive":
return _generate_satin_guide_helper_lines_with_varying_pattern_distance(
stroke, guide_line, outline, outline0, outline_width, outline_rotation
)
else:
return _generate_satin_guide_helper_lines_with_constant_pattern_distance(
stroke, guide_line, outline, outline0, outline_width, outline_rotation
)
def _generate_simple_satin_guide_helper_lines(stroke, outline, guide_line):
count = _get_guided_line_count(stroke, guide_line.center_line)
spacing = guide_line.center_line.length / max(1, count - 1)
if stroke.render_at_rungs:
if stroke.satin_guide_pattern_position == "render_at_rungs":
sections = guide_line.flattened_sections
pairs = []
for (rail0, rail1) in sections:
@ -413,6 +442,70 @@ def _generate_satin_guide_helper_lines(stroke, outline, guide_line):
return _point_dict_to_helper_lines(len(outline.coords), line_point_dict)
def _generate_satin_guide_helper_lines_with_constant_pattern_distance(stroke, guide_line, outline, outline0, outline_width, outline_rotation):
# add scaled and rotated outlines along the satin column guide line
if stroke.satin_guide_pattern_position == "render_at_rungs":
sections = guide_line.flattened_sections
pairs = []
for (rail0, rail1) in sections:
pairs.append((rail0[-1], rail1[-1]))
else:
count = _get_guided_line_count(stroke, guide_line.center_line)
spacing = guide_line.center_line.length / max(1, count - 1)
pairs = guide_line.plot_points_on_rails(spacing)
if pairs[0] == pairs[-1]:
pairs = pairs[:-1]
line_point_dict = defaultdict(list)
for i, (point0, point1) in enumerate(pairs):
check_stop_flag()
# move to point0, rotate and scale so the other point hits point1
scaling = (point1 - point0).length() / outline_width
rotation = atan2(point1.y - point0.y, point1.x - point0.x)
rotation = rotation - outline_rotation
translation = point0 - outline0
transformed_outline = _transform_outline(translation, rotation, scaling, outline, Point(point0), 0)
# outline to helper line points
for j, point in enumerate(transformed_outline.coords):
line_point_dict[j].append(InkstitchPoint(point[0], point[1]))
return _point_dict_to_helper_lines(len(outline.coords), line_point_dict)
def _generate_satin_guide_helper_lines_with_varying_pattern_distance(stroke, guide_line, outline, outline0, outline_width, outline_rotation):
# rotate pattern and get the pattern width
minx, miny, maxx, maxy = _transform_outline(Point([0, 0]), outline_rotation, 1, outline, Point(outline0), 0).bounds
pattern_width = maxx - minx
distance = 0
line_point_dict = defaultdict(list)
while True:
if distance > guide_line.center_line.length:
break
check_stop_flag()
cut_point = guide_line.center_line.interpolate(distance)
point0, point1 = guide_line.find_cut_points(*cut_point.coords)
# move to point0, rotate and scale so the other point hits point1
scaling = (point1 - point0).length() / outline_width
rotation = atan2(point1.y - point0.y, point1.x - point0.x)
rotation = rotation - outline_rotation
translation = point0 - outline0
transformed_outline = _transform_outline(translation, rotation, scaling, outline, Point(point0), 0)
min_distance = stroke.min_line_dist or 0
distance += max(1, (pattern_width * scaling) + min_distance)
# outline to helper line points
for j, point in enumerate(transformed_outline.coords):
line_point_dict[j].append(InkstitchPoint(point[0], point[1]))
return _point_dict_to_helper_lines(len(outline.coords), line_point_dict)
def _transform_outline(translation, rotation, scaling, outline, origin, scale_axis):
# transform
transformed_outline = translate(outline, translation.x, translation.y)

Wyświetl plik

@ -122,7 +122,7 @@ inkstitch_attribs = [
'flip_copies',
'line_count',
'min_line_dist_mm',
'render_at_rungs',
'satin_guide_pattern_position',
'exponent',
'flip_exponent',
'skip_start',
@ -132,6 +132,7 @@ inkstitch_attribs = [
'scale_end',
'rotate_ripples',
'grid_size_mm',
'grid_first',
# satin column
'satin_column',
'satin_method',

Wyświetl plik

@ -16,6 +16,25 @@
xmlns:inkstitch="http://inkstitch.org/namespace">
<defs
id="defs1">
<marker
refX="10"
refY="5"
orient="auto"
id="inkstitch-anchor-line-marker"
markerUnits="userSpaceOnUse"
markerWidth="0.5"
viewBox="0 0 1 1">
<g
id="inkstitch-anchor-group">
<path
style="fill:#fafafa;stroke:#ff5500;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1, 1;stroke-dashoffset:0;stroke-opacity:1;fill-opacity:0.8;"
d="M 10.12911,5.2916678 A 4.8374424,4.8374426 0 0 1 5.2916656,10.12911 4.8374424,4.8374426 0 0 1 0.45422399,5.2916678 4.8374424,4.8374426 0 0 1 5.2916656,0.45422399 4.8374424,4.8374426 0 0 1 10.12911,5.2916678 Z"
id="inkstitch-anchor-marker-circle" />
<path
id="inkstitch-anchor-marker-anchor"
d="M 5.2906995,1.4202597 C 4.8679006,1.4083964 4.4823552,1.8090635 4.5397442,2.2343062 4.568792,2.5148029 4.7793023,2.7587002 5.0437779,2.8466079 5.0437779,2.9192332 5.0437779,2.9918586 5.0437779,3.0644837 4.757712,3.0652868 4.4715658,3.0628316 4.1855481,3.0656999 3.983847,3.0756357 3.8853404,3.3543656 4.0348752,3.489474 4.1478426,3.6002592 4.31397,3.5512227 4.4554139,3.5621911 4.6515344,3.5621911 4.8476573,3.5621911 5.0437779,3.5621911 5.0437779,5.0224311 5.0437779,6.4826482 5.0437779,7.9428881 5.0096543,8.024302 4.9683829,8.1027557 4.9314507,8.1830222 4.5485281,8.0564269 4.1615026,7.9023189 3.8538855,7.6348557 3.7475473,7.5519503 3.6239924,7.3164515 3.6794837,7.2402006 3.7811339,7.2804256 3.8827819,7.3206736 3.9844344,7.3609216 3.7194356,6.8548158 3.4544369,6.34871 3.1894404,5.8425813 3.018173,6.3946946 2.9111692,6.9738617 2.9541363,7.5536254 3.0251487,7.4780859 3.0961587,7.4025463 3.1671686,7.3270297 3.210932,7.7306569 3.5076174,8.0547518 3.8487157,8.2481441 4.2154106,8.4697145 4.6368556,8.6024135 4.9665311,8.8827497 5.0675274,8.9663434 5.0899483,9.1360779 5.2401555,9.1568903 5.3638962,9.1876385 5.4871069,9.1042742 5.5285504,8.9923876 5.6758342,8.77302 5.9336002,8.6689352 6.1579449,8.546149 6.558449,8.3439683 7.0105734,8.1629441 7.2590462,7.7657878 7.3472866,7.6408676 7.3925713,7.4561491 7.4223649,7.3336154 7.4913096,7.406952 7.5602521,7.4802887 7.6291968,7.5536254 7.6721663,6.9738617 7.5651626,6.3946946 7.3938953,5.8425813 7.1288988,6.34871 6.8639,6.8548158 6.5989012,7.3609216 6.7089682,7.3173464 6.8190352,7.2737711 6.9290999,7.2301959 6.9168144,7.4798528 6.728156,7.6694589 6.5254544,7.7910518 6.2545561,7.9625073 5.9565673,8.0857983 5.6518827,8.1830222 5.6172818,8.0962619 5.5584541,8.0156053 5.5395578,7.9247834 5.5395578,6.4705783 5.5395578,5.0163962 5.5395578,3.5621911 5.8256237,3.561365 6.1117698,3.5638203 6.3977852,3.560952 6.5994887,3.5510392 6.6979976,3.2723093 6.5484582,3.1372009 6.4354885,3.0263929 6.2693634,3.0754521 6.1279195,3.0644837 5.9318012,3.0644837 5.7356784,3.0644837 5.5395578,3.0644837 5.5395578,2.9918586 5.5395578,2.9192332 5.5395578,2.8466079 5.8943848,2.7361898 6.1301614,2.3326085 6.023401,1.9714093 5.9391303,1.6494025 5.6214856,1.4158999 5.2906995,1.4202597 Z M 5.2906995,1.9179901 C 5.457747,1.9056908 5.6057444,2.0890325 5.5308427,2.247202 5.4408929,2.4597086 5.0701226,2.4287081 5.036871,2.1916259 5.0098769,2.043025 5.1461509,1.9119093 5.2906995,1.9179901 Z" />
</g>
</marker>
<marker
refX="10"
refY="5"

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 3.8 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 7.0 KiB

Wyświetl plik

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Selection to anchor line</name>
<id>org.{{ id_inkstitch }}.selection_to_anchor_line</id>
<param name="extension" type="string" gui-hidden="true">selection_to_anchor_line</param>
<effect>
<object-type>all</object-type>
<icon>{{ icon_path }}inx/anchor_line.svg</icon>
<menu-tip>Marks selected elements as anchor lines</menu-tip>
<effects-menu>
<submenu name="{{ menu_inkstitch }}" translatable="no">
<submenu name="Edit" />
</submenu>
</effects-menu>
</effect>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>