summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/elements/element.py3
-rw-r--r--lib/elements/stroke.py11
-rw-r--r--lib/extensions/__init__.py4
-rw-r--r--lib/extensions/stroke_to_lpe_satin.py160
-rw-r--r--lib/svg/tags.py3
5 files changed, 178 insertions, 3 deletions
diff --git a/lib/elements/element.py b/lib/elements/element.py
index d3b9f07d..51555129 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -249,7 +249,8 @@ class EmbroideryElement(object):
@property
@param('ties',
_('Allow lock stitches'),
- tooltip=_('Tie thread at the beginning and/or end of this object. Manual stitch will only add lock stitches if force lock stitched is checked.'),
+ tooltip=_('Tie thread at the beginning and/or end of this object. '
+ 'Manual stitch will only add lock stitches if force lock stitched is checked.'),
type='dropdown',
# Ties: 0 = Both | 1 = Before | 2 = After | 3 = Neither
# L10N options to allow lock stitch before and after objects
diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py
index 8f50690c..9fe6fee1 100644
--- a/lib/elements/stroke.py
+++ b/lib/elements/stroke.py
@@ -73,6 +73,14 @@ class Stroke(EmbroideryElement):
def dashed(self):
return self.get_style("stroke-dasharray") is not None
+ def update_dash(self, to_dash):
+ if self.dashed == to_dash:
+ return
+ if to_dash is False:
+ del self.node.style['stroke-dasharray']
+ else:
+ self.node.style['stroke-dasharray'] = "1,0.5"
+
@property
@param('stroke_method',
_('Method'),
@@ -87,7 +95,8 @@ class Stroke(EmbroideryElement):
@property
@param('manual_stitch',
_('Manual stitch placement'),
- tooltip=_("Stitch every node in the path. All options other than stop and trim are ignored. Lock stitches will be added only if force lock stitches is checked."),
+ tooltip=_("Stitch every node in the path. All options other than stop and trim are ignored. "
+ "Lock stitches will be added only if force lock stitches is checked."),
type='boolean',
default=False,
select_items=[('stroke_method', 0)],
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py
index f9f6072b..50623bb6 100644
--- a/lib/extensions/__init__.py
+++ b/lib/extensions/__init__.py
@@ -17,7 +17,6 @@ from .cut_satin import CutSatin
from .cutwork_segmentation import CutworkSegmentation
from .density_map import DensityMap
from .duplicate_params import DuplicateParams
-from .preferences import Preferences
from .fill_to_stroke import FillToStroke
from .flip import Flip
from .generate_palette import GeneratePalette
@@ -40,6 +39,7 @@ from .output import Output
from .palette_split_text import PaletteSplitText
from .palette_to_text import PaletteToText
from .params import Params
+from .preferences import Preferences
from .print_pdf import Print
from .remove_embroidery_settings import RemoveEmbroiderySettings
from .reorder import Reorder
@@ -48,6 +48,7 @@ from .selection_to_pattern import SelectionToPattern
from .simulator import Simulator
from .stitch_plan_preview import StitchPlanPreview
from .stitch_plan_preview_undo import StitchPlanPreviewUndo
+from .stroke_to_lpe_satin import StrokeToLpeSatin
from .zip import Zip
from.lettering_along_path import LetteringAlongPath
@@ -70,6 +71,7 @@ __all__ = extensions = [StitchPlanPreview,
GlobalCommands,
CommandsScaleSymbols,
ConvertToSatin,
+ StrokeToLpeSatin,
ConvertToStroke,
JumpToStroke,
FillToStroke,
diff --git a/lib/extensions/stroke_to_lpe_satin.py b/lib/extensions/stroke_to_lpe_satin.py
new file mode 100644
index 00000000..20c1e07d
--- /dev/null
+++ b/lib/extensions/stroke_to_lpe_satin.py
@@ -0,0 +1,160 @@
+# 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 inkex
+
+from ..elements import SatinColumn, Stroke
+from ..i18n import _
+from ..svg.tags import ORIGINAL_D, PATH_EFFECT, SODIPODI_NODETYPES
+from .base import InkstitchExtension
+
+
+class StrokeToLpeSatin(InkstitchExtension):
+ """Convert a satin column into a running stitch."""
+
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("--lpe_satin", type=str, default=None)
+ self.arg_parser.add_argument("--options", type=str, default=None)
+ self.arg_parser.add_argument("--info", type=str, default=None)
+
+ self.arg_parser.add_argument("-p", "--pattern", type=str, default="normal", dest="pattern")
+ self.arg_parser.add_argument("-i", "--min-width", type=float, default=1.5, dest="min_width")
+ self.arg_parser.add_argument("-a", "--max-width", type=float, default=7, dest="max_width")
+ self.arg_parser.add_argument("-l", "--length", type=float, default=15, dest="length")
+ self.arg_parser.add_argument("-t", "--stretched", type=inkex.Boolean, default=False, dest="stretched")
+
+ def effect(self):
+ if not self.svg.selection or not self.get_elements():
+ inkex.errormsg(_("Please select at least one stroke."))
+ return
+
+ 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."))
+ return
+
+ pattern = self.options.pattern
+ if pattern not in satin_patterns:
+ inkex.errormsg(_("Could not find the specified pattern."))
+ return
+
+ # convert user input values to the units of the current svg
+ min_width = inkex.units.convert_unit(str(max(self.options.min_width, 0.5)) + 'mm', self.svg.unit)
+ max_width = inkex.units.convert_unit(str(max(self.options.max_width, 0.5)) + 'mm', self.svg.unit)
+ length = inkex.units.convert_unit(str(self.options.length) + 'mm', self.svg.unit)
+
+ # get pattern path and nodetypes
+ pattern_obj = satin_patterns[pattern]
+ pattern_path = pattern_obj.get_path(min_width, max_width, length, self.svg.unit)
+ pattern_node_type = pattern_obj.node_types
+
+ # the lpe 'pattern along path' has two options to repeat the pattern, get user input
+ copy_type = 'repeated' if self.options.stretched is False else 'repeated_stretched'
+
+ # add the path effect element to the defs section
+ self.lpe = inkex.PathEffect(attrib={'id': f'inkstitch-effect-{pattern}',
+ 'effect': "skeletal",
+ 'is_visible': "true",
+ 'lpeversion': "1",
+ 'pattern': pattern_path,
+ 'copytype': copy_type,
+ 'prop_scale': "1",
+ 'scale_y_rel': "false",
+ 'spacing': "0",
+ 'normal_offset': "0",
+ 'tang_offset': "0",
+ 'prop_units': "false",
+ 'vertical_pattern': "false",
+ 'hide_knot': "false",
+ 'fuse_tolerance': "0.02",
+ 'pattern-nodetypes': pattern_node_type})
+ self.svg.defs.add(self.lpe)
+
+ for element in self.elements:
+ if isinstance(element, SatinColumn):
+ self._process_satin_column(element)
+ elif isinstance(element, Stroke):
+ self._process_stroke(element)
+
+ def _process_stroke(self, element):
+ previous_effects = element.node.get(PATH_EFFECT, '')
+ url = previous_effects + ';' + self.lpe.get_id(as_url=1)
+ element.set_param('satin_column', 'true')
+ element.node.set(PATH_EFFECT, url)
+ if not previous_effects:
+ element.node.set(ORIGINAL_D, element.node.get('d', ''))
+ element.node.pop('d')
+
+ element.node.style['stroke-width'] = self.svg.viewport_to_unit('0.756')
+ # remove running_stitch dashes if they are there
+ element.update_dash(False)
+
+ def _process_satin_column(self, element):
+ current_effects = element.node.get(PATH_EFFECT, None)
+ # there are possibly multiple path effects, let's check if inkstitch-effect is among them
+ if not current_effects or 'inkstitch-effect' not in current_effects:
+ # it wouldn't make sense to apply it to a normal satin column without the inkstitch-effect
+ inkex.errormsg(_('Cannot convert a satin column into a live path effect satin. Please select a stroke.'))
+ return
+ # isolate get the inkstitch effect
+ current_effects = current_effects.split(';')
+ inkstitch_effect_position = [i for i, effect in enumerate(current_effects) if 'inkstitch-effect' in effect][0]
+ inkstitch_effect = current_effects[inkstitch_effect_position][1:]
+ # get the path effect element
+ old_effect_element = self.svg.getElementById(inkstitch_effect)
+ # remove the old inkstitch-effect
+ old_effect_element.getparent().remove(old_effect_element)
+ # update the path effect link
+ current_effects[inkstitch_effect_position] = self.lpe.get_id(as_url=1)
+ element.node.set(PATH_EFFECT, ';'.join(current_effects))
+ element.node.pop('d')
+
+
+class SatinPattern:
+ def __init__(self, path=None, node_types=None, flip=True):
+ self.path: str = path
+ self.node_types: str = node_types
+ self.flip: bool = flip
+
+ def get_path(self, min_width, max_width, length, to_unit):
+ # scale the pattern path to fit the unit of the current svg
+ scale_factor = scale_factor = 1 / inkex.units.convert_unit('1mm', f'{to_unit}')
+ pattern_path = inkex.Path(self.path).transform(inkex.Transform(f'scale({scale_factor})'), True)
+
+ # create a path element
+ el1 = inkex.PathElement(attrib={'d': str(pattern_path),
+ SODIPODI_NODETYPES: self.node_types})
+
+ # transform to fit user input size values
+ bbox = el1.bounding_box()
+ scale_x = length / max(bbox.width, 0.1)
+ if bbox.height == 0:
+ scale_y = 1
+ else:
+ scale_y = (max_width - min_width) / (bbox.height * 2)
+ el1.transform = inkex.Transform(f'scale({scale_x}, {scale_y})')
+ el1.apply_transform()
+ path1 = el1.get_path()
+
+ # copy first path and (optionally) flip it to generate the second satin rail
+ el2 = el1.copy()
+ if self.flip:
+ el2.transform = inkex.Transform(f'scale(1, -1) translate(0, {min_width})')
+ else:
+ el2.transform = inkex.Transform(f'translate(0, {-min_width})')
+ el2.apply_transform()
+ path2 = el2.get_path()
+
+ return str(path1) + str(path2)
+
+
+satin_patterns = {'normal': SatinPattern('M 0,0.4 H 8', 'cc'),
+ 'pearl': SatinPattern('M 0,0 C 0,0.22 0.18,0.4 0.4,0.4 0.62,0.4 0.8,0.22 0.8,0', 'csc'),
+ 'diamond': SatinPattern('M 0,0 0.4,0.2 0.8,0', 'ccc'),
+ 'triangle': SatinPattern('M 0.0,0 0.8,0.2 V 0', 'cccc'),
+ 'square': SatinPattern('M 0,0 H 0.2 0.4 V 0.2 H 0.8 V 0', 'ccccc'),
+ 'wave': SatinPattern('M 0,0 C 0.2,0.01 0.29,0.2 0.4,0.2 0.51,0.2 0.58,0.01 0.8,0', 'cac'),
+ 'arch': SatinPattern('M 0,0.25 C 0,0.25 0.07,0.05 0.4,0.05 0.7,0.05 0.8,0.25 0.8,0.25', 'czcczc', False)}
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
index 60250dec..7b0da93d 100644
--- a/lib/svg/tags.py
+++ b/lib/svg/tags.py
@@ -33,6 +33,8 @@ CONNECTION_START = inkex.addNS('connection-start', 'inkscape')
CONNECTION_END = inkex.addNS('connection-end', 'inkscape')
CONNECTOR_TYPE = inkex.addNS('connector-type', 'inkscape')
INKSCAPE_DOCUMENT_UNITS = inkex.addNS('document-units', 'inkscape')
+ORIGINAL_D = inkex.addNS('original-d', 'inkscape')
+PATH_EFFECT = inkex.addNS('path-effect', 'inkscape')
XLINK_HREF = inkex.addNS('href', 'xlink')
@@ -40,6 +42,7 @@ SODIPODI_NAMEDVIEW = inkex.addNS('namedview', 'sodipodi')
SODIPODI_GUIDE = inkex.addNS('guide', 'sodipodi')
SODIPODI_ROLE = inkex.addNS('role', 'sodipodi')
SODIPODI_INSENSITIVE = inkex.addNS('insensitive', 'sodipodi')
+SODIPODI_NODETYPES = inkex.addNS('nodetypes', 'sodipodi')
INKSTITCH_LETTERING = inkex.addNS('lettering', 'inkstitch')