diff options
| author | Kaalleen <36401965+kaalleen@users.noreply.github.com> | 2023-03-07 18:27:28 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-07 18:27:28 +0100 |
| commit | 28eb374bd08f2230c57f6de821c3c8b83b40c0ff (patch) | |
| tree | c3afe66f8dfc16675eeadf4f458120d4d4da3057 | |
| parent | 74157dd1cb9adada35c2079c1e053e0b4b2dec19 (diff) | |
Add extension stroke to lpe satin (#2115)
| -rw-r--r-- | lib/elements/element.py | 3 | ||||
| -rw-r--r-- | lib/elements/stroke.py | 11 | ||||
| -rw-r--r-- | lib/extensions/__init__.py | 4 | ||||
| -rw-r--r-- | lib/extensions/stroke_to_lpe_satin.py | 160 | ||||
| -rw-r--r-- | lib/svg/tags.py | 3 | ||||
| -rw-r--r-- | templates/stroke_to_lpe_satin.xml | 44 |
6 files changed, 222 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') diff --git a/templates/stroke_to_lpe_satin.xml b/templates/stroke_to_lpe_satin.xml new file mode 100644 index 00000000..d4f67bfb --- /dev/null +++ b/templates/stroke_to_lpe_satin.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <name>Stroke to Live Path Effect Satin</name> + <id>org.inkstitch.stroke_lpe_satin</id> + <param name="extension" type="string" gui-hidden="true">stroke_to_lpe_satin</param> + <param name="lpe_satin" type="notebook"> + <page name="options" gui-text="Options"> + <label>Converts a stroke into a satin stitch with a changeable life path effect.</label> + <param name="pattern" type="optiongroup" appearance="combo" gui-text="Pattern"> + <option value="normal">Normal</option> + <option value="pearl">Pearls</option> + <option value="diamond">Diamonds</option> + <option value="square">Squares</option> + <option value="triangle">Triangles</option> + <option value="wave">Wave</option> + <option value="arch">Arch</option> + </param> + <param name="min-width" type="float" precision="1" min="0" max="100" gui-text="Min Width (mm)">4</param> + <param name="max-width" type="float" precision="1" min="0" max="100" gui-text="Max Width (mm)">7</param> + <param name="length" type="float" precision="1" min="0.1" max="100" gui-text="Pattern Length (mm)">15</param> + <param name="stretched" type="boolean" gui-text="Stretched">false</param> + </page> + <page name="info" gui-text="Help"> + <label appearance="header">This extension converts a stroke into a satin column using the path effect "pattern along path".</label> + <label>* Please note, that the size values can only be an approximation and will be distorted if the original path is not completely straight.</label> + <label>* You can edit the satin path through the live path effect settings through Path > Path Effects ...</label> + <label>* If you want to add rungs or change specific parts of the satin, convert it to a normal path with Ctrl + Shift + C</label> + <spacer /> + <label>Get more information on our website</label> + <label appearance="url">https://inkstitch.org/docs/satin-tools/#stroke-to-live-path-effect-satin</label> + </page> + </param> + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu name="Ink/Stitch" translatable="no"> + <submenu name="Tools: Satin" /> + </submenu> + </effects-menu> + </effect> + <script> + {{ command_tag | safe }} + </script> +</inkscape-extension> |
