summaryrefslogtreecommitdiff
path: root/lib/extensions
diff options
context:
space:
mode:
authorGeorge Steel <george.steel@gmail.com>2022-12-10 19:27:20 -0500
committerGeorge Steel <george.steel@gmail.com>2022-12-10 19:29:01 -0500
commit2cec72cbbd73b2dece5d15715e2ab8bddd417c69 (patch)
treeaff4569cb68f626e05489a86511fbd4be8325e17 /lib/extensions
parent495a22ea55234e032ac526450fd7969d9aa799c8 (diff)
parente52f889bb0cb208685b3f3ef8271f63ecd318af1 (diff)
Merge branch 'george-steel/expose-trim-after'
Diffstat (limited to 'lib/extensions')
-rw-r--r--lib/extensions/__init__.py3
-rw-r--r--lib/extensions/convert_to_satin.py10
-rw-r--r--lib/extensions/lettering.py4
-rw-r--r--lib/extensions/lettering_along_path.py144
4 files changed, 156 insertions, 5 deletions
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py
index 5c702ce8..a2261a80 100644
--- a/lib/extensions/__init__.py
+++ b/lib/extensions/__init__.py
@@ -48,6 +48,8 @@ from .stitch_plan_preview import StitchPlanPreview
from .stitch_plan_preview_undo import StitchPlanPreviewUndo
from .zip import Zip
+from.lettering_along_path import LetteringAlongPath
+
__all__ = extensions = [StitchPlanPreview,
StitchPlanPreviewUndo,
DensityMap,
@@ -75,6 +77,7 @@ __all__ = extensions = [StitchPlanPreview,
LetteringRemoveKerning,
LetteringCustomFontDir,
LetteringForceLockStitches,
+ LetteringAlongPath,
LettersToFont,
Troubleshoot,
RemoveEmbroiderySettings,
diff --git a/lib/extensions/convert_to_satin.py b/lib/extensions/convert_to_satin.py
index 93850789..5512a095 100644
--- a/lib/extensions/convert_to_satin.py
+++ b/lib/extensions/convert_to_satin.py
@@ -129,11 +129,16 @@ class ConvertToSatin(InkstitchExtension):
if Point(*path[0]).distance(Point(*path[-1])) < 1:
raise SelfIntersectionError()
+ # Shapely is supposed to return right sided offsets in reversed direction, which it does, except for macOS.
+ # To avoid direction checking, we are going to rely on left side offsets only.
+ # Therefore we need to reverse the original path.
+ reversed_path = shgeo.LineString(reversed(path))
path = shgeo.LineString(path)
+ distance = stroke_width / 2.0
try:
- left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
- right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
+ left_rail = path.parallel_offset(distance, 'left', **style_args)
+ right_rail = reversed_path.parallel_offset(distance, 'left', **style_args)
except ValueError:
# TODO: fix this error automatically
# Error reference: https://github.com/inkstitch/inkstitch/issues/964
@@ -149,7 +154,6 @@ class ConvertToSatin(InkstitchExtension):
# https://shapely.readthedocs.io/en/latest/manual.html#object.parallel_offset
raise SelfIntersectionError()
- # for whatever reason, shapely returns a right-side offset's coordinates in reverse
left_rail = list(left_rail.coords)
right_rail = list(reversed(right_rail.coords))
diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py
index 40fd48af..0d18449a 100644
--- a/lib/extensions/lettering.py
+++ b/lib/extensions/lettering.py
@@ -71,8 +71,8 @@ class LetteringFrame(wx.Frame):
self.back_and_forth_checkbox = wx.CheckBox(self, label=_("Stitch lines of text back and forth"))
self.back_and_forth_checkbox.Bind(wx.EVT_CHECKBOX, lambda event: self.on_change("back_and_forth", event))
- self.trim_option_choice = wx.Choice(self, choices=["Never", "after each line", "after each word", "after each letter"],
- name=_("Add trim after"))
+ self.trim_option_choice = wx.Choice(self, choices=[_("Never"), _("after each line"), _("after each word"), _("after each letter")],
+ name=_("Add trim command"))
self.trim_option_choice.Bind(wx.EVT_CHOICE, lambda event: self.on_trim_option_change(event))
# text editor
diff --git a/lib/extensions/lettering_along_path.py b/lib/extensions/lettering_along_path.py
new file mode 100644
index 00000000..63ffc817
--- /dev/null
+++ b/lib/extensions/lettering_along_path.py
@@ -0,0 +1,144 @@
+# Authors: see git history
+#
+# Copyright (c) 2021 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+import json
+import sys
+from math import atan2, degrees
+
+from inkex import Boolean, Transform, errormsg
+from shapely.ops import substring
+
+from ..elements import Stroke
+from ..i18n import _
+from ..svg import get_correction_transform
+from ..svg.tags import EMBROIDERABLE_TAGS, INKSTITCH_LETTERING, SVG_GROUP_TAG
+from ..utils import DotDict
+from ..utils import Point as InkstitchPoint
+from .base import InkstitchExtension
+
+
+class LetteringAlongPath(InkstitchExtension):
+ '''
+ This extension aligns an Ink/Stitch Lettering group along a path
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-o", "--options", type=str, default=None, dest="page_1")
+ self.arg_parser.add_argument("-i", "--info", type=str, default=None, dest="page_2")
+ self.arg_parser.add_argument("-s", "--stretch-spaces", type=Boolean, default=False, dest="stretch_spaces")
+
+ def effect(self):
+ # we ignore everything but the first path/text
+ text, path = self.get_selection()
+ self.load_settings(text)
+
+ glyphs = [glyph for glyph in text.iterdescendants(SVG_GROUP_TAG) if len(glyph.label) == 1]
+ if not glyphs:
+ errormsg(_("The text doesn't contain any glyphs."))
+ sys.exit(1)
+
+ path = Stroke(path).as_multi_line_string().geoms[0]
+ path_length = path.length
+
+ # overall bounding box - get from direct glyph parent
+ text_bbox = glyphs[0].getparent().bounding_box()
+ text_y = text_bbox.bottom
+
+ if self.options.stretch_spaces:
+ text_content = self.settings["text"]
+ space_indices = [i for i, t in enumerate(text_content) if t == " "]
+ text_width = text_bbox.width
+
+ if len(text_content) - 1 != 0:
+ stretch_space = (path_length - text_width) / (len(text_content) - 1)
+ else:
+ stretch_space = 0
+ else:
+ stretch_space = 0
+ space_indices = []
+
+ self.transform_glyphs(glyphs, path, stretch_space, space_indices, text_y)
+
+ def transform_glyphs(self, glyphs, path, stretch_space, space_indices, text_y):
+ text_scale = Transform(f'scale({self.settings["scale"] / 100})')
+ distance = 0
+ old_bbox = None
+ i = 0
+
+ for glyph in glyphs:
+ # dimensions
+ bbox = glyph.bounding_box()
+ left = bbox.left
+ width = bbox.width
+
+ # adjust position
+ if old_bbox:
+ right = old_bbox.right
+ distance += left - right + stretch_space
+
+ if self.options.stretch_spaces and i in space_indices:
+ distance += stretch_space
+ i += 1
+
+ new_distance = distance + width
+
+ # calculate and apply transform
+ first = substring(path, distance, distance)
+ last = substring(path, new_distance, new_distance)
+
+ angle = degrees(atan2(last.y - first.y, last.x - first.x)) % 360
+ translate = InkstitchPoint(first.x, first.y) - InkstitchPoint(left, text_y)
+
+ transform = Transform(f"rotate({angle}, {first.x}, {first.y}) translate({translate.x} {translate.y})")
+ correction_transform = Transform(get_correction_transform(glyph))
+ glyph.transform = correction_transform @ transform @ glyph.transform @ text_scale
+
+ # set values for next iteration
+ distance = new_distance
+ old_bbox = bbox
+ i += 1
+
+ def load_settings(self, text):
+ """Load the settings saved into the text element"""
+
+ self.settings = DotDict({
+ "text": "",
+ "back_and_forth": False,
+ "font": None,
+ "scale": 100,
+ "trim_option": 0
+ })
+
+ if INKSTITCH_LETTERING in text.attrib:
+ try:
+ self.settings.update(json.loads(text.get(INKSTITCH_LETTERING)))
+ except (TypeError, ValueError):
+ pass
+
+ def get_selection(self):
+ groups = list()
+ paths = list()
+
+ for node in self.svg.selection:
+ lettering = False
+ if node.tag == SVG_GROUP_TAG and INKSTITCH_LETTERING in node.attrib:
+ groups.append(node)
+ lettering = True
+ continue
+
+ for group in node.iterancestors(SVG_GROUP_TAG):
+ if INKSTITCH_LETTERING in group.attrib:
+ groups.append(group)
+ lettering = True
+ break
+
+ if not lettering and node.tag in EMBROIDERABLE_TAGS:
+ paths.append(node)
+
+ if not groups or not paths:
+ errormsg(_("Please select one path and one Ink/Stitch lettering group."))
+ sys.exit(1)
+
+ return [groups[0], paths[0]]