summaryrefslogtreecommitdiff
path: root/lib/elements
diff options
context:
space:
mode:
Diffstat (limited to 'lib/elements')
-rw-r--r--lib/elements/__init__.py2
-rw-r--r--lib/elements/auto_fill.py281
-rw-r--r--lib/elements/clone.py10
-rw-r--r--lib/elements/element.py4
-rw-r--r--lib/elements/fill.py205
-rw-r--r--lib/elements/utils.py10
6 files changed, 263 insertions, 249 deletions
diff --git a/lib/elements/__init__.py b/lib/elements/__init__.py
index 2e4c31a7..bb5c95ba 100644
--- a/lib/elements/__init__.py
+++ b/lib/elements/__init__.py
@@ -7,7 +7,7 @@ from .auto_fill import AutoFill
from .clone import Clone
from .element import EmbroideryElement
from .empty_d_object import EmptyDObject
-from .fill import Fill
+#from .fill import Fill
from .image import ImageObject
from .polyline import Polyline
from .satin_column import SatinColumn
diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py
index fbbd86d3..87bdb010 100644
--- a/lib/elements/auto_fill.py
+++ b/lib/elements/auto_fill.py
@@ -6,18 +6,26 @@
import math
import sys
import traceback
+import re
+import logging
+import inkex
from shapely import geometry as shgeo
-
-from .element import param
-from .fill import Fill
-from .validation import ValidationWarning
+from shapely.validation import explain_validity
+from ..stitches import legacy_fill
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..stitches import auto_fill
-from ..svg.tags import INKSCAPE_LABEL
+from ..stitches import StitchPattern
from ..utils import cache, version
-
+from .element import param
+from .element import EmbroideryElement
+from ..patterns import get_patterns
+#from .fill import Fill
+from .validation import ValidationWarning
+from ..utils import Point as InkstitchPoint
+from ..svg import PIXELS_PER_MM
+from ..svg.tags import INKSCAPE_LABEL
class SmallShapeWarning(ValidationWarning):
name = _("Small Fill")
@@ -38,13 +46,125 @@ class UnderlayInsetWarning(ValidationWarning):
"Ink/Stitch will ignore it and will use the original size instead.")
-class AutoFill(Fill):
+class AutoFill(EmbroideryElement):
element_name = _("AutoFill")
@property
- @param('auto_fill', _('Automatically routed fill stitching'), type='toggle', default=True)
- def auto_fill(self):
- return self.get_boolean_param('auto_fill', True)
+ @param('auto_fill', _('Automatically routed fill stitching'), type='toggle', default=True, sort_index = 1)
+ def auto_fill2(self):
+ return self.get_boolean_param('auto_fill', True)
+
+ @property
+ @param('fill_method', _('Fill method'), type='dropdown', default=0, options=[_("Auto Fill"), _("Tangential"), _("Guided Auto Fill")], sort_index = 2)
+ def fill_method(self):
+ return self.get_int_param('fill_method', 0)
+
+ @property
+ @param('tangential_strategy', _('Tangential strategy'), type='dropdown', default=1, options=[_("Closest point"), _("Inner to Outer")],select_items=[('fill_method',1)], sort_index = 2)
+ def tangential_strategy(self):
+ return self.get_int_param('tangential_strategy', 1)
+
+ @property
+ @param('join_style', _('Join Style'), type='dropdown', default=0, options=[_("Round"), _("Mitered"), _("Beveled")],select_items=[('fill_method',1)], sort_index = 2)
+ def join_style(self):
+ return self.get_int_param('join_style', 0)
+
+ @property
+ @param('interlaced', _('Interlaced'), type='boolean', default=True,select_items=[('fill_method',1),('fill_method',2)], sort_index = 2)
+ def interlaced(self):
+ return self.get_boolean_param('interlaced', True)
+
+ @property
+ @param('angle',
+ _('Angle of lines of stitches'),
+ tooltip=_('The angle increases in a counter-clockwise direction. 0 is horizontal. Negative angles are allowed.'),
+ unit='deg',
+ type='float',
+ sort_index = 4,
+ select_items=[('fill_method',0)],
+ 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',
+ sort_index = 4,
+ select_items=[('fill_method',0), ('fill_method',2)],
+ 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',
+ sort_index = 4,
+ select_items=[('fill_method',0), ('fill_method',2)],
+ 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',
+ sort_index = 4,
+ 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'),
+ tooltip=_('The length of each stitch in a row. Shorter stitch may be used at the start or end of a row.'),
+ unit='mm',
+ sort_index = 4,
+ 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'),
+ tooltip=_('Setting this dictates how many rows apart the stitches will be before they fall in the same column position.'),
+ type='int',
+ sort_index = 4,
+ select_items=[('fill_method',0)],
+ default=4)
+ def staggers(self):
+ return max(self.get_int_param("staggers", 4), 1)
+
+ @property
+ @cache
+ def paths(self):
+ paths = self.flatten(self.parse_path())
+ # ensure path length
+ for i, path in enumerate(paths):
+ if len(path) < 3:
+ paths[i] = [(path[0][0], path[0][1]), (path[0][0]+1.0, path[0][1]), (path[0][0], path[0][1]+1.0)]
+ return paths
+
@property
@cache
@@ -66,7 +186,9 @@ class AutoFill(Fill):
tooltip=_('Length of stitches around the outline of the fill region used when moving from section to section.'),
unit='mm',
type='float',
- default=1.5)
+ default=1.5,
+ select_items=[('fill_method',0),('fill_method',2)],
+ sort_index = 4)
def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
@@ -147,7 +269,9 @@ class AutoFill(Fill):
tooltip=_('Expand the shape before fill stitching, to compensate for gaps between shapes.'),
unit='mm',
type='float',
- default=0)
+ default=0,
+ sort_index = 5,
+ select_items=[('fill_method',0),('fill_method',2)])
def expand(self):
return self.get_float_param('expand_mm', 0)
@@ -158,7 +282,9 @@ class AutoFill(Fill):
'stitches avoid traveling in the direction of the row angle so that they '
'are not visible. This gives them a jagged appearance.'),
type='boolean',
- default=True)
+ default=True,
+ select_items=[('fill_method',0),('fill_method',2)],
+ sort_index = 6)
def underpath(self):
return self.get_boolean_param('underpath', True)
@@ -175,6 +301,51 @@ class AutoFill(Fill):
def underlay_underpath(self):
return self.get_boolean_param('underlay_underpath', True)
+ @property
+ @cache
+ def shape(self):
+ # 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
+ paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
+ # 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]
+
+ polygon = shgeo.MultiPolygon([(paths[0], paths[1:])])
+
+ # There is a great number of "crossing border" errors on fill shapes
+ # If the polygon fails, we can try to run buffer(0) on the polygon in the
+ # hope it will fix at least some of them
+ if not self.shape_is_valid(polygon):
+ why = explain_validity(polygon)
+ message = re.match(r".+?(?=\[)", why)
+ if message.group(0) == "Self-intersection":
+ buffered = polygon.buffer(0)
+ # we do not want to break apart into multiple objects (possibly in the future?!)
+ # best way to distinguish the resulting polygon is to compare the area size of the two
+ # and make sure users will not experience significantly altered shapes without a warning
+ if math.isclose(polygon.area, buffered.area):
+ polygon = shgeo.MultiPolygon([buffered])
+
+ return polygon
+
+ 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 shrink_or_grow_shape(self, amount, validate=False):
if amount:
shape = self.shape.buffer(amount)
@@ -226,7 +397,8 @@ class AutoFill(Fill):
color=self.color,
tags=("auto_fill", "auto_fill_underlay"),
stitches=auto_fill(
- self.underlay_shape,
+ self.underlay_shape,
+ None,
self.fill_underlay_angle[i],
self.fill_underlay_row_spacing,
self.fill_underlay_row_spacing,
@@ -237,25 +409,70 @@ class AutoFill(Fill):
starting_point,
underpath=self.underlay_underpath))
stitch_groups.append(underlay)
+ starting_point = underlay.stitches[-1]
+
+ if self.fill_method == 0: #Auto Fill
+ stitch_group = StitchGroup(
+ color=self.color,
+ tags=("auto_fill", "auto_fill_top"),
+ stitches=auto_fill(
+ self.fill_shape,
+ None,
+ self.angle,
+ self.row_spacing,
+ self.end_row_spacing,
+ self.max_stitch_length,
+ self.running_stitch_length,
+ self.staggers,
+ self.skip_last,
+ starting_point,
+ ending_point,
+ self.underpath))
+ stitch_groups.append(stitch_group)
+ elif self.fill_method == 1: #Tangential Fill
+ polygons = list(self.fill_shape)
+ if not starting_point:
+ starting_point = (0,0)
+ for poly in polygons:
+ connectedLine, connectedLineOrigin = StitchPattern.offset_poly(
+ poly,
+ -self.row_spacing,
+ self.join_style+1,
+ self.max_stitch_length,
+ self.interlaced,
+ self.tangential_strategy,
+ shgeo.Point(starting_point))
+ path = [InkstitchPoint(*p) for p in connectedLine]
+ stitch_group = StitchGroup(
+ color=self.color,
+ tags=("auto_fill", "auto_fill_top"),
+ stitches=path)
+ stitch_groups.append(stitch_group)
+ elif self.fill_method == 2: #Guided Auto Fill
+ lines = get_patterns(self.node,"#inkstitch-guide-line-marker")
+ lines = lines['stroke_patterns']
+ if not lines or lines[0].is_empty:
+ inkex.errormsg(_("No line marked as guide line found within the same group as patch"))
+ else:
+ stitch_group = StitchGroup(
+ color=self.color,
+ tags=("auto_fill", "auto_fill_top"),
+ stitches=auto_fill(
+ self.fill_shape,
+ lines[0].geoms[0],
+ self.angle,
+ self.row_spacing,
+ self.end_row_spacing,
+ self.max_stitch_length,
+ self.running_stitch_length,
+ 0,
+ self.skip_last,
+ starting_point,
+ ending_point,
+ self.underpath,
+ self.interlaced))
+ stitch_groups.append(stitch_group)
- starting_point = underlay.stitches[-1]
-
- stitch_group = StitchGroup(
- color=self.color,
- tags=("auto_fill", "auto_fill_top"),
- stitches=auto_fill(
- self.fill_shape,
- self.angle,
- self.row_spacing,
- self.end_row_spacing,
- self.max_stitch_length,
- self.running_stitch_length,
- self.staggers,
- self.skip_last,
- starting_point,
- ending_point,
- self.underpath))
- stitch_groups.append(stitch_group)
except Exception:
if hasattr(sys, 'gettrace') and sys.gettrace():
# if we're debugging, let the exception bubble up
diff --git a/lib/elements/clone.py b/lib/elements/clone.py
index f408917d..bcecf3f0 100644
--- a/lib/elements/clone.py
+++ b/lib/elements/clone.py
@@ -14,7 +14,7 @@ from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS,
from ..utils import cache
from .auto_fill import AutoFill
from .element import EmbroideryElement, param
-from .fill import Fill
+#from .fill import Fill
from .polyline import Polyline
from .satin_column import SatinColumn
from .stroke import Stroke
@@ -79,10 +79,10 @@ class Clone(EmbroideryElement):
else:
elements = []
if element.get_style("fill", "black") and not element.get_style("stroke", 1) == "0":
- if element.get_boolean_param("auto_fill", True):
- elements.append(AutoFill(node))
- else:
- elements.append(Fill(node))
+ #if element.get_boolean_param("auto_fill", True):
+ elements.append(AutoFill(node))
+ #else:
+ # elements.append(Fill(node))
if element.get_style("stroke", self.node) is not None:
if not is_command(element.node):
elements.append(Stroke(node))
diff --git a/lib/elements/element.py b/lib/elements/element.py
index 05bfd353..b8728f60 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -20,7 +20,7 @@ from ..utils import Point, cache
class Param(object):
def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False,
- options=[], default=None, tooltip=None, sort_index=0):
+ options=[], default=None, tooltip=None, sort_index=0, select_items=None):
self.name = name
self.description = description
self.unit = unit
@@ -32,6 +32,8 @@ class Param(object):
self.default = default
self.tooltip = tooltip
self.sort_index = sort_index
+ self.select_items = select_items
+ #print("IN PARAM: ", self.values)
def __repr__(self):
return "Param(%s)" % vars(self)
diff --git a/lib/elements/fill.py b/lib/elements/fill.py
deleted file mode 100644
index 51a6d703..00000000
--- a/lib/elements/fill.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# Authors: see git history
-#
-# Copyright (c) 2010 Authors
-# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-
-import logging
-import math
-import re
-
-from shapely import geometry as shgeo
-from shapely.validation import explain_validity
-
-from .element import EmbroideryElement, param
-from .validation import ValidationError
-from ..i18n import _
-from ..stitch_plan import StitchGroup
-from ..stitches import legacy_fill
-from ..svg import PIXELS_PER_MM
-from ..utils import cache
-
-
-class UnconnectedError(ValidationError):
- name = _("Unconnected")
- description = _("Fill: This object is made up of unconnected shapes. This is not allowed because "
- "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'),
- ]
-
-
-class InvalidShapeError(ValidationError):
- name = _("Border crosses itself")
- description = _("Fill: Shape is not valid. This can happen if the border crosses over itself.")
- steps_to_solve = [
- _('* Extensions > Ink/Stitch > Fill Tools > Break Apart Fill Objects')
- ]
-
-
-class Fill(EmbroideryElement):
- element_name = _("Fill")
-
- def __init__(self, *args, **kwargs):
- super(Fill, self).__init__(*args, **kwargs)
-
- @property
- @param('auto_fill',
- _('Manually routed fill stitching'),
- tooltip=_('AutoFill is the default method for generating fill stitching.'),
- type='toggle',
- inverse=True,
- default=True)
- def auto_fill(self):
- return self.get_boolean_param('auto_fill', True)
-
- @property
- @param('angle',
- _('Angle of lines of stitches'),
- tooltip=_('The angle increases in a counter-clockwise direction. 0 is horizontal. Negative angles are allowed.'),
- unit='deg',
- type='float',
- 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',
- 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',
- 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',
- 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'),
- tooltip=_('The length of each stitch in a row. Shorter stitch may be used at the start or end of a row.'),
- unit='mm',
- 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'),
- tooltip=_('Setting this dictates how many rows apart the stitches will be before they fall in the same column position.'),
- type='int',
- default=4)
- def staggers(self):
- return max(self.get_int_param("staggers", 4), 1)
-
- @property
- @cache
- def paths(self):
- paths = self.flatten(self.parse_path())
- # ensure path length
- for i, path in enumerate(paths):
- if len(path) < 3:
- paths[i] = [(path[0][0], path[0][1]), (path[0][0]+1.0, path[0][1]), (path[0][0], path[0][1]+1.0)]
- return paths
-
- @property
- @cache
- def shape(self):
- # 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
- paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
- # 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]
-
- polygon = shgeo.MultiPolygon([(paths[0], paths[1:])])
-
- # There is a great number of "crossing border" errors on fill shapes
- # If the polygon fails, we can try to run buffer(0) on the polygon in the
- # hope it will fix at least some of them
- if not self.shape_is_valid(polygon):
- why = explain_validity(polygon)
- message = re.match(r".+?(?=\[)", why)
- if message.group(0) == "Self-intersection":
- buffered = polygon.buffer(0)
- # if we receive a multipolygon, only use the first one of it
- if type(buffered) == shgeo.MultiPolygon:
- buffered = buffered[0]
- # we do not want to break apart into multiple objects (possibly in the future?!)
- # best way to distinguish the resulting polygon is to compare the area size of the two
- # and make sure users will not experience significantly altered shapes without a warning
- if type(buffered) == shgeo.Polygon and math.isclose(polygon.area, buffered.area, abs_tol=0.5):
- polygon = shgeo.MultiPolygon([buffered])
-
- return polygon
-
- 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)
-
- # I Wish this weren't so brittle...
- if "Hole lies outside shell" in message:
- yield UnconnectedError((x, y))
- else:
- yield InvalidShapeError((x, y))
-
- def to_stitch_groups(self, last_patch):
- 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]
diff --git a/lib/elements/utils.py b/lib/elements/utils.py
index 99df7002..f858cc81 100644
--- a/lib/elements/utils.py
+++ b/lib/elements/utils.py
@@ -11,7 +11,7 @@ from .auto_fill import AutoFill
from .clone import Clone, is_clone
from .element import EmbroideryElement
from .empty_d_object import EmptyDObject
-from .fill import Fill
+#from .fill import Fill
from .image import ImageObject
from .pattern import PatternObject
from .polyline import Polyline
@@ -41,10 +41,10 @@ def node_to_elements(node): # noqa: C901
else:
elements = []
if element.get_style("fill", "black") and not element.get_style('fill-opacity', 1) == "0":
- if element.get_boolean_param("auto_fill", True):
- elements.append(AutoFill(node))
- else:
- elements.append(Fill(node))
+ #if element.get_boolean_param("auto_fill", True):
+ elements.append(AutoFill(node))
+ #else:
+ # elements.append(Fill(node))
if element.get_style("stroke"):
if not is_command(element.node):
elements.append(Stroke(node))