pull/2098/head
Kaalleen 2023-02-27 16:01:37 +01:00 zatwierdzone przez GitHub
rodzic 1885deff35
commit ed4aa55a73
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
5 zmienionych plików z 215 dodań i 34 usunięć

Wyświetl plik

@ -9,22 +9,25 @@ import re
import sys
import traceback
from inkex import Transform
from shapely import geometry as shgeo
from shapely.errors import TopologicalError
from shapely.validation import explain_validity, make_valid
from .. import tiles
from ..i18n import _
from ..marker import get_marker_elements
from ..stitch_plan import StitchGroup
from ..stitches import auto_fill, contour_fill, guided_fill, legacy_fill
from ..stitches import (auto_fill, circular_fill, contour_fill, guided_fill,
legacy_fill)
from ..stitches.meander_fill import meander_fill
from ..svg import PIXELS_PER_MM
from ..svg import PIXELS_PER_MM, get_node_transform
from ..svg.tags import INKSCAPE_LABEL
from .. import tiles
from ..utils import cache, version
from ..utils.param import ParamOption
from ..utils.threading import ExitThread
from .element import EmbroideryElement, param
from .validation import ValidationError, ValidationWarning
from ..utils.threading import ExitThread
class SmallShapeWarning(ValidationWarning):
@ -107,15 +110,33 @@ class FillStitch(EmbroideryElement):
def auto_fill(self):
return self.get_boolean_param('auto_fill', True)
_fill_methods = [ParamOption('auto_fill', _("Auto Fill"), 0),
ParamOption('contour_fill', _("Contour Fill"), 1),
ParamOption('guided_fill', _("Guided Fill"), 2),
ParamOption('meander_fill', _("Meander Fill")),
ParamOption('circular_fill', _("Circular Fill")),
ParamOption('legacy_fill', _("Legacy Fill"), 3)]
@property
@param('fill_method', _('Fill method'), type='dropdown', default=0,
options=[_("Auto Fill"), _("Contour Fill"), _("Guided Fill"), _("Legacy Fill"), _("Meander Fill")], sort_index=2)
@param('fill_method',
_('Fill method'),
type='combo',
default=0,
options=_fill_methods,
sort_index=2)
def fill_method(self):
return self.get_int_param('fill_method', 0)
# convert legacy values
legacy_method = self.get_int_param('fill_method', None)
if legacy_method in range(0, 4):
method = [method.id for method in self._fill_methods if method.legacy == legacy_method][0]
self.set_param('fill_method', method)
return method
return self.get_param('fill_method', 'auto_fill')
@property
@param('guided_fill_strategy', _('Guided Fill Strategy'), type='dropdown', default=0,
options=[_("Copy"), _("Parallel Offset")], select_items=[('fill_method', 2)], sort_index=3,
options=[_("Copy"), _("Parallel Offset")], select_items=[('fill_method', 'guided_fill')], sort_index=3,
tooltip=_('Copy (the default) will fill the shape with shifted copies of the line. '
'Parallel offset will ensure that each line is always a consistent distance from its neighbor. '
'Sharp corners may be introduced.'))
@ -124,18 +145,23 @@ class FillStitch(EmbroideryElement):
@property
@param('contour_strategy', _('Contour Fill Strategy'), type='dropdown', default=0,
options=[_("Inner to Outer"), _("Single spiral"), _("Double spiral")], select_items=[('fill_method', 1)], sort_index=3)
options=[_("Inner to Outer"), _("Single spiral"), _("Double spiral")], select_items=[('fill_method', 'contour_fill')], sort_index=3)
def contour_strategy(self):
return self.get_int_param('contour_strategy', 0)
@property
@param('join_style', _('Join Style'), type='dropdown', default=0,
options=[_("Round"), _("Mitered"), _("Beveled")], select_items=[('fill_method', 1)], sort_index=4)
options=[_("Round"), _("Mitered"), _("Beveled")], select_items=[('fill_method', 'contour_fill')], sort_index=4)
def join_style(self):
return self.get_int_param('join_style', 0)
@property
@param('avoid_self_crossing', _('Avoid self-crossing'), type='boolean', default=False, select_items=[('fill_method', 1)], sort_index=5)
@param('avoid_self_crossing',
_('Avoid self-crossing'),
type='boolean',
default=False,
select_items=[('fill_method', 'contour_fill')],
sort_index=5)
def avoid_self_crossing(self):
return self.get_boolean_param('avoid_self_crossing', False)
@ -149,24 +175,29 @@ class FillStitch(EmbroideryElement):
type='integer',
unit='mm',
default=0,
select_items=[('fill_method', 1), ('fill_method', 4)],
select_items=[('fill_method', 'contour_fill'), ('fill_method', 'meander_fill')],
sort_index=5)
def smoothness(self):
return self.get_float_param('smoothness_mm', 0)
@property
@param('clockwise', _('Clockwise'), type='boolean', default=True, select_items=[('fill_method', 1)], sort_index=5)
@param('clockwise', _('Clockwise'), type='boolean', default=True, select_items=[('fill_method', 'contour_fill')], sort_index=5)
def clockwise(self):
return self.get_boolean_param('clockwise', True)
@property
@param('meander_pattern', _('Meander Pattern'), type='combo', default=0,
options=sorted(tiles.all_tiles()), select_items=[('fill_method', 4)], sort_index=3)
options=sorted(tiles.all_tiles()), select_items=[('fill_method', 'meander_fill')], sort_index=3)
def meander_pattern(self):
return self.get_param('meander_pattern', None)
@property
@param('meander_scale_percent', _('Meander pattern scale'), type='float', unit="%", default=100, select_items=[('fill_method', 4)], sort_index=4)
@param('meander_scale_percent',
_('Meander pattern scale'),
type='float', unit="%",
default=100,
select_items=[('fill_method', 'meander_fill')],
sort_index=4)
def meander_scale(self):
return self.get_split_float_param('meander_scale_percent', (100, 100)) / 100
@ -177,7 +208,7 @@ class FillStitch(EmbroideryElement):
unit='deg',
type='float',
sort_index=6,
select_items=[('fill_method', 0), ('fill_method', 3)],
select_items=[('fill_method', 'auto_fill'), ('fill_method', 'legacy_fill')],
default=0)
@cache
def angle(self):
@ -196,8 +227,8 @@ class FillStitch(EmbroideryElement):
'Skipping it decreases stitch count and density.'),
type='boolean',
sort_index=6,
select_items=[('fill_method', 0), ('fill_method', 2),
('fill_method', 3)],
select_items=[('fill_method', 'auto_fill'), ('fill_method', 'guided_fill'),
('fill_method', 'legacy_fill')],
default=False)
def skip_last(self):
return self.get_boolean_param("skip_last", False)
@ -210,7 +241,7 @@ class FillStitch(EmbroideryElement):
'When you enable flip, stitching goes from right-to-left instead of left-to-right.'),
type='boolean',
sort_index=7,
select_items=[('fill_method', 3)],
select_items=[('fill_method', 'legacy_fill')],
default=False)
def flip(self):
return self.get_boolean_param("flip", False)
@ -222,7 +253,10 @@ class FillStitch(EmbroideryElement):
unit='mm',
sort_index=6,
type='float',
select_items=[('fill_method', 0), ('fill_method', 1), ('fill_method', 2), ('fill_method', 3)],
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'contour_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'legacy_fill')],
default=0.25)
def row_spacing(self):
return max(self.get_float_param("row_spacing_mm", 0.25), 0.1 * PIXELS_PER_MM)
@ -239,7 +273,10 @@ class FillStitch(EmbroideryElement):
unit='mm',
sort_index=6,
type='float',
select_items=[('fill_method', 0), ('fill_method', 1), ('fill_method', 2), ('fill_method', 3)],
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'contour_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'legacy_fill')],
default=3.0)
def max_stitch_length(self):
return max(self.get_float_param("max_stitch_length_mm", 3.0), 0.1 * PIXELS_PER_MM)
@ -251,7 +288,7 @@ class FillStitch(EmbroideryElement):
'Fractional values are allowed and can have less visible diagonals than integer values.'),
type='int',
sort_index=6,
select_items=[('fill_method', 0), ('fill_method', 2), ('fill_method', 3)],
select_items=[('fill_method', 'auto_fill'), ('fill_method', 'guided_fill'), ('fill_method', 'legacy_fill')],
default=4)
def staggers(self):
return self.get_float_param("staggers", 4)
@ -380,7 +417,7 @@ class FillStitch(EmbroideryElement):
yield UnderlayInsetWarning(shape.centroid)
# guided fill warnings
if self.fill_method == 2:
if self.fill_method == 'guided_fill':
guide_lines = self._get_guide_lines(True)
if not guide_lines or guide_lines[0].is_empty:
yield MissingGuideLineWarning(self.shape.centroid)
@ -406,12 +443,15 @@ class FillStitch(EmbroideryElement):
@property
@param('running_stitch_length_mm',
_('Running stitch length'),
tooltip=_(
'Length of stitches around the outline of the fill region used when moving from section to section. Also used for meander fill.'),
tooltip=_('Length of stitches around the outline of the fill region used when moving from section to section. '
'Also used for meander and circular fill.'),
unit='mm',
type='float',
default=1.5,
select_items=[('fill_method', 0), ('fill_method', 2), ('fill_method', 4)],
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'meander_fill'),
('fill_method', 'circular_fill')],
sort_index=6)
def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
@ -511,7 +551,10 @@ class FillStitch(EmbroideryElement):
type='float',
default=0,
sort_index=5,
select_items=[('fill_method', 0), ('fill_method', 2), ('fill_method', 4)])
select_items=[('fill_method', 'auto_fill'),
('fill_method', 'guided_fill'),
('fill_method', 'meander_fill'),
('fill_method', 'circular_fill')])
def expand(self):
return self.get_float_param('expand_mm', 0)
@ -523,7 +566,7 @@ class FillStitch(EmbroideryElement):
'are not visible. This gives them a jagged appearance.'),
type='boolean',
default=True,
select_items=[('fill_method', 0), ('fill_method', 2)],
select_items=[('fill_method', 'auto_fill'), ('fill_method', 'guided_fill'), ('fill_method', 'circular_fill')],
sort_index=6)
def underpath(self):
return self.get_boolean_param('underpath', True)
@ -586,7 +629,7 @@ class FillStitch(EmbroideryElement):
def to_stitch_groups(self, previous_stitch_group): # noqa: C901
# backwards compatibility: legacy_fill used to be inkstitch:auto_fill == False
if not self.auto_fill or self.fill_method == 3:
if not self.auto_fill or self.fill_method == 'legacy_fill':
return self.do_legacy_fill()
else:
stitch_groups = []
@ -603,14 +646,16 @@ class FillStitch(EmbroideryElement):
fill_shapes = self.fill_shape(shape)
for i, fill_shape in enumerate(fill_shapes.geoms):
if self.fill_method == 0:
if self.fill_method == 'auto_fill':
stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
if self.fill_method == 1:
elif self.fill_method == 'contour_fill':
stitch_groups.extend(self.do_contour_fill(fill_shape, previous_stitch_group, start))
elif self.fill_method == 2:
elif self.fill_method == 'guided_fill':
stitch_groups.extend(self.do_guided_fill(fill_shape, previous_stitch_group, start, end))
elif self.fill_method == 4:
elif self.fill_method == 'meander_fill':
stitch_groups.extend(self.do_meander_fill(fill_shape, i, start, end))
elif self.fill_method == 'circular_fill':
stitch_groups.extend(self.do_circular_fill(fill_shape, previous_stitch_group, start, end))
except ExitThread:
raise
except Exception:
@ -782,3 +827,33 @@ class FillStitch(EmbroideryElement):
message += traceback.format_exc()
self.fatal(message)
def do_circular_fill(self, shape, last_patch, starting_point, ending_point):
# get target position
command = self.get_command('ripple_target')
if command:
pos = [float(command.use.get("x", 0)), float(command.use.get("y", 0))]
transform = get_node_transform(command.use)
pos = Transform(transform).apply_to_point(pos)
target = shgeo.Point(*pos)
else:
target = shape.centroid
stitches = circular_fill(
shape,
self.angle,
self.row_spacing,
self.staggers,
self.running_stitch_length,
self.running_stitch_tolerance,
self.skip_last,
starting_point,
ending_point,
self.underpath,
target
)
stitch_group = StitchGroup(
color=self.color,
tags=("circular_fill", "auto_fill_top"),
stitches=stitches)
return [stitch_group]

Wyświetl plik

@ -24,8 +24,9 @@ from ..gui import PresetsPanel, SimulatorPreview, WarningPanel
from ..i18n import _
from ..svg.tags import SVG_POLYLINE_TAG
from ..utils import get_resource_dir
from .base import InkstitchExtension
from ..utils.param import ParamOption
from ..utils.threading import ExitThread, check_stop_flag
from .base import InkstitchExtension
def grouper(iterable_obj, count, fillvalue=None):
@ -404,6 +405,8 @@ class ParamsTab(ScrolledPanel):
input = wx.ComboBox(self, wx.ID_ANY, choices=[], style=wx.CB_READONLY)
for option in param.options:
input.Append(option.name, option)
if not param.options:
input.Append(_('No options available'), ParamOption('not_available'))
value = self.get_combo_value_index(param.values[0], param.options)
input.SetSelection(value)
input.Bind(wx.EVT_COMBOBOX, self.changed)

Wyświetl plik

@ -4,6 +4,7 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from .auto_fill import auto_fill
from .circular_fill import circular_fill
from .fill import legacy_fill
from .guided_fill import guided_fill

Wyświetl plik

@ -0,0 +1,94 @@
from shapely import geometry as shgeo
from ..stitch_plan import Stitch
from ..utils.geometry import reverse_line_string
from .auto_fill import (build_fill_stitch_graph, build_travel_graph,
collapse_sequential_outline_edges, fallback,
find_stitch_path, graph_is_valid, travel)
from .contour_fill import _make_fermat_spiral
from .running_stitch import running_stitch
def circular_fill(shape,
angle,
row_spacing,
num_staggers,
running_stitch_length,
running_stitch_tolerance,
skip_last,
starting_point,
ending_point,
underpath,
target
):
# get furthest distance of the target point to a shape border
# so we know how many circles we will need
distance = shape.hausdorff_distance(target) + 1
radius = row_spacing
center = shgeo.Point(target)
circles = []
# add a small inner circle to make sure that the spiral ends close to the center
circles.append(shgeo.LineString(center.buffer(0.1).exterior.coords))
while distance > radius:
circles.append(shgeo.LineString(center.buffer(radius).exterior.coords))
radius += row_spacing
circles.reverse()
# Use double spiral from contour fill (we don't want to get stuck in the middle of the spiral)
double_spiral = _make_fermat_spiral(circles, running_stitch_length, circles[0].coords[0])
double_spiral = shgeo.LineString(list(double_spiral))
intersection = double_spiral.intersection(shape)
segments = []
for line in intersection.geoms:
if isinstance(line, shgeo.LineString):
segments.append(line.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):
return fallback(shape, running_stitch_length, running_stitch_tolerance)
travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath)
path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point)
result = path_to_stitches(path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last)
# use running stitch to adjust the stitch length
result = running_stitch(result,
running_stitch_length,
running_stitch_tolerance)
return result
def path_to_stitches(path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last):
path = collapse_sequential_outline_edges(path)
stitches = []
# 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]))
for edge in path:
if edge.is_segment():
current_edge = fill_stitch_graph[edge[0]][edge[-1]]['segment']
path_geometry = current_edge['geometry']
if edge[0] != path_geometry.coords[0]:
path_geometry = reverse_line_string(path_geometry)
new_stitches = [Stitch(*point) for point in path_geometry.coords]
# need to tag stitches
if skip_last:
del new_stitches[-1]
stitches.extend(new_stitches)
travel_graph.remove_edges_from(fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', []))
else:
stitches.extend(travel(travel_graph, edge[0], edge[1], running_stitch_length, running_stitch_tolerance, skip_last))
return stitches

Wyświetl plik

@ -0,0 +1,8 @@
class ParamOption:
def __init__(self, param_id=None, name=None, legacy=None):
self.id: str = param_id
self.name: str = name
self.legacy: int = legacy
def __repr__(self):
return "ParamOption(%s, %s, %s)" % (self.id, self.name, self.legacy)