2021-03-12 04:17:19 +00:00
|
|
|
# Authors: see git history
|
|
|
|
#
|
|
|
|
# Copyright (c) 2010 Authors
|
|
|
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
|
|
|
|
2022-01-29 08:53:50 +00:00
|
|
|
import logging
|
2018-03-31 00:37:11 +00:00
|
|
|
import math
|
2022-01-29 08:53:50 +00:00
|
|
|
import re
|
2019-03-10 22:24:10 +00:00
|
|
|
import sys
|
2019-02-16 01:51:10 +00:00
|
|
|
import traceback
|
2018-12-13 01:26:22 +00:00
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
from shapely import geometry as shgeo
|
2022-06-21 17:59:26 +00:00
|
|
|
from shapely.errors import TopologicalError
|
|
|
|
from shapely.validation import explain_validity, make_valid
|
2022-01-29 08:53:50 +00:00
|
|
|
|
2018-05-02 01:21:07 +00:00
|
|
|
from ..i18n import _
|
2022-01-29 08:53:50 +00:00
|
|
|
from ..marker import get_marker_elements
|
2021-08-07 15:21:13 +00:00
|
|
|
from ..stitch_plan import StitchGroup
|
2022-06-21 17:59:26 +00:00
|
|
|
from ..stitches import auto_fill, contour_fill, guided_fill, legacy_fill
|
2023-01-16 19:27:06 +00:00
|
|
|
from ..stitches.meander_fill import meander_fill
|
2021-10-21 14:24:40 +00:00
|
|
|
from ..svg import PIXELS_PER_MM
|
|
|
|
from ..svg.tags import INKSCAPE_LABEL
|
2023-01-16 19:27:06 +00:00
|
|
|
from .. import tiles
|
2022-01-29 08:53:50 +00:00
|
|
|
from ..utils import cache, version
|
|
|
|
from .element import EmbroideryElement, param
|
2022-01-30 14:48:51 +00:00
|
|
|
from .validation import ValidationError, ValidationWarning
|
2023-01-31 04:55:18 +00:00
|
|
|
from ..utils.threading import ExitThread
|
2019-08-06 02:42:48 +00:00
|
|
|
|
2021-10-29 14:18:22 +00:00
|
|
|
|
2019-08-06 02:42:48 +00:00
|
|
|
class SmallShapeWarning(ValidationWarning):
|
|
|
|
name = _("Small Fill")
|
|
|
|
description = _("This fill object is so small that it would probably look better as running stitch or satin column. "
|
|
|
|
"For very small shapes, fill stitch is not possible, and Ink/Stitch will use running stitch around "
|
|
|
|
"the outline instead.")
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
|
2021-03-04 17:40:53 +00:00
|
|
|
class ExpandWarning(ValidationWarning):
|
|
|
|
name = _("Expand")
|
|
|
|
description = _("The expand parameter for this fill object cannot be applied. "
|
|
|
|
"Ink/Stitch will ignore it and will use original size instead.")
|
|
|
|
|
|
|
|
|
|
|
|
class UnderlayInsetWarning(ValidationWarning):
|
|
|
|
name = _("Inset")
|
|
|
|
description = _("The underlay inset parameter for this fill object cannot be applied. "
|
|
|
|
"Ink/Stitch will ignore it and will use the original size instead.")
|
|
|
|
|
2022-02-01 18:47:19 +00:00
|
|
|
|
2022-01-30 14:48:51 +00:00
|
|
|
class MissingGuideLineWarning(ValidationWarning):
|
|
|
|
name = _("Missing Guideline")
|
2022-02-18 14:36:01 +00:00
|
|
|
description = _('This object is set to "Guided Fill", but has no guide line.')
|
2022-01-30 14:48:51 +00:00
|
|
|
steps_to_solve = [
|
|
|
|
_('* Create a stroke object'),
|
|
|
|
_('* Select this object and run Extensions > Ink/Stitch > Edit > Selection to guide line')
|
|
|
|
]
|
|
|
|
|
2022-02-01 18:47:19 +00:00
|
|
|
|
2022-01-30 14:48:51 +00:00
|
|
|
class DisjointGuideLineWarning(ValidationWarning):
|
|
|
|
name = _("Disjointed Guide Line")
|
|
|
|
description = _("The guide line of this object isn't within the object borders. "
|
|
|
|
"The guide line works best, if it is within the target element.")
|
|
|
|
steps_to_solve = [
|
|
|
|
_('* Move the guide line into the element')
|
|
|
|
]
|
|
|
|
|
2022-02-01 18:47:19 +00:00
|
|
|
|
2022-01-30 14:48:51 +00:00
|
|
|
class MultipleGuideLineWarning(ValidationWarning):
|
|
|
|
name = _("Multiple Guide Lines")
|
2022-02-02 20:19:31 +00:00
|
|
|
description = _("This object has multiple guide lines, but only the first one will be used.")
|
2022-01-30 14:48:51 +00:00
|
|
|
steps_to_solve = [
|
|
|
|
_("* Remove all guide lines, except for one.")
|
|
|
|
]
|
|
|
|
|
2022-02-01 18:47:19 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
class UnconnectedWarning(ValidationWarning):
|
2022-01-30 14:48:51 +00:00
|
|
|
name = _("Unconnected")
|
2022-06-21 17:59:26 +00:00
|
|
|
description = _("Fill: This object is made up of unconnected shapes. "
|
2022-01-30 14:48:51 +00:00
|
|
|
"Ink/Stitch doesn't know what order to stitch them in. Please break this "
|
|
|
|
"object up into separate shapes.")
|
|
|
|
steps_to_solve = [
|
|
|
|
_('* Extensions > Ink/Stitch > Fill Tools > Break Apart Fill Objects'),
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
class BorderCrossWarning(ValidationWarning):
|
2022-01-30 14:48:51 +00:00
|
|
|
name = _("Border crosses itself")
|
2022-06-21 17:59:26 +00:00
|
|
|
description = _("Fill: The border crosses over itself. This may lead into unconnected shapes. "
|
|
|
|
"Please break this object into separate shapes to indicate in which order it should be stitched in.")
|
|
|
|
steps_to_solve = [
|
|
|
|
_('* Extensions > Ink/Stitch > Fill Tools > Break Apart Fill Objects')
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class InvalidShapeError(ValidationError):
|
|
|
|
name = _("This shape is invalid")
|
|
|
|
description = _('Fill: This shape cannot be stitched out. Please try to repair it with the "Break Apart Fill Objects" extension.')
|
2022-01-30 14:48:51 +00:00
|
|
|
steps_to_solve = [
|
|
|
|
_('* Extensions > Ink/Stitch > Fill Tools > Break Apart Fill Objects')
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class FillStitch(EmbroideryElement):
|
|
|
|
element_name = _("FillStitch")
|
2018-03-31 00:37:11 +00:00
|
|
|
|
|
|
|
@property
|
2021-10-29 14:18:22 +00:00
|
|
|
@param('auto_fill', _('Automatically routed fill stitching'), type='toggle', default=True, sort_index=1)
|
2022-01-30 14:48:51 +00:00
|
|
|
def auto_fill(self):
|
2021-10-29 14:18:22 +00:00
|
|
|
return self.get_boolean_param('auto_fill', True)
|
|
|
|
|
2021-10-21 14:24:40 +00:00
|
|
|
@property
|
2021-10-29 14:18:22 +00:00
|
|
|
@param('fill_method', _('Fill method'), type='dropdown', default=0,
|
2023-01-16 19:27:06 +00:00
|
|
|
options=[_("Auto Fill"), _("Contour Fill"), _("Guided Fill"), _("Legacy Fill"), _("Meander Fill")], sort_index=2)
|
2021-10-21 14:24:40 +00:00
|
|
|
def fill_method(self):
|
|
|
|
return self.get_int_param('fill_method', 0)
|
|
|
|
|
2022-06-22 14:11:12 +00:00
|
|
|
@property
|
|
|
|
@param('guided_fill_strategy', _('Guided Fill Strategy'), type='dropdown', default=0,
|
|
|
|
options=[_("Copy"), _("Parallel Offset")], select_items=[('fill_method', 2)], sort_index=3,
|
2022-11-27 09:17:43 +00:00
|
|
|
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. '
|
2022-06-22 14:11:12 +00:00
|
|
|
'Sharp corners may be introduced.'))
|
|
|
|
def guided_fill_strategy(self):
|
|
|
|
return self.get_int_param('guided_fill_strategy', 0)
|
|
|
|
|
2021-10-21 14:24:40 +00:00
|
|
|
@property
|
2022-05-08 03:14:55 +00:00
|
|
|
@param('contour_strategy', _('Contour Fill Strategy'), type='dropdown', default=0,
|
2022-05-06 02:53:31 +00:00
|
|
|
options=[_("Inner to Outer"), _("Single spiral"), _("Double spiral")], select_items=[('fill_method', 1)], sort_index=3)
|
|
|
|
def contour_strategy(self):
|
2022-05-08 03:14:55 +00:00
|
|
|
return self.get_int_param('contour_strategy', 0)
|
2021-10-21 14:24:40 +00:00
|
|
|
|
|
|
|
@property
|
2021-10-29 14:18:22 +00:00
|
|
|
@param('join_style', _('Join Style'), type='dropdown', default=0,
|
2022-05-06 02:53:31 +00:00
|
|
|
options=[_("Round"), _("Mitered"), _("Beveled")], select_items=[('fill_method', 1)], sort_index=4)
|
2021-10-21 14:24:40 +00:00
|
|
|
def join_style(self):
|
|
|
|
return self.get_int_param('join_style', 0)
|
|
|
|
|
|
|
|
@property
|
2022-05-06 02:53:31 +00:00
|
|
|
@param('avoid_self_crossing', _('Avoid self-crossing'), type='boolean', default=False, select_items=[('fill_method', 1)], sort_index=5)
|
2022-05-02 19:00:52 +00:00
|
|
|
def avoid_self_crossing(self):
|
|
|
|
return self.get_boolean_param('avoid_self_crossing', False)
|
|
|
|
|
2022-08-26 03:10:16 +00:00
|
|
|
@property
|
|
|
|
@param('smoothness_mm', _('Smoothness'),
|
|
|
|
tooltip=_(
|
|
|
|
'Smooth the stitch path. Smoothness limits how far the smoothed stitch path ' +
|
|
|
|
'is allowed to deviate from the original path. Hint: a lower stitchc tolerance may be needed too.'
|
|
|
|
),
|
|
|
|
type='integer',
|
|
|
|
unit='mm',
|
|
|
|
default=0,
|
2023-01-16 19:27:06 +00:00
|
|
|
select_items=[('fill_method', 1), ('fill_method', 4)],
|
2022-08-26 03:10:16 +00:00
|
|
|
sort_index=5)
|
|
|
|
def smoothness(self):
|
|
|
|
return self.get_float_param('smoothness_mm', 0)
|
|
|
|
|
2022-05-03 03:48:46 +00:00
|
|
|
@property
|
2022-05-06 02:53:31 +00:00
|
|
|
@param('clockwise', _('Clockwise'), type='boolean', default=True, select_items=[('fill_method', 1)], sort_index=5)
|
2022-05-03 03:48:46 +00:00
|
|
|
def clockwise(self):
|
|
|
|
return self.get_boolean_param('clockwise', True)
|
|
|
|
|
2023-01-16 19:27:06 +00:00
|
|
|
@property
|
|
|
|
@param('meander_pattern', _('Meander Pattern'), type='dropdown', default=0,
|
|
|
|
options=[tile.name for tile in tiles.all_tiles()], select_items=[('fill_method', 4)], sort_index=3)
|
|
|
|
def meander_pattern(self):
|
|
|
|
return self.get_param('meander_pattern', None)
|
|
|
|
|
2021-10-21 14:24:40 +00:00
|
|
|
@property
|
|
|
|
@param('angle',
|
|
|
|
_('Angle of lines of stitches'),
|
2022-02-02 20:19:31 +00:00
|
|
|
tooltip=_('The angle increases in a counter-clockwise direction. 0 is horizontal. Negative angles are allowed.'),
|
2021-10-21 14:24:40 +00:00
|
|
|
unit='deg',
|
|
|
|
type='float',
|
2022-05-06 02:53:31 +00:00
|
|
|
sort_index=6,
|
2022-01-28 21:31:55 +00:00
|
|
|
select_items=[('fill_method', 0), ('fill_method', 3)],
|
2021-10-21 14:24:40 +00:00
|
|
|
default=0)
|
|
|
|
@cache
|
|
|
|
def angle(self):
|
|
|
|
return math.radians(self.get_float_param('angle', 0))
|
|
|
|
|
|
|
|
@property
|
|
|
|
def color(self):
|
|
|
|
# SVG spec says the default fill is black
|
|
|
|
return self.get_style("fill", "#000000")
|
|
|
|
|
|
|
|
@property
|
|
|
|
@param(
|
|
|
|
'skip_last',
|
|
|
|
_('Skip last stitch in each row'),
|
|
|
|
tooltip=_('The last stitch in each row is quite close to the first stitch in the next row. '
|
|
|
|
'Skipping it decreases stitch count and density.'),
|
|
|
|
type='boolean',
|
2022-05-06 02:53:31 +00:00
|
|
|
sort_index=6,
|
2022-01-28 21:31:55 +00:00
|
|
|
select_items=[('fill_method', 0), ('fill_method', 2),
|
|
|
|
('fill_method', 3)],
|
2021-10-21 14:24:40 +00:00
|
|
|
default=False)
|
|
|
|
def skip_last(self):
|
|
|
|
return self.get_boolean_param("skip_last", False)
|
|
|
|
|
|
|
|
@property
|
|
|
|
@param(
|
|
|
|
'flip',
|
|
|
|
_('Flip fill (start right-to-left)'),
|
|
|
|
tooltip=_('The flip option can help you with routing your stitch path. '
|
|
|
|
'When you enable flip, stitching goes from right-to-left instead of left-to-right.'),
|
|
|
|
type='boolean',
|
2022-05-06 02:53:31 +00:00
|
|
|
sort_index=7,
|
2022-05-17 15:33:10 +00:00
|
|
|
select_items=[('fill_method', 3)],
|
2021-10-21 14:24:40 +00:00
|
|
|
default=False)
|
|
|
|
def flip(self):
|
|
|
|
return self.get_boolean_param("flip", False)
|
|
|
|
|
|
|
|
@property
|
|
|
|
@param('row_spacing_mm',
|
|
|
|
_('Spacing between rows'),
|
|
|
|
tooltip=_('Distance between rows of stitches.'),
|
|
|
|
unit='mm',
|
2022-05-06 02:53:31 +00:00
|
|
|
sort_index=6,
|
2021-10-21 14:24:40 +00:00
|
|
|
type='float',
|
|
|
|
default=0.25)
|
|
|
|
def row_spacing(self):
|
|
|
|
return max(self.get_float_param("row_spacing_mm", 0.25), 0.1 * PIXELS_PER_MM)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def end_row_spacing(self):
|
|
|
|
return self.get_float_param("end_row_spacing_mm")
|
|
|
|
|
|
|
|
@property
|
|
|
|
@param('max_stitch_length_mm',
|
|
|
|
_('Maximum fill stitch length'),
|
2021-10-29 14:18:22 +00:00
|
|
|
tooltip=_(
|
|
|
|
'The length of each stitch in a row. Shorter stitch may be used at the start or end of a row.'),
|
2021-10-21 14:24:40 +00:00
|
|
|
unit='mm',
|
2022-05-06 02:53:31 +00:00
|
|
|
sort_index=6,
|
2021-10-21 14:24:40 +00:00
|
|
|
type='float',
|
|
|
|
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)
|
|
|
|
|
|
|
|
@property
|
|
|
|
@param('staggers',
|
|
|
|
_('Stagger rows this many times before repeating'),
|
2022-11-27 09:17:43 +00:00
|
|
|
tooltip=_('Length of the cycle by which successive stitch rows are staggered. '
|
2022-11-10 13:03:32 +00:00
|
|
|
'Fractional values are allowed and can have less visible diagonals than integer values.'),
|
2021-10-21 14:24:40 +00:00
|
|
|
type='int',
|
2022-05-06 02:53:31 +00:00
|
|
|
sort_index=6,
|
2022-06-22 14:11:12 +00:00
|
|
|
select_items=[('fill_method', 0), ('fill_method', 2), ('fill_method', 3)],
|
2021-10-21 14:24:40 +00:00
|
|
|
default=4)
|
|
|
|
def staggers(self):
|
2022-10-23 04:54:35 +00:00
|
|
|
return self.get_float_param("staggers", 4)
|
2021-10-21 14:24:40 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
@cache
|
|
|
|
def paths(self):
|
|
|
|
paths = self.flatten(self.parse_path())
|
|
|
|
# ensure path length
|
|
|
|
for i, path in enumerate(paths):
|
|
|
|
if len(path) < 3:
|
2022-05-07 20:20:15 +00:00
|
|
|
paths[i] = [(path[0][0], path[0][1]), (path[0][0] + 1.0, path[0][1]), (path[0][0], path[0][1] + 1.0)]
|
2021-10-21 14:24:40 +00:00
|
|
|
return paths
|
|
|
|
|
2022-01-30 14:48:51 +00:00
|
|
|
@property
|
|
|
|
@cache
|
2022-06-21 17:59:26 +00:00
|
|
|
def original_shape(self):
|
2022-01-30 14:48:51 +00:00
|
|
|
# shapely's idea of "holes" are to subtract everything in the second set
|
|
|
|
# from the first. So let's at least make sure the "first" thing is the
|
|
|
|
# biggest path.
|
|
|
|
paths = self.paths
|
2022-06-21 17:59:26 +00:00
|
|
|
paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
|
2022-01-30 14:48:51 +00:00
|
|
|
# Very small holes will cause a shape to be rendered as an outline only
|
|
|
|
# they are too small to be rendered and only confuse the auto_fill algorithm.
|
|
|
|
# So let's ignore them
|
|
|
|
if shgeo.Polygon(paths[0]).area > 5 and shgeo.Polygon(paths[-1]).area < 5:
|
|
|
|
paths = [path for path in paths if shgeo.Polygon(path).area > 3]
|
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
return shgeo.MultiPolygon([(paths[0], paths[1:])])
|
|
|
|
|
|
|
|
@property
|
|
|
|
@cache
|
|
|
|
def shape(self):
|
|
|
|
shape = self._get_clipped_path()
|
|
|
|
|
|
|
|
if self.shape_is_valid(shape):
|
|
|
|
return shape
|
|
|
|
|
|
|
|
# Repair not valid shapes
|
|
|
|
logger = logging.getLogger('shapely.geos')
|
|
|
|
level = logger.level
|
|
|
|
logger.setLevel(logging.CRITICAL)
|
|
|
|
|
|
|
|
valid_shape = make_valid(shape)
|
|
|
|
|
|
|
|
logger.setLevel(level)
|
2022-06-30 17:22:33 +00:00
|
|
|
|
|
|
|
if isinstance(valid_shape, shgeo.Polygon):
|
|
|
|
return shgeo.MultiPolygon([valid_shape])
|
2022-07-14 14:23:46 +00:00
|
|
|
if isinstance(valid_shape, shgeo.LineString):
|
|
|
|
return shgeo.MultiPolygon([])
|
2022-11-27 07:37:59 +00:00
|
|
|
if shape.area == 0:
|
|
|
|
return shgeo.MultiPolygon([])
|
2022-06-30 17:22:33 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
polygons = []
|
|
|
|
for polygon in valid_shape.geoms:
|
|
|
|
if isinstance(polygon, shgeo.Polygon):
|
|
|
|
polygons.append(polygon)
|
|
|
|
if isinstance(polygon, shgeo.MultiPolygon):
|
|
|
|
polygons.extend(polygon.geoms)
|
|
|
|
return shgeo.MultiPolygon(polygons)
|
|
|
|
|
|
|
|
def _get_clipped_path(self):
|
|
|
|
if self.node.clip is None:
|
|
|
|
return self.original_shape
|
|
|
|
|
|
|
|
from .element import EmbroideryElement
|
|
|
|
clip_element = EmbroideryElement(self.node.clip)
|
|
|
|
clip_element.paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
|
|
|
|
polygon = shgeo.MultiPolygon([(clip_element.paths[0], clip_element.paths[1:])])
|
|
|
|
try:
|
|
|
|
intersection = polygon.intersection(self.original_shape)
|
|
|
|
except TopologicalError:
|
|
|
|
return self.original_shape
|
|
|
|
|
|
|
|
if isinstance(intersection, shgeo.Polygon):
|
|
|
|
return shgeo.MultiPolygon([intersection])
|
|
|
|
|
|
|
|
if isinstance(intersection, shgeo.MultiPolygon):
|
|
|
|
return intersection
|
|
|
|
|
|
|
|
polygons = []
|
|
|
|
if isinstance(intersection, shgeo.GeometryCollection):
|
|
|
|
for geom in intersection.geoms:
|
|
|
|
if isinstance(geom, shgeo.Polygon):
|
|
|
|
polygons.append(geom)
|
|
|
|
return shgeo.MultiPolygon([polygons])
|
2022-01-30 14:48:51 +00:00
|
|
|
|
|
|
|
def shape_is_valid(self, shape):
|
|
|
|
# Shapely will log to stdout to complain about the shape unless we make
|
|
|
|
# it shut up.
|
|
|
|
logger = logging.getLogger('shapely.geos')
|
|
|
|
level = logger.level
|
|
|
|
logger.setLevel(logging.CRITICAL)
|
|
|
|
|
|
|
|
valid = shape.is_valid
|
|
|
|
|
|
|
|
logger.setLevel(level)
|
|
|
|
|
|
|
|
return valid
|
|
|
|
|
|
|
|
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)
|
2022-06-21 17:59:26 +00:00
|
|
|
yield InvalidShapeError((x, y))
|
2022-01-30 14:48:51 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
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)
|
2022-01-30 14:48:51 +00:00
|
|
|
if "Hole lies outside shell" in message:
|
2022-06-21 17:59:26 +00:00
|
|
|
yield UnconnectedWarning((x, y))
|
2022-01-30 14:48:51 +00:00
|
|
|
else:
|
2022-06-21 17:59:26 +00:00
|
|
|
yield BorderCrossWarning((x, y))
|
2022-01-30 14:48:51 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
for shape in self.shape.geoms:
|
|
|
|
if self.shape.area < 20:
|
|
|
|
label = self.node.get(INKSCAPE_LABEL) or self.node.get("id")
|
|
|
|
yield SmallShapeWarning(shape.centroid, label)
|
2022-01-30 14:48:51 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
if self.shrink_or_grow_shape(shape, self.expand, True).is_empty:
|
|
|
|
yield ExpandWarning(shape.centroid)
|
2022-01-30 14:48:51 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
if self.shrink_or_grow_shape(shape, -self.fill_underlay_inset, True).is_empty:
|
|
|
|
yield UnderlayInsetWarning(shape.centroid)
|
2022-01-30 14:48:51 +00:00
|
|
|
|
|
|
|
# guided fill warnings
|
|
|
|
if self.fill_method == 2:
|
|
|
|
guide_lines = self._get_guide_lines(True)
|
|
|
|
if not guide_lines or guide_lines[0].is_empty:
|
|
|
|
yield MissingGuideLineWarning(self.shape.centroid)
|
|
|
|
elif len(guide_lines) > 1:
|
|
|
|
yield MultipleGuideLineWarning(self.shape.centroid)
|
|
|
|
elif guide_lines[0].disjoint(self.shape):
|
|
|
|
yield DisjointGuideLineWarning(self.shape.centroid)
|
|
|
|
return None
|
|
|
|
|
|
|
|
for warning in super(FillStitch, self).validation_warnings():
|
|
|
|
yield warning
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
@property
|
|
|
|
@cache
|
|
|
|
def outline(self):
|
|
|
|
return self.shape.boundary[0]
|
|
|
|
|
|
|
|
@property
|
|
|
|
@cache
|
|
|
|
def outline_length(self):
|
|
|
|
return self.outline.length
|
|
|
|
|
|
|
|
@property
|
2018-08-09 18:32:41 +00:00
|
|
|
@param('running_stitch_length_mm',
|
2018-08-22 00:32:50 +00:00
|
|
|
_('Running stitch length (traversal between sections)'),
|
2022-02-02 20:19:31 +00:00
|
|
|
tooltip=_('Length of stitches around the outline of the fill region used when moving from section to section.'),
|
2018-08-22 00:32:50 +00:00
|
|
|
unit='mm',
|
|
|
|
type='float',
|
2021-10-21 14:24:40 +00:00
|
|
|
default=1.5,
|
2021-10-29 14:18:22 +00:00
|
|
|
select_items=[('fill_method', 0), ('fill_method', 2)],
|
2022-05-06 02:53:31 +00:00
|
|
|
sort_index=6)
|
2018-03-31 00:37:11 +00:00
|
|
|
def running_stitch_length(self):
|
|
|
|
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
|
|
|
|
|
2022-06-22 13:26:37 +00:00
|
|
|
@property
|
|
|
|
@param('running_stitch_tolerance_mm',
|
|
|
|
_('Running stitch tolerance'),
|
|
|
|
tooltip=_('All stitches must be within this distance of the path. ' +
|
|
|
|
'A lower tolerance means stitches will be closer together. ' +
|
|
|
|
'A higher tolerance means sharp corners may be rounded.'),
|
|
|
|
unit='mm',
|
|
|
|
type='float',
|
|
|
|
default=0.2,
|
|
|
|
sort_index=6)
|
|
|
|
def running_stitch_tolerance(self):
|
|
|
|
return max(self.get_float_param("running_stitch_tolerance_mm", 0.2), 0.01)
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
@property
|
2022-05-17 15:33:10 +00:00
|
|
|
@param('fill_underlay', _('Underlay'), type='toggle', group=_('Fill Underlay'), default=True)
|
2018-03-31 00:37:11 +00:00
|
|
|
def fill_underlay(self):
|
2020-04-25 12:45:27 +00:00
|
|
|
return self.get_boolean_param("fill_underlay", default=True)
|
2018-03-31 00:37:11 +00:00
|
|
|
|
|
|
|
@property
|
2018-08-09 18:32:41 +00:00
|
|
|
@param('fill_underlay_angle',
|
2018-08-22 00:32:50 +00:00
|
|
|
_('Fill angle'),
|
2023-01-15 19:04:10 +00:00
|
|
|
tooltip=_('Default: fill angle + 90 deg. Insert a list for multiple layers separated by a space.'),
|
2018-08-22 00:32:50 +00:00
|
|
|
unit='deg',
|
2022-05-17 15:33:10 +00:00
|
|
|
group=_('Fill Underlay'),
|
2018-08-22 00:32:50 +00:00
|
|
|
type='float')
|
2018-03-31 00:37:11 +00:00
|
|
|
@cache
|
|
|
|
def fill_underlay_angle(self):
|
2020-03-16 17:38:10 +00:00
|
|
|
underlay_angles = self.get_param('fill_underlay_angle', None)
|
|
|
|
default_value = [self.angle + math.pi / 2.0]
|
|
|
|
if underlay_angles is not None:
|
2023-01-15 19:04:10 +00:00
|
|
|
underlay_angles = underlay_angles.strip().split(' ')
|
|
|
|
# remove comma separator for backward compatibility
|
|
|
|
underlay_angles = [angle[:-1] if angle.endswith(',') else angle for angle in underlay_angles]
|
2020-03-16 17:38:10 +00:00
|
|
|
try:
|
2021-10-29 14:18:22 +00:00
|
|
|
underlay_angles = [math.radians(
|
|
|
|
float(angle)) for angle in underlay_angles]
|
2020-03-16 17:38:10 +00:00
|
|
|
except (TypeError, ValueError):
|
|
|
|
return default_value
|
2018-03-31 00:37:11 +00:00
|
|
|
else:
|
2020-03-16 17:38:10 +00:00
|
|
|
underlay_angles = default_value
|
|
|
|
|
|
|
|
return underlay_angles
|
2018-03-31 00:37:11 +00:00
|
|
|
|
|
|
|
@property
|
2018-08-09 18:32:41 +00:00
|
|
|
@param('fill_underlay_row_spacing_mm',
|
2018-08-22 00:32:50 +00:00
|
|
|
_('Row spacing'),
|
|
|
|
tooltip=_('default: 3x fill row spacing'),
|
|
|
|
unit='mm',
|
2022-05-17 15:33:10 +00:00
|
|
|
group=_('Fill Underlay'),
|
2018-08-22 00:32:50 +00:00
|
|
|
type='float')
|
2018-03-31 00:37:11 +00:00
|
|
|
@cache
|
|
|
|
def fill_underlay_row_spacing(self):
|
|
|
|
return self.get_float_param("fill_underlay_row_spacing_mm") or self.row_spacing * 3
|
|
|
|
|
|
|
|
@property
|
2018-08-09 18:32:41 +00:00
|
|
|
@param('fill_underlay_max_stitch_length_mm',
|
2018-08-22 00:32:50 +00:00
|
|
|
_('Max stitch length'),
|
|
|
|
tooltip=_('default: equal to fill max stitch length'),
|
|
|
|
unit='mm',
|
2022-05-17 15:33:10 +00:00
|
|
|
group=_('Fill Underlay'), type='float')
|
2018-03-31 00:37:11 +00:00
|
|
|
@cache
|
|
|
|
def fill_underlay_max_stitch_length(self):
|
|
|
|
return self.get_float_param("fill_underlay_max_stitch_length_mm") or self.max_stitch_length
|
|
|
|
|
|
|
|
@property
|
2018-06-02 00:34:27 +00:00
|
|
|
@param('fill_underlay_inset_mm',
|
2018-08-22 00:32:50 +00:00
|
|
|
_('Inset'),
|
2022-02-02 20:19:31 +00:00
|
|
|
tooltip=_('Shrink the shape before doing underlay, to prevent underlay from showing around the outside of the fill.'),
|
2018-08-22 00:32:50 +00:00
|
|
|
unit='mm',
|
2022-05-17 15:33:10 +00:00
|
|
|
group=_('Fill Underlay'),
|
2018-08-22 00:32:50 +00:00
|
|
|
type='float',
|
|
|
|
default=0)
|
2018-03-31 00:37:11 +00:00
|
|
|
def fill_underlay_inset(self):
|
|
|
|
return self.get_float_param('fill_underlay_inset_mm', 0)
|
|
|
|
|
2018-12-13 01:26:22 +00:00
|
|
|
@property
|
|
|
|
@param(
|
|
|
|
'fill_underlay_skip_last',
|
|
|
|
_('Skip last stitch in each row'),
|
|
|
|
tooltip=_('The last stitch in each row is quite close to the first stitch in the next row. '
|
|
|
|
'Skipping it decreases stitch count and density.'),
|
2022-05-17 15:33:10 +00:00
|
|
|
group=_('Fill Underlay'),
|
2018-12-13 01:26:22 +00:00
|
|
|
type='boolean',
|
|
|
|
default=False)
|
|
|
|
def fill_underlay_skip_last(self):
|
|
|
|
return self.get_boolean_param("fill_underlay_skip_last", False)
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
@property
|
2018-06-02 00:34:27 +00:00
|
|
|
@param('expand_mm',
|
2018-08-22 00:32:50 +00:00
|
|
|
_('Expand'),
|
2022-02-02 20:19:31 +00:00
|
|
|
tooltip=_('Expand the shape before fill stitching, to compensate for gaps between shapes.'),
|
2018-08-22 00:32:50 +00:00
|
|
|
unit='mm',
|
|
|
|
type='float',
|
2021-10-21 14:24:40 +00:00
|
|
|
default=0,
|
2021-10-29 14:18:22 +00:00
|
|
|
sort_index=5,
|
|
|
|
select_items=[('fill_method', 0), ('fill_method', 2)])
|
2018-06-02 00:34:27 +00:00
|
|
|
def expand(self):
|
|
|
|
return self.get_float_param('expand_mm', 0)
|
|
|
|
|
2019-03-20 02:30:07 +00:00
|
|
|
@property
|
|
|
|
@param('underpath',
|
|
|
|
_('Underpath'),
|
|
|
|
tooltip=_('Travel inside the shape when moving from section to section. Underpath '
|
|
|
|
'stitches avoid traveling in the direction of the row angle so that they '
|
|
|
|
'are not visible. This gives them a jagged appearance.'),
|
|
|
|
type='boolean',
|
2021-10-21 14:24:40 +00:00
|
|
|
default=True,
|
2021-10-29 14:18:22 +00:00
|
|
|
select_items=[('fill_method', 0), ('fill_method', 2)],
|
|
|
|
sort_index=6)
|
2019-03-20 02:30:07 +00:00
|
|
|
def underpath(self):
|
|
|
|
return self.get_boolean_param('underpath', True)
|
|
|
|
|
|
|
|
@property
|
2019-03-22 01:07:48 +00:00
|
|
|
@param(
|
|
|
|
'underlay_underpath',
|
|
|
|
_('Underpath'),
|
|
|
|
tooltip=_('Travel inside the shape when moving from section to section. Underpath '
|
|
|
|
'stitches avoid traveling in the direction of the row angle so that they '
|
|
|
|
'are not visible. This gives them a jagged appearance.'),
|
2022-05-17 15:33:10 +00:00
|
|
|
group=_('Fill Underlay'),
|
2019-03-22 01:07:48 +00:00
|
|
|
type='boolean',
|
2019-03-22 01:25:14 +00:00
|
|
|
default=True)
|
2019-03-20 02:30:07 +00:00
|
|
|
def underlay_underpath(self):
|
2019-03-31 01:56:39 +00:00
|
|
|
return self.get_boolean_param('underlay_underpath', True)
|
2019-03-20 02:30:07 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
def shrink_or_grow_shape(self, shape, amount, validate=False):
|
2022-06-30 17:22:33 +00:00
|
|
|
new_shape = shape
|
2018-06-02 00:34:27 +00:00
|
|
|
if amount:
|
2022-06-30 17:22:33 +00:00
|
|
|
new_shape = shape.buffer(amount)
|
2021-03-04 17:40:53 +00:00
|
|
|
# changing the size can empty the shape
|
|
|
|
# in this case we want to use the original shape rather than returning an error
|
2022-06-30 17:22:33 +00:00
|
|
|
if (new_shape.is_empty and not validate):
|
|
|
|
new_shape = shape
|
|
|
|
|
|
|
|
if not isinstance(new_shape, shgeo.MultiPolygon):
|
|
|
|
new_shape = shgeo.MultiPolygon([new_shape])
|
|
|
|
|
|
|
|
return new_shape
|
2018-03-31 00:37:11 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
def underlay_shape(self, shape):
|
|
|
|
return self.shrink_or_grow_shape(shape, -self.fill_underlay_inset)
|
2018-06-02 00:34:27 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
def fill_shape(self, shape):
|
|
|
|
return self.shrink_or_grow_shape(shape, self.expand)
|
2018-06-02 00:34:27 +00:00
|
|
|
|
2022-07-23 15:49:09 +00:00
|
|
|
def get_starting_point(self, previous_stitch_group):
|
2018-06-23 02:29:23 +00:00
|
|
|
# If there is a "fill_start" Command, then use that; otherwise pick
|
|
|
|
# the point closest to the end of the last patch.
|
|
|
|
|
|
|
|
if self.get_command('fill_start'):
|
|
|
|
return self.get_command('fill_start').target_point
|
2022-07-23 15:49:09 +00:00
|
|
|
elif previous_stitch_group:
|
|
|
|
return previous_stitch_group.stitches[-1]
|
2018-06-23 02:29:23 +00:00
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2022-07-23 15:49:09 +00:00
|
|
|
def uses_previous_stitch(self):
|
|
|
|
if self.get_command('fill_start'):
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
2018-06-23 02:31:42 +00:00
|
|
|
def get_ending_point(self):
|
|
|
|
if self.get_command('fill_end'):
|
|
|
|
return self.get_command('fill_end').target_point
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2022-07-23 15:49:09 +00:00
|
|
|
def to_stitch_groups(self, previous_stitch_group): # noqa: C901
|
2022-01-30 14:48:51 +00:00
|
|
|
# backwards compatibility: legacy_fill used to be inkstitch:auto_fill == False
|
|
|
|
if not self.auto_fill or self.fill_method == 3:
|
|
|
|
return self.do_legacy_fill()
|
|
|
|
else:
|
|
|
|
stitch_groups = []
|
|
|
|
end = self.get_ending_point()
|
2018-03-31 00:37:11 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
for shape in self.shape.geoms:
|
2022-07-23 15:49:09 +00:00
|
|
|
start = self.get_starting_point(previous_stitch_group)
|
2022-06-21 17:59:26 +00:00
|
|
|
try:
|
|
|
|
if self.fill_underlay:
|
2022-06-30 17:22:33 +00:00
|
|
|
underlay_shapes = self.underlay_shape(shape)
|
|
|
|
for underlay_shape in underlay_shapes.geoms:
|
|
|
|
underlay_stitch_groups, start = self.do_underlay(underlay_shape, start)
|
|
|
|
stitch_groups.extend(underlay_stitch_groups)
|
|
|
|
|
|
|
|
fill_shapes = self.fill_shape(shape)
|
|
|
|
for fill_shape in fill_shapes.geoms:
|
|
|
|
if self.fill_method == 0:
|
2022-07-23 15:49:09 +00:00
|
|
|
stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
|
2022-06-30 17:22:33 +00:00
|
|
|
if self.fill_method == 1:
|
2022-07-23 15:49:09 +00:00
|
|
|
stitch_groups.extend(self.do_contour_fill(fill_shape, previous_stitch_group, start))
|
2022-06-30 17:22:33 +00:00
|
|
|
elif self.fill_method == 2:
|
2022-07-23 15:49:09 +00:00
|
|
|
stitch_groups.extend(self.do_guided_fill(fill_shape, previous_stitch_group, start, end))
|
2023-01-16 19:27:06 +00:00
|
|
|
elif self.fill_method == 4:
|
|
|
|
stitch_groups.extend(self.do_meander_fill(fill_shape, start, end))
|
2023-01-31 04:55:18 +00:00
|
|
|
except ExitThread:
|
|
|
|
raise
|
2022-06-21 17:59:26 +00:00
|
|
|
except Exception:
|
|
|
|
self.fatal_fill_error()
|
2022-07-23 15:49:09 +00:00
|
|
|
previous_stitch_group = stitch_groups[-1]
|
2022-01-30 14:48:51 +00:00
|
|
|
|
|
|
|
return stitch_groups
|
|
|
|
|
|
|
|
def do_legacy_fill(self):
|
|
|
|
stitch_lists = legacy_fill(self.shape,
|
|
|
|
self.angle,
|
|
|
|
self.row_spacing,
|
|
|
|
self.end_row_spacing,
|
|
|
|
self.max_stitch_length,
|
|
|
|
self.flip,
|
|
|
|
self.staggers,
|
|
|
|
self.skip_last)
|
|
|
|
return [StitchGroup(stitches=stitch_list, color=self.color) for stitch_list in stitch_lists]
|
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
def do_underlay(self, shape, starting_point):
|
2022-01-30 14:48:51 +00:00
|
|
|
stitch_groups = []
|
|
|
|
for i in range(len(self.fill_underlay_angle)):
|
|
|
|
underlay = StitchGroup(
|
|
|
|
color=self.color,
|
|
|
|
tags=("auto_fill", "auto_fill_underlay"),
|
|
|
|
stitches=auto_fill(
|
2022-06-30 17:22:33 +00:00
|
|
|
shape,
|
2022-01-30 14:48:51 +00:00
|
|
|
self.fill_underlay_angle[i],
|
|
|
|
self.fill_underlay_row_spacing,
|
|
|
|
self.fill_underlay_row_spacing,
|
|
|
|
self.fill_underlay_max_stitch_length,
|
|
|
|
self.running_stitch_length,
|
2022-06-22 13:26:37 +00:00
|
|
|
self.running_stitch_tolerance,
|
2022-01-30 14:48:51 +00:00
|
|
|
self.staggers,
|
|
|
|
self.fill_underlay_skip_last,
|
|
|
|
starting_point,
|
|
|
|
underpath=self.underlay_underpath))
|
|
|
|
stitch_groups.append(underlay)
|
|
|
|
|
|
|
|
starting_point = underlay.stitches[-1]
|
|
|
|
return [stitch_groups, starting_point]
|
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
def do_auto_fill(self, shape, last_patch, starting_point, ending_point):
|
2022-01-30 14:48:51 +00:00
|
|
|
stitch_group = StitchGroup(
|
|
|
|
color=self.color,
|
|
|
|
tags=("auto_fill", "auto_fill_top"),
|
|
|
|
stitches=auto_fill(
|
2022-06-30 17:22:33 +00:00
|
|
|
shape,
|
2022-01-30 14:48:51 +00:00
|
|
|
self.angle,
|
|
|
|
self.row_spacing,
|
|
|
|
self.end_row_spacing,
|
|
|
|
self.max_stitch_length,
|
|
|
|
self.running_stitch_length,
|
2022-06-22 13:26:37 +00:00
|
|
|
self.running_stitch_tolerance,
|
2022-01-30 14:48:51 +00:00
|
|
|
self.staggers,
|
|
|
|
self.skip_last,
|
|
|
|
starting_point,
|
|
|
|
ending_point,
|
|
|
|
self.underpath))
|
|
|
|
return [stitch_group]
|
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
def do_contour_fill(self, polygon, last_patch, starting_point):
|
2022-01-30 14:48:51 +00:00
|
|
|
if not starting_point:
|
|
|
|
starting_point = (0, 0)
|
2022-05-04 01:08:48 +00:00
|
|
|
starting_point = shgeo.Point(starting_point)
|
|
|
|
|
|
|
|
stitch_groups = []
|
2022-06-21 17:59:26 +00:00
|
|
|
tree = contour_fill.offset_polygon(polygon, self.row_spacing, self.join_style + 1, self.clockwise)
|
|
|
|
|
|
|
|
stitches = []
|
|
|
|
if self.contour_strategy == 0:
|
|
|
|
stitches = contour_fill.inner_to_outer(
|
|
|
|
tree,
|
2022-08-26 03:10:16 +00:00
|
|
|
polygon,
|
2022-06-21 17:59:26 +00:00
|
|
|
self.row_spacing,
|
|
|
|
self.max_stitch_length,
|
2022-06-22 13:26:37 +00:00
|
|
|
self.running_stitch_tolerance,
|
2022-08-26 03:10:16 +00:00
|
|
|
self.smoothness,
|
2022-06-21 17:59:26 +00:00
|
|
|
starting_point,
|
|
|
|
self.avoid_self_crossing
|
|
|
|
)
|
|
|
|
elif self.contour_strategy == 1:
|
|
|
|
stitches = contour_fill.single_spiral(
|
|
|
|
tree,
|
|
|
|
self.max_stitch_length,
|
2022-06-22 13:26:37 +00:00
|
|
|
self.running_stitch_tolerance,
|
2022-06-21 17:59:26 +00:00
|
|
|
starting_point
|
|
|
|
)
|
|
|
|
elif self.contour_strategy == 2:
|
|
|
|
stitches = contour_fill.double_spiral(
|
|
|
|
tree,
|
|
|
|
self.max_stitch_length,
|
2022-06-22 13:26:37 +00:00
|
|
|
self.running_stitch_tolerance,
|
2022-06-21 17:59:26 +00:00
|
|
|
starting_point
|
|
|
|
)
|
|
|
|
|
|
|
|
stitch_group = StitchGroup(
|
|
|
|
color=self.color,
|
|
|
|
tags=("auto_fill", "auto_fill_top"),
|
|
|
|
stitches=stitches)
|
|
|
|
stitch_groups.append(stitch_group)
|
2018-03-31 00:37:11 +00:00
|
|
|
|
2021-08-15 21:24:59 +00:00
|
|
|
return stitch_groups
|
2019-08-06 02:42:48 +00:00
|
|
|
|
2022-06-21 17:59:26 +00:00
|
|
|
def do_guided_fill(self, shape, last_patch, starting_point, ending_point):
|
2022-01-30 14:48:51 +00:00
|
|
|
guide_line = self._get_guide_lines()
|
|
|
|
|
|
|
|
# No guide line: fallback to normal autofill
|
|
|
|
if not guide_line:
|
2022-06-22 14:11:12 +00:00
|
|
|
return self.do_auto_fill(shape, last_patch, starting_point, ending_point)
|
2022-01-30 14:48:51 +00:00
|
|
|
|
|
|
|
stitch_group = StitchGroup(
|
|
|
|
color=self.color,
|
2022-02-18 14:36:01 +00:00
|
|
|
tags=("guided_fill", "auto_fill_top"),
|
|
|
|
stitches=guided_fill(
|
2022-06-30 17:22:33 +00:00
|
|
|
shape,
|
2022-01-30 14:48:51 +00:00
|
|
|
guide_line.geoms[0],
|
|
|
|
self.angle,
|
|
|
|
self.row_spacing,
|
2022-06-22 14:11:12 +00:00
|
|
|
self.staggers,
|
2022-01-30 14:48:51 +00:00
|
|
|
self.max_stitch_length,
|
|
|
|
self.running_stitch_length,
|
2022-06-22 14:11:12 +00:00
|
|
|
self.running_stitch_tolerance,
|
2022-01-30 14:48:51 +00:00
|
|
|
self.skip_last,
|
|
|
|
starting_point,
|
|
|
|
ending_point,
|
2022-06-22 14:11:12 +00:00
|
|
|
self.underpath,
|
|
|
|
self.guided_fill_strategy,
|
|
|
|
))
|
2022-01-30 14:48:51 +00:00
|
|
|
return [stitch_group]
|
2019-08-06 02:42:48 +00:00
|
|
|
|
2023-01-16 19:27:06 +00:00
|
|
|
def do_meander_fill(self, shape, starting_point, ending_point):
|
|
|
|
stitch_group = StitchGroup(
|
|
|
|
color=self.color,
|
|
|
|
tags=("meander_fill", "meander_fill_top"),
|
|
|
|
stitches=meander_fill(self, shape, starting_point, ending_point))
|
|
|
|
return [stitch_group]
|
|
|
|
|
2022-01-30 14:48:51 +00:00
|
|
|
@cache
|
|
|
|
def _get_guide_lines(self, multiple=False):
|
|
|
|
guide_lines = get_marker_elements(self.node, "guide-line", False, True)
|
|
|
|
# No or empty guide line
|
2022-02-01 18:47:19 +00:00
|
|
|
if not guide_lines or not guide_lines['stroke']:
|
2022-01-30 14:48:51 +00:00
|
|
|
return None
|
2022-02-01 18:47:19 +00:00
|
|
|
|
2022-01-30 14:48:51 +00:00
|
|
|
if multiple:
|
|
|
|
return guide_lines['stroke']
|
|
|
|
else:
|
|
|
|
return guide_lines['stroke'][0]
|
|
|
|
|
|
|
|
def fatal_fill_error(self):
|
|
|
|
if hasattr(sys, 'gettrace') and sys.gettrace():
|
|
|
|
# if we're debugging, let the exception bubble up
|
|
|
|
raise
|
|
|
|
|
|
|
|
# for an uncaught exception, give a little more info so that they can create a bug report
|
|
|
|
message = ""
|
2022-11-27 07:37:59 +00:00
|
|
|
message += _("Error during autofill! This means it is a bug in Ink/Stitch.")
|
2022-01-30 14:48:51 +00:00
|
|
|
message += "\n\n"
|
|
|
|
# L10N this message is followed by a URL: https://github.com/inkstitch/inkstitch/issues/new
|
2022-11-27 07:37:59 +00:00
|
|
|
message += _("If you'd like to help please\n"
|
|
|
|
"- copy the entire error message below\n"
|
|
|
|
"- save your SVG file and\n"
|
|
|
|
"- create a new issue at")
|
|
|
|
message += " https://github.com/inkstitch/inkstitch/issues/new\n\n"
|
|
|
|
message += _("Include the error description and also (if possible) the svg file.")
|
|
|
|
message += '\n\n\n'
|
|
|
|
message += version.get_inkstitch_version() + '\n'
|
2022-01-30 14:48:51 +00:00
|
|
|
message += traceback.format_exc()
|
|
|
|
|
|
|
|
self.fatal(message)
|