Add linear gradient fill (#2587)

pull/2635/head
Kaalleen 2023-11-22 20:55:58 +01:00 zatwierdzone przez GitHub
rodzic 0b12922d3f
commit 3bd92265b2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 464 dodań i 41 usunięć

Wyświetl plik

@ -526,6 +526,14 @@ class EmbroideryElement(object):
def _get_guides_cache_key_data(self):
return get_marker_elements_cache_key_data(self.node, "guide-line")
def _get_gradient_cache_key_data(self):
gradient = {}
if hasattr(self, 'gradient') and self.gradient is not None:
gradient['stops'] = self.gradient.stop_offsets
gradient['orientation'] = [self.gradient.x1(), self.gradient.x2(), self.gradient.y1(), self.gradient.y2()]
gradient['styles'] = [(style['stop-color'], style['stop-opacity']) for style in self.gradient.stop_styles]
return gradient
def get_cache_key_data(self, previous_stitch):
return []
@ -535,6 +543,7 @@ class EmbroideryElement(object):
cache_key_generator.update(self.get_params_and_values())
cache_key_generator.update(self.parse_path())
cache_key_generator.update(list(self._get_specified_style().items()))
cache_key_generator.update(self._get_gradient_cache_key_data())
cache_key_generator.update(previous_stitch)
cache_key_generator.update([(c.command, c.target_point) for c in self.commands])
cache_key_generator.update(self._get_patterns_cache_key_data())

Wyświetl plik

@ -11,6 +11,7 @@ import numpy as np
from inkex import Transform
from shapely import geometry as shgeo
from shapely.errors import GEOSException
from shapely.ops import nearest_points
from shapely.validation import explain_validity, make_valid
from .. import tiles
@ -18,8 +19,8 @@ from ..i18n import _
from ..marker import get_marker_elements
from ..stitch_plan import StitchGroup
from ..stitches import (auto_fill, circular_fill, contour_fill, guided_fill,
legacy_fill)
from ..stitches.meander_fill import meander_fill
legacy_fill, linear_gradient_fill, meander_fill)
from ..stitches.linear_gradient_fill import gradient_angle
from ..svg import PIXELS_PER_MM, get_node_transform
from ..svg.clip import get_clip_path
from ..svg.tags import INKSCAPE_LABEL
@ -114,6 +115,7 @@ class FillStitch(EmbroideryElement):
ParamOption('guided_fill', _("Guided Fill")),
ParamOption('meander_fill', _("Meander Fill")),
ParamOption('circular_fill', _("Circular Fill")),
ParamOption('linear_gradient_fill', _("Linear Gradient Fill")),
ParamOption('legacy_fill', _("Legacy Fill"))]
@property
@ -226,7 +228,8 @@ class FillStitch(EmbroideryElement):
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'meander_fill'),
('fill_method', 'circular_fill')])
('fill_method', 'circular_fill'),
('fill_method', 'linear_gradient_fill')])
def expand(self):
return self.get_float_param('expand_mm', 0)
@ -254,6 +257,7 @@ class FillStitch(EmbroideryElement):
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'contour_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'linear_gradient_fill'),
('fill_method', 'legacy_fill')],
default=3.0)
def max_stitch_length(self):
@ -270,6 +274,7 @@ class FillStitch(EmbroideryElement):
('fill_method', 'contour_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'circular_fill'),
('fill_method', 'linear_gradient_fill'),
('fill_method', 'legacy_fill')],
default=0.25)
def row_spacing(self):
@ -297,7 +302,10 @@ class FillStitch(EmbroideryElement):
'Fractional values are allowed and can have less visible diagonals than integer values.'),
type='int',
sort_index=25,
select_items=[('fill_method', 'auto_fill'), ('fill_method', 'guided_fill'), ('fill_method', 'legacy_fill')],
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'linear_gradient_fill'),
('fill_method', 'legacy_fill')],
default=4)
def staggers(self):
return self.get_float_param("staggers", 4)
@ -310,7 +318,9 @@ class FillStitch(EmbroideryElement):
'Skipping it decreases stitch count and density.'),
type='boolean',
sort_index=26,
select_items=[('fill_method', 'auto_fill'), ('fill_method', 'guided_fill'),
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'linear_gradient_fill'),
('fill_method', 'legacy_fill')],
default=False)
def skip_last(self):
@ -329,6 +339,20 @@ class FillStitch(EmbroideryElement):
def flip(self):
return self.get_boolean_param("flip", False)
@property
@param(
'stop_at_ending_point',
_('Stop at ending point'),
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,
select_items=[('fill_method', 'linear_gradient_fill')],
default=False
)
def stop_at_ending_point(self):
return self.get_boolean_param("stop_at_ending_point", False)
@property
@param('underpath',
_('Underpath'),
@ -353,7 +377,8 @@ class FillStitch(EmbroideryElement):
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'meander_fill'),
('fill_method', 'circular_fill')],
('fill_method', 'circular_fill'),
('fill_method', 'linear_gradient_fill')],
sort_index=31)
def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 2.5), 0.01)
@ -402,6 +427,12 @@ class FillStitch(EmbroideryElement):
# SVG spec says the default fill is black
return self.get_style("fill", "#000000")
@property
def gradient(self):
color = self.color[5:-1]
xpath = f'.//svg:defs/svg:linearGradient[@id="{color}"]'
return self.node.getroottree().getroot().findone(xpath)
@property
@param('fill_underlay', _('Underlay'), type='toggle', group=_('Fill Underlay'), default=True)
def fill_underlay(self):
@ -427,6 +458,8 @@ class FillStitch(EmbroideryElement):
float(angle)) for angle in underlay_angles]
except (TypeError, ValueError):
return default_value
elif self.fill_method == 'linear_gradient_fill' and self.gradient is not None:
return [-gradient_angle(self.node, self.gradient)]
else:
underlay_angles = default_value
@ -704,10 +737,25 @@ class FillStitch(EmbroideryElement):
return self.do_legacy_fill()
else:
stitch_groups = []
end = self.get_ending_point()
for shape in self.shape.geoms:
# start and end points
start = self.get_starting_point(previous_stitch_group)
final_end = self.get_ending_point()
# sort shapes to get a nicer routing
shapes = list(self.shape.geoms)
if start:
shapes.sort(key=lambda shape: shape.distance(shgeo.Point(start)))
else:
shapes.sort(key=lambda shape: shape.bounds[0])
for i, shape in enumerate(shapes):
start = self.get_starting_point(previous_stitch_group)
if i < len(shapes) - 1:
end = nearest_points(shape, shapes[i+1])[0].coords
else:
end = final_end
if self.fill_underlay:
underlay_shapes = self.underlay_shape(shape)
for underlay_shape in underlay_shapes.geoms:
@ -724,10 +772,18 @@ class FillStitch(EmbroideryElement):
stitch_groups.extend(self.do_meander_fill(fill_shape, shape, i, start, end))
elif self.fill_method == 'circular_fill':
stitch_groups.extend(self.do_circular_fill(fill_shape, previous_stitch_group, start, end))
elif self.fill_method == 'linear_gradient_fill':
stitch_groups.extend(self.do_linear_gradient_fill(fill_shape, previous_stitch_group, start, end))
else:
# auto_fill
stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
previous_stitch_group = stitch_groups[-1]
if stitch_groups:
previous_stitch_group = stitch_groups[-1]
# sort colors of linear gradient (if multiple shapes)
if self.fill_method == 'linear_gradient_fill':
colors = [stitch_group.color for stitch_group in stitch_groups]
stitch_groups.sort(key=lambda group: colors.index(group.color))
return stitch_groups
@ -746,10 +802,13 @@ class FillStitch(EmbroideryElement):
lock_stitches=self.lock_stitches) for stitch_list in stitch_lists]
def do_underlay(self, shape, starting_point):
color = self.color
if self.gradient is not None and self.fill_method == 'linear_gradient_fill':
color = [style['stop-color'] for style in self.gradient.stop_styles][0]
stitch_groups = []
for i in range(len(self.fill_underlay_angle)):
underlay = StitchGroup(
color=self.color,
color=color,
tags=("auto_fill", "auto_fill_underlay"),
lock_stitches=self.lock_stitches,
stitches=auto_fill(
@ -763,7 +822,9 @@ class FillStitch(EmbroideryElement):
self.staggers,
self.fill_underlay_skip_last,
starting_point,
underpath=self.underlay_underpath))
underpath=self.underlay_underpath
)
)
stitch_groups.append(underlay)
starting_point = underlay.stitches[-1]
return [stitch_groups, starting_point]
@ -859,15 +920,9 @@ class FillStitch(EmbroideryElement):
starting_point,
ending_point,
self.underpath,
self.guided_fill_strategy,
))
return [stitch_group]
def do_meander_fill(self, shape, original_shape, i, starting_point, ending_point):
stitch_group = StitchGroup(
color=self.color,
tags=("meander_fill", "meander_fill_top"),
stitches=meander_fill(self, shape, original_shape, i, starting_point, ending_point))
self.guided_fill_strategy
)
)
return [stitch_group]
@cache
@ -882,6 +937,16 @@ class FillStitch(EmbroideryElement):
else:
return guide_lines['stroke'][0]
def do_meander_fill(self, shape, original_shape, i, starting_point, ending_point):
stitch_group = StitchGroup(
color=self.color,
tags=("meander_fill", "meander_fill_top"),
stitches=meander_fill(self, shape, original_shape, i, starting_point, ending_point),
force_lock_stitches=self.force_lock_stitches,
lock_stitches=self.lock_stitches,
)
return [stitch_group]
def do_circular_fill(self, shape, last_patch, starting_point, ending_point):
# get target position
command = self.get_command('ripple_target')
@ -893,24 +958,29 @@ class FillStitch(EmbroideryElement):
else:
target = shape.centroid
stitches = circular_fill(
shape,
self.angle,
self.row_spacing,
self.end_row_spacing,
self.staggers,
self.running_stitch_length,
self.running_stitch_tolerance,
self.bean_stitch_repeats,
self.repeats,
self.skip_last,
starting_point,
ending_point,
self.underpath,
target
)
shape,
self.angle,
self.row_spacing,
self.end_row_spacing,
self.staggers,
self.running_stitch_length,
self.running_stitch_tolerance,
self.bean_stitch_repeats,
self.repeats,
self.skip_last,
starting_point,
ending_point,
self.underpath,
target
)
stitch_group = StitchGroup(
color=self.color,
tags=("circular_fill", "auto_fill_top"),
stitches=stitches)
stitches=stitches,
force_lock_stitches=self.force_lock_stitches,
lock_stitches=self.lock_stitches,)
return [stitch_group]
def do_linear_gradient_fill(self, shape, last_patch, start, end):
return linear_gradient_fill(self, shape, start, end)

Wyświetl plik

@ -7,6 +7,8 @@ from .auto_fill import auto_fill
from .circular_fill import circular_fill
from .fill import legacy_fill
from .guided_fill import guided_fill
from .linear_gradient_fill import linear_gradient_fill
from .meander_fill import meander_fill
# Can't put this here because we get a circular import :(
# from .auto_satin import auto_satin

Wyświetl plik

@ -78,7 +78,7 @@ def auto_fill(shape,
segments = [segment for row in rows for segment in row]
fill_stitch_graph = build_fill_stitch_graph(shape, segments, starting_point, ending_point)
if not graph_is_valid(fill_stitch_graph, shape, max_stitch_length):
if not graph_is_valid(fill_stitch_graph):
return fallback(shape, running_stitch_length, running_stitch_tolerance)
travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath)
@ -269,7 +269,7 @@ def add_edges_between_outline_nodes(graph, duplicate_every_other=False):
check_stop_flag()
def graph_is_valid(graph, shape, max_stitch_length):
def graph_is_valid(graph):
# The graph may be empty if the shape is so small that it fits between the
# rows of stitching. Certain small weird shapes can also cause a non-
# eulerian graph.

Wyświetl plik

@ -73,7 +73,7 @@ def circular_fill(shape,
segments.append([(point.x, point.y) for point in coords])
fill_stitch_graph = build_fill_stitch_graph(shape, segments, starting_point, ending_point)
if not graph_is_valid(fill_stitch_graph, shape, running_stitch_length):
if not graph_is_valid(fill_stitch_graph):
return fallback(shape, running_stitch_length, running_stitch_tolerance)
travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath)
@ -124,7 +124,7 @@ def path_to_stitches(shape, path, travel_graph, fill_stitch_graph, running_stitc
# If the very first stitch is travel, we'll omit it in travel(), so add it here.
if not path[0].is_segment():
stitches.append(Stitch(*path[0].nodes[0]))
stitches.append(Stitch(*path[0].nodes[0], tags={'auto_fill_travel'}))
for edge in path:
if edge.is_segment():

Wyświetl plik

@ -39,7 +39,7 @@ def guided_fill(shape,
fill_stitch_graph = build_fill_stitch_graph(shape, segments, starting_point, ending_point)
if not graph_is_valid(fill_stitch_graph, shape, max_stitch_length):
if not graph_is_valid(fill_stitch_graph):
return fallback(shape, guideline, row_spacing, max_stitch_length, running_stitch_length, running_stitch_tolerance,
num_staggers, skip_last, starting_point, ending_point, underpath)

Wyświetl plik

@ -0,0 +1,341 @@
# Authors: see git history
#
# Copyright (c) 2023 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from math import ceil, floor, sqrt
import numpy as np
from inkex import DirectedLineSegment, Transform
from networkx import eulerize
from shapely import segmentize
from shapely.affinity import rotate
from shapely.geometry import LineString, MultiLineString, Point, Polygon
from ..stitch_plan import StitchGroup
from ..svg import get_node_transform
from ..utils.threading import check_stop_flag
from .auto_fill import (build_fill_stitch_graph, build_travel_graph,
find_stitch_path, graph_is_valid)
from .circular_fill import path_to_stitches
from .guided_fill import apply_stitches
def linear_gradient_fill(fill, shape, starting_point, ending_point):
lines, colors, stop_color_line_indices = _get_lines_and_colors(shape, fill)
color_lines, colors = _get_color_lines(lines, colors, stop_color_line_indices)
if fill.gradient is None:
colors.pop()
stitch_groups = _get_stitch_groups(fill, shape, colors, color_lines, starting_point, ending_point)
return stitch_groups
def _get_lines_and_colors(shape, fill):
'''
Returns lines and color gradient information
lines: a list of lines which cover the whole shape in a 90° angle to the gradient line
colors: a list of color values
stop_color_line_indices: line indices indicating where color changes are positioned at
'''
orig_bbox = shape.bounds
# get angle, colors, as well as start and stop position of the gradient
angle, colors, offsets, gradient_start, gradient_end = _get_gradient_info(fill, orig_bbox)
# get lines
lines, bottom_line = _get_lines(fill, shape, orig_bbox, angle)
gradient_start_line_index = round(bottom_line.project(Point(gradient_start)) / fill.row_spacing)
if gradient_start_line_index == 0:
gradient_start_line_index = -round(LineString([gradient_start, gradient_end]).project(Point(bottom_line.coords[0])) / fill.row_spacing)
stop_color_line_indices = [gradient_start_line_index]
gradient_line = LineString([gradient_start, gradient_end])
for offset in offsets[1:]:
stop_color_line_indices.append(round((gradient_line.length * offset) / fill.row_spacing) + gradient_start_line_index)
return lines, colors, stop_color_line_indices
def _get_gradient_info(fill, bbox):
if fill.gradient is None:
# there is no linear gradient, let's simply space out one single color instead
angle = fill.angle
offsets = [0, 1]
colors = [fill.color, 'none']
gradient_start = (bbox[0], bbox[1])
gradient_end = (bbox[2], bbox[3])
else:
fill.gradient.apply_transform()
offsets = fill.gradient.stop_offsets
colors = [style['stop-color'] if float(style['stop-opacity']) > 0 else 'none' for style in fill.gradient.stop_styles]
gradient_start, gradient_end = gradient_start_end(fill.node, fill.gradient)
angle = gradient_angle(fill.node, fill.gradient)
return angle, colors, offsets, gradient_start, gradient_end
def _get_lines(fill, shape, bounding_box, angle):
'''
To generate the lines we rotate the bounding box to bring the angle in vertical position.
From bounds we create a Polygon which we then rotate back, so we receive a rotated bounding box
which aligns well to the stitch angle. Combining the points of the subdivided top and bottom line
will finally deliver to our stitch rows
'''
# get the rotated bounding box for the shape
rotated_shape = rotate(shape, -angle, origin=(bounding_box[0], bounding_box[3]), use_radians=True)
bounds = rotated_shape.bounds
# Generate a Polygon from the rotated bounding box which we then rotate back into original position
# extend bounding box for lines just a little to make sure we cover the whole area with lines
# this avoids rounding errors due to the rotation later on
rot_bbox = Polygon([
(bounds[0] - fill.max_stitch_length, bounds[1] - fill.row_spacing),
(bounds[2] + fill.max_stitch_length, bounds[1] - fill.row_spacing),
(bounds[2] + fill.max_stitch_length, bounds[3] + fill.row_spacing),
(bounds[0] - fill.max_stitch_length, bounds[3] + fill.row_spacing)
])
# and rotate it back into original position
rot_bbox = list(rotate(rot_bbox, angle, origin=(bounding_box[0], bounding_box[3]), use_radians=True).exterior.coords)
# segmentize top and bottom line to finally be ableto generate the stitch lines
top_line = LineString([rot_bbox[0], rot_bbox[1]])
top = segmentize(top_line, max_segment_length=fill.row_spacing)
bottom_line = LineString([rot_bbox[3], rot_bbox[2]])
bottom = segmentize(bottom_line, max_segment_length=fill.row_spacing)
lines = list(zip(top.coords, bottom.coords))
# stagger stitched lines according to user settings
staggered_lines = []
for i, line in enumerate(lines):
staggered_line = apply_stitches(LineString(line), fill.max_stitch_length, fill.staggers, fill.row_spacing, i)
staggered_lines.append(staggered_line)
return staggered_lines, bottom_line
def _get_color_lines(lines, colors, stop_color_line_indices):
'''
To define which line will be stitched in which color, we will loop through the color sections
defined by the stop positions of the gradient (stop_color_line_indices).
Each section will then be subdivided into smaller sections using the square root of the total line number
of the whole section. Lines left over from this operation will be added step by step to the smaller sub-sections.
Since we do this symmetrically we may end one line short, which we an add at the end.
Now we define the line colors of the first half of our color section, we will later mirror this on the second half.
Therefor we use one additional line of color2 in each sub-section and position them as evenly as possible between the color1 lines.
Doing this we take care, that the number of consecutive lines of color1 is always decreasing.
For example let's take a 12 lines sub-section, with 5 lines of color2.
12 / 5 = 2.4
12 % 5 = 2
This results into the following pattern:
xx|xx|x|x|x| (while x = color1 and | = color2).
Note that the first two parts have an additional line (as defined by the modulo operation)
Method returns
color_lines: A dictionary with lines grouped by color
colors: An updated list of color values.
Colors which are positioned outside the shape will be removed.
'''
# create dictionary with a key for each color
color_lines = {}
for color in colors:
color_lines[color] = []
prev_color = colors[0]
prev = None
for line_index, color in zip(stop_color_line_indices, colors):
if prev is None:
if line_index > 0:
color_lines[color].extend(lines[0:line_index + 1])
prev = line_index
prev_color = color
continue
if prev < 0 and line_index < 0:
prev = line_index
prev_color = color
continue
prev += 1
line_index += 1
total_lines = line_index - prev
sections = floor(sqrt(total_lines))
color1 = []
color2 = []
c2_count = 0
c1_count = 0
current_line = 0
line_count_diff = floor((total_lines - sections**2) / 2)
stop = False
for i in range(sections):
if stop:
break
c2_count += 1
c1_count = sections - c2_count
rest = c1_count % c2_count
c1_count = ceil(c1_count / c2_count)
current_line, line_count_diff, color1, color2, stop = _add_lines(
current_line,
total_lines,
line_count_diff,
color1,
color2,
stop,
rest,
c1_count,
c2_count
)
# mirror the first half of the color section to receive the full section
second_half = color2[-1] * 2 + 1
color1 = np.array(color1)
color2 = np.array(color2)
c1 = np.append(color1, second_half - color2)
color2 = np.append(color2, second_half - color1)
color1 = c1
# until now we only cared about the length of the section
# now we need to move it to the correct position
color1 += prev
color2 += prev
# add lines to their color key in the dictionary
# as sections can start before or after the actual shape we need to make sure,
# that we only try to add existing lines
color_lines[prev_color].extend([lines[x] for x in color1 if 0 < x < len(lines)])
color_lines[color].extend([lines[x] for x in color2 if 0 < x < len(lines)])
prev = np.max(color2)
prev_color = color
check_stop_flag()
# add left over lines to last color
color_lines[color].extend(lines[prev+1:])
# remove transparent colors (we just want a gap)
color_lines.pop('none', None)
# remove empty line lists and update colors
color_lines = {color: lines for color, lines in color_lines.items() if lines}
colors = list(color_lines.keys())
return color_lines, colors
def _add_lines(current_line, total_lines, line_count_diff, color1, color2, stop, rest, c1_count, c2_count):
for j in range(c2_count):
if stop:
break
if rest == 0 or j < rest:
count = c1_count
else:
count = c1_count - 1
if line_count_diff > 0:
count += 1
line_count_diff -= 1
for k in range(count):
color1.append(current_line)
current_line += 1
if total_lines / 2 <= current_line + 1:
stop = True
break
color2.append(current_line)
current_line += 1
return current_line, line_count_diff, color1, color2, stop
def _get_stitch_groups(fill, shape, colors, color_lines, starting_point, ending_point):
stitch_groups = []
for i, color in enumerate(colors):
lines = color_lines[color]
multiline = MultiLineString(lines).intersection(shape)
if not isinstance(multiline, MultiLineString):
if isinstance(multiline, LineString):
multiline = MultiLineString([multiline])
else:
continue
segments = [list(line.coords) for line in multiline.geoms if len(line.coords) > 1]
fill_stitch_graph = build_fill_stitch_graph(shape, segments, starting_point, ending_point)
if not graph_is_valid(fill_stitch_graph):
# try to eulerize
fill_stitch_graph = eulerize(fill_stitch_graph)
# still not valid? continue without rendering the color section
if not graph_is_valid(fill_stitch_graph):
continue
travel_graph = build_travel_graph(fill_stitch_graph, shape, fill.angle, False)
path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point)
stitches = path_to_stitches(
shape,
path,
travel_graph,
fill_stitch_graph,
fill.running_stitch_length,
fill.running_stitch_tolerance,
fill.skip_last,
False # no underpath
)
stitches = _remove_start_end_travel(fill, stitches, colors, i)
stitch_groups.append(StitchGroup(
color=color,
tags=("linear_gradient_fill", "auto_fill_top"),
stitches=stitches,
force_lock_stitches=fill.force_lock_stitches,
lock_stitches=fill.lock_stitches,
trim_after=fill.has_command("trim") or fill.trim_after
))
return stitch_groups
def _remove_start_end_travel(fill, stitches, colors, color_section):
# We can savely remove travel stitches at start since we are changing color all the time
# but we do care for the first starting point, it is important when they use an underlay of the same color
remove_before = 0
if color_section > 0 or not fill.fill_underlay:
for stitch in range(len(stitches)-1):
if 'auto_fill_travel' not in stitches[stitch].tags:
remove_before = stitch
break
stitches = stitches[remove_before:]
remove_after = len(stitches) - 1
# We also remove travel stitches at the end. It is optional to the user if the last color block travels
# to the defined ending point
if color_section < len(colors) - 2 or not fill.stop_at_ending_point:
for stitch in range(remove_after, 0, -1):
if 'auto_fill_travel' not in stitches[stitch].tags:
remove_after = stitch + 1
break
stitches = stitches[:remove_after]
return stitches
def gradient_start_end(node, gradient):
transform = Transform(get_node_transform(node))
gradient_start = transform.apply_to_point((float(gradient.x1()), float(gradient.y1())))
gradient_end = transform.apply_to_point((float(gradient.x2()), float(gradient.y2())))
return gradient_start, gradient_end
def gradient_angle(node, gradient):
if gradient is None:
return
gradient_start, gradient_end = gradient_start_end(node, gradient)
gradient_line = DirectedLineSegment(gradient_start, gradient_end)
return gradient_line.angle

Wyświetl plik

@ -97,6 +97,7 @@ inkstitch_attribs = [
'staggers',
'underlay_underpath',
'underpath',
'stop_at_ending_point',
'flip',
'clip',
# stroke