Merge pull request #2284 from inkstitch/kaalleen/various-fixes

pull/2194/head
Kaalleen 2023-05-16 16:58:17 +02:00 zatwierdzone przez GitHub
commit 1193b4f206
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
11 zmienionych plików z 181 dodań i 120 usunięć

Wyświetl plik

@ -352,14 +352,14 @@ class FillStitch(EmbroideryElement):
'Also used for meander and circular fill.'),
unit='mm',
type='float',
default=1.5,
default=2.5,
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'meander_fill'),
('fill_method', 'circular_fill')],
sort_index=31)
def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
return max(self.get_float_param("running_stitch_length_mm", 2.5), 0.01)
@property
@param('running_stitch_tolerance_mm',
@ -607,13 +607,13 @@ class FillStitch(EmbroideryElement):
def validation_errors(self):
if not self.shape_is_valid(self.shape):
why = explain_validity(self.shape)
message, x, y = re.findall(r".+?(?=\[)|-?\d+(?:\.\d+)?", why)
message, x, y = re.match(r"(?P<message>.+)\[(?P<x>.+)\s(?P<y>.+)\]", why).groups()
yield InvalidShapeError((x, y))
def validation_warnings(self): # noqa: C901
if not self.shape_is_valid(self.original_shape):
why = explain_validity(self.original_shape)
message, x, y = re.findall(r".+?(?=\[)|-?\d+(?:\.\d+)?", why)
message, x, y = re.match(r"(?P<message>.+)\[(?P<x>.+)\s(?P<y>.+)\]", why).groups()
if "Hole lies outside shell" in message:
yield UnconnectedWarning((x, y))
else:

Wyświetl plik

@ -1,79 +0,0 @@
from math import pi
from inkex import DirectedLineSegment, Transform
from shapely import geometry as shgeo
from shapely.affinity import affine_transform, rotate
from shapely.ops import split
from ..svg import PIXELS_PER_MM, get_correction_transform
def gradient_shapes_and_attributes(element, shape):
# e.g. url(#linearGradient872) -> linearGradient872
color = element.color[5:-1]
xpath = f'.//svg:defs/svg:linearGradient[@id="{color}"]'
gradient = element.node.getroottree().getroot().findone(xpath)
gradient.apply_transform()
point1 = (float(gradient.get('x1')), float(gradient.get('y1')))
point2 = (float(gradient.get('x2')), float(gradient.get('y2')))
# get 90° angle to calculate the splitting angle
line = DirectedLineSegment(point1, point2)
angle = line.angle - (pi / 2)
# Ink/Stitch somehow turns the stitch angle
stitch_angle = angle * -1
# create bbox polygon to calculate the length necessary to make sure that
# the gradient splitter lines will cut the entire design
bbox = element.node.bounding_box()
bbox_polygon = shgeo.Polygon([(bbox.left, bbox.top), (bbox.right, bbox.top),
(bbox.right, bbox.bottom), (bbox.left, bbox.bottom)])
# gradient stops
offsets = gradient.stop_offsets
stop_styles = gradient.stop_styles
# now split the shape according to the gradient stops
polygons = []
colors = []
attributes = []
previous_color = None
end_row_spacing = None
for i, offset in enumerate(offsets):
shape_rest = []
split_point = shgeo.Point(line.point_at_ratio(float(offset)))
length = split_point.hausdorff_distance(bbox_polygon)
split_line = shgeo.LineString([(split_point.x - length - 2, split_point.y),
(split_point.x + length + 2, split_point.y)])
split_line = rotate(split_line, angle, origin=split_point, use_radians=True)
transform = -Transform(get_correction_transform(element.node))
transform = list(transform.to_hexad())
split_line = affine_transform(split_line, transform)
offset_line = split_line.parallel_offset(1, 'right')
polygon = split(shape, split_line)
color = stop_styles[i]['stop-color']
# does this gradient line split the shape
offset_outside_shape = len(polygon.geoms) == 1
for poly in polygon.geoms:
if isinstance(poly, shgeo.Polygon) and element.shape_is_valid(poly):
if poly.intersects(offset_line):
if previous_color:
polygons.append(poly)
colors.append(previous_color)
attributes.append({'angle': stitch_angle, 'end_row_spacing': end_row_spacing, 'color': previous_color})
polygons.append(poly)
attributes.append({'angle': stitch_angle + pi, 'end_row_spacing': end_row_spacing, 'color': color})
else:
shape_rest.append(poly)
shape = shgeo.MultiPolygon(shape_rest)
previous_color = color
end_row_spacing = element.row_spacing / PIXELS_PER_MM * 2
# add left over shape(s)
if shape:
if offset_outside_shape:
for s in shape.geoms:
polygons.append(s)
attributes.append({'color': stop_styles[-2]['stop-color'], 'angle': stitch_angle, 'end_row_spacing': end_row_spacing})
stitch_angle += pi
else:
end_row_spacing = None
for s in shape.geoms:
polygons.append(s)
attributes.append({'color': stop_styles[-1]['stop-color'], 'angle': stitch_angle, 'end_row_spacing': end_row_spacing})
return polygons, attributes

Wyświetl plik

@ -101,10 +101,10 @@ class Stroke(EmbroideryElement):
unit='mm',
type='float',
select_items=[('stroke_method', 'running_stitch'), ('stroke_method', 'ripple_stitch')],
default=1.5,
default=2.5,
sort_index=4)
def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
return max(self.get_float_param("running_stitch_length_mm", 2.5), 0.01)
@property
@param('running_stitch_tolerance_mm',
@ -261,6 +261,27 @@ class Stroke(EmbroideryElement):
def reverse(self):
return self.get_boolean_param("reverse", False)
_reverse_rails_options = [ParamOption('automatic', _('Automatic')),
ParamOption('none', _("Don't reverse")),
ParamOption('first', _('Reverse first rail')),
ParamOption('second', _('Reverse second rail')),
ParamOption('both', _('Reverse both rails'))
]
@property
@param(
'reverse_rails',
_('Reverse rails'),
tooltip=_('Reverse satin ripple rails. ' +
'Default: automatically detect and fix a reversed rail.'),
type='combo',
options=_reverse_rails_options,
default='automatic',
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=15)
def reverse_rails(self):
return self.get_param('reverse_rails', 'automatic')
@property
@param('grid_size_mm',
_('Grid size'),
@ -269,7 +290,7 @@ class Stroke(EmbroideryElement):
default=0,
unit='mm',
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=15)
sort_index=16)
@cache
def grid_size(self):
return abs(self.get_float_param("grid_size_mm", 0))
@ -283,7 +304,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=16)
sort_index=17)
def scale_axis(self):
return self.get_int_param('scale_axis', 0)
@ -295,7 +316,7 @@ class Stroke(EmbroideryElement):
unit='%',
default=100,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=17)
sort_index=18)
def scale_start(self):
return self.get_float_param('scale_start', 100.0)
@ -307,7 +328,7 @@ class Stroke(EmbroideryElement):
unit='%',
default=0.0,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=18)
sort_index=19)
def scale_end(self):
return self.get_float_param('scale_end', 0.0)
@ -318,7 +339,7 @@ class Stroke(EmbroideryElement):
type='boolean',
default=True,
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=19)
sort_index=20)
@cache
def rotate_ripples(self):
return self.get_boolean_param("rotate_ripples", True)
@ -331,7 +352,7 @@ class Stroke(EmbroideryElement):
default=0,
options=(_("flat"), _("point")),
select_items=[('stroke_method', 'ripple_stitch')],
sort_index=20)
sort_index=21)
@cache
def join_style(self):
return self.get_int_param('join_style', 0)

Wyświetl plik

@ -99,6 +99,7 @@ class ConvertToSatin(InkstitchExtension):
path[0] = start.as_tuple()
def remove_duplicate_points(self, path):
path = [[round(coord, 4) for coord in point] for point in path]
return [point for point, repeats in groupby(path)]
def join_style_args(self, element):

Wyświetl plik

@ -3,17 +3,18 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from math import degrees
from math import degrees, pi
from inkex import DirectedLineSegment, PathElement, errormsg
from inkex import DirectedLineSegment, PathElement, Transform, errormsg
from shapely import geometry as shgeo
from shapely.affinity import affine_transform, rotate
from shapely.geometry import Point
from shapely.ops import nearest_points
from shapely.ops import nearest_points, split
from ..commands import add_commands
from ..elements import FillStitch
from ..elements.gradient_fill import gradient_shapes_and_attributes
from ..i18n import _
from ..svg import get_correction_transform
from ..svg import PIXELS_PER_MM, get_correction_transform
from ..svg.tags import INKSTITCH_ATTRIBS
from .commands import CommandsExtension
from .duplicate_params import get_inkstitch_attributes
@ -28,6 +29,9 @@ class GradientBlocks(CommandsExtension):
def __init__(self, *args, **kwargs):
CommandsExtension.__init__(self, *args, **kwargs)
self.arg_parser.add_argument("--notebook", type=str, default=0.0)
self.arg_parser.add_argument("--options", type=str, default=0.0)
self.arg_parser.add_argument("--info", type=str, default=0.0)
self.arg_parser.add_argument("-e", "--end-row-spacing", type=float, default=0.0, dest="end_row_spacing")
def effect(self):
@ -53,35 +57,45 @@ class GradientBlocks(CommandsExtension):
fill_shapes.reverse()
attributes.reverse()
if self.options.end_row_spacing != 0:
end_row_spacing = self.options.end_row_spacing
else:
end_row_spacing = element.row_spacing / PIXELS_PER_MM * 2
end_row_spacing = f'{end_row_spacing: .2f}'
previous_color = None
previous_element = None
for i, shape in enumerate(fill_shapes):
color = attributes[i]['color']
style['fill'] = color
end_row_spacing = attributes[i]['end_row_spacing'] or None
is_gradient = attributes[i]['is_gradient']
angle = degrees(attributes[i]['angle'])
angle = f'{angle: .2f}'
d = "M " + " ".join([f'{x}, {y}' for x, y in list(shape.exterior.coords)]) + " Z"
block = PathElement(attrib={
"id": self.uniqueId("path"),
"style": str(style),
"transform": correction_transform,
"d": d,
INKSTITCH_ATTRIBS['angle']: f'{angle: .2f}'
INKSTITCH_ATTRIBS['angle']: angle
})
# apply parameters from original element
params = get_inkstitch_attributes(element.node)
for attrib in params:
block.attrib[attrib] = str(element.node.attrib[attrib])
# set end_row_spacing
if end_row_spacing:
if self.options.end_row_spacing != 0:
end_row_spacing = self.options.end_row_spacing
block.set('inkstitch:end_row_spacing_mm', f'{end_row_spacing: .2f}')
else:
block.pop('inkstitch:end_row_spacing_mm')
# disable underlay and underpath
block.set('inkstitch:fill_underlay', False)
block.set('inkstitch:underpath', False)
# set end_row_spacing
if is_gradient:
block.set('inkstitch:end_row_spacing_mm', end_row_spacing)
else:
block.pop('inkstitch:end_row_spacing_mm')
# use underlay to compensate for higher density in the gradient parts
block.set('inkstitch:fill_underlay', True)
block.set('inkstitch:fill_underlay_angle', angle)
block.set('inkstitch:fill_underlay_row_spacing_mm', end_row_spacing)
parent.insert(index, block)
if previous_color == color:
@ -106,6 +120,77 @@ class GradientBlocks(CommandsExtension):
return Point(pos)
def gradient_shapes_and_attributes(element, shape):
# e.g. url(#linearGradient872) -> linearGradient872
color = element.color[5:-1]
xpath = f'.//svg:defs/svg:linearGradient[@id="{color}"]'
gradient = element.node.getroottree().getroot().findone(xpath)
gradient.apply_transform()
point1 = (float(gradient.get('x1')), float(gradient.get('y1')))
point2 = (float(gradient.get('x2')), float(gradient.get('y2')))
# get 90° angle to calculate the splitting angle
line = DirectedLineSegment(point1, point2)
angle = line.angle - (pi / 2)
# Ink/Stitch somehow turns the stitch angle
stitch_angle = angle * -1
# create bbox polygon to calculate the length necessary to make sure that
# the gradient splitter lines will cut the entire design
bbox = element.node.bounding_box()
bbox_polygon = shgeo.Polygon([(bbox.left, bbox.top), (bbox.right, bbox.top),
(bbox.right, bbox.bottom), (bbox.left, bbox.bottom)])
# gradient stops
offsets = gradient.stop_offsets
stop_styles = gradient.stop_styles
# now split the shape according to the gradient stops
polygons = []
colors = []
attributes = []
previous_color = None
is_gradient = False
for i, offset in enumerate(offsets):
shape_rest = []
split_point = shgeo.Point(line.point_at_ratio(float(offset)))
length = split_point.hausdorff_distance(bbox_polygon)
split_line = shgeo.LineString([(split_point.x - length - 2, split_point.y),
(split_point.x + length + 2, split_point.y)])
split_line = rotate(split_line, angle, origin=split_point, use_radians=True)
transform = -Transform(get_correction_transform(element.node))
transform = list(transform.to_hexad())
split_line = affine_transform(split_line, transform)
offset_line = split_line.parallel_offset(1, 'right')
polygon = split(shape, split_line)
color = stop_styles[i]['stop-color']
# does this gradient line split the shape
offset_outside_shape = len(polygon.geoms) == 1
for poly in polygon.geoms:
if isinstance(poly, shgeo.Polygon) and element.shape_is_valid(poly):
if poly.intersects(offset_line):
if previous_color:
polygons.append(poly)
colors.append(previous_color)
attributes.append({'color': previous_color, 'angle': stitch_angle, 'is_gradient': is_gradient})
polygons.append(poly)
attributes.append({'color': color, 'angle': stitch_angle + pi, 'is_gradient': is_gradient})
else:
shape_rest.append(poly)
shape = shgeo.MultiPolygon(shape_rest)
previous_color = color
is_gradient = True
# add left over shape(s)
if shape:
if offset_outside_shape:
for s in shape.geoms:
polygons.append(s)
attributes.append({'color': stop_styles[-2]['stop-color'], 'angle': stitch_angle, 'is_gradient': is_gradient})
stitch_angle += pi
else:
is_gradient = False
for s in shape.geoms:
polygons.append(s)
attributes.append({'color': stop_styles[-1]['stop-color'], 'angle': stitch_angle, 'is_gradient': is_gradient})
return polygons, attributes
if __name__ == '__main__':
e = GradientBlocks()
e.effect()

Wyświetl plik

@ -5,7 +5,7 @@
import inkex
from ..elements import SatinColumn, Stroke
from ..elements import EmptyDObject, SatinColumn, Stroke
from ..i18n import _
from ..svg.tags import ORIGINAL_D, PATH_EFFECT, SODIPODI_NODETYPES
from .base import InkstitchExtension
@ -35,7 +35,10 @@ class StrokeToLpeSatin(InkstitchExtension):
if not any((isinstance(item, Stroke) or isinstance(item, SatinColumn)) for item in self.elements):
# L10N: Convert To Satin extension, user selected one or more objects that were not lines.
inkex.errormsg(_("Please select at least one stroke to convert to a satin column."))
if any(isinstance(item, EmptyDObject) for item in self.elements):
inkex.errormsg(_("This element has lost its path information. Please move the element slightly back and forth before you try again."))
else:
inkex.errormsg(_("Please select at least one stroke to convert to a satin column."))
return
pattern = self.options.pattern

Wyświetl plik

@ -14,8 +14,8 @@ class UpdateSvg(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
# TODO: When there are more legacy versions than only one, this can be transformed in a user input
# inkstitch_svg_version history: 1 -> v2.3.0
# TODO: When there are more legacy versions than only one, this can be transformed into a user input
# inkstitch_svg_version history: 1 -> v3.0.0, May 2023
self.update_from = 0
def effect(self):

Wyświetl plik

@ -182,7 +182,10 @@ class Font(object):
return self.name + '*'
def is_custom_font(self):
return get_custom_font_dir() in self.path
custom_dir = get_custom_font_dir()
if not custom_dir:
return False
return custom_dir in self.path
def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim_option=0, use_trim_symbols=False):

Wyświetl plik

@ -103,9 +103,7 @@ def _get_satin_line_count(stroke, pairs):
if shortest_line_len == 0 or length < shortest_line_len:
shortest_line_len = length
num_lines = ceil(shortest_line_len / stroke.min_line_dist)
if stroke.join_style == 1:
num_lines += 1
return num_lines
return _line_count_adjust(stroke, num_lines)
def _get_target_line_count(stroke, target, outline):
@ -117,7 +115,19 @@ def _get_guided_line_count(stroke, guide_line):
num_lines = stroke.line_count
else:
num_lines = ceil(guide_line.length / stroke.min_line_dist)
return _line_count_adjust(stroke, num_lines)
def _line_count_adjust(stroke, num_lines):
if stroke.min_line_dist and stroke.line_count % 2 != num_lines % 2:
# We want the line count always to be either even or odd - depending on the line count value.
# So that the end point stays the same even if the design is resized. This is necessary to enable
# the user to carefully plan the output and and connect the end point to the following object
num_lines -= 1
# ensure minimum line count
num_lines = max(1, num_lines)
if stroke.is_closed or stroke.join_style == 1:
# for flat join styles we need to add an other line
num_lines += 1
return num_lines

Wyświetl plik

@ -104,7 +104,7 @@ def _update_to_one(element): # noqa: C901
element.remove_param('e_stitch')
element.set_param('satin_method', 'e_stitch')
if element.get_boolean_param('satin_column', False):
if element.get_boolean_param('satin_column', False) or element.get_int_param('stroke_method', 0) == 1:
# reverse_rails defaults to Automatic, but we should never reverse an
# old satin automatically, only new ones
element.set_param('reverse_rails', 'none')
@ -112,6 +112,9 @@ def _update_to_one(element): # noqa: C901
# default setting for fill_underlay has changed
if legacy_attribs and not element.get_param('fill_underlay', ""):
element.set_param('fill_underlay', False)
# default setting for running stitch length has changed (fills and strokes, not satins)
if not element.get_boolean_param('satin_column', False) and element.get_float_param('running_stitch_length_mm', None) is None:
element.set_param('running_stitch_length_mm', 1.5)
# convert legacy stroke_method
if element.get_style("stroke") and not element.node.get('inkscape:connection-start', None):

Wyświetl plik

@ -11,12 +11,26 @@
</submenu>
</effects-menu>
</effect>
<param name="end-row-spacing"
gui-text="End row spacing"
gui-description="Set to zero to use twice the row spacing value"
type="float"
min="0" max="100"
indents="1">0.5</param>
<param name="notebook" type="notebook">
<page name="options" gui-text="Options">
<param name="end-row-spacing"
gui-text="End row spacing"
gui-description="Set to zero to use twice the row spacing value"
type="float"
min="0" max="100"
precision="2"
indents="1">0</param>
</page>
<page name="info" gui-text="Help">
<label appearance="header">Converts a fill with a linear color gradient into color blocks with variable row spacing.</label>
<spacer />
<label>This may add density at the center.</label>
<spacer />
<label>If necessary adapt the end row spacing value after the conversion with the params dialog.</label>
<spacer />
<label appearance="url">https://inkstitch.org/docs/fill-tools/#convert-to-gradient-blocks</label>
</page>
</param>
<script>
{{ command_tag | safe }}
</script>