From 958cb4210a89abc38ac07b03b42604e7e4b37157 Mon Sep 17 00:00:00 2001
From: Kaalleen <36401965+kaalleen@users.noreply.github.com>
Date: Thu, 9 May 2024 10:32:08 +0200
Subject: [PATCH] Rewrite outline extensions (#2881)
---
lib/extensions/outline.py | 72 ++++++++++++++++++++++++++++-----------
templates/outline.xml | 17 +++++++--
2 files changed, 67 insertions(+), 22 deletions(-)
diff --git a/lib/extensions/outline.py b/lib/extensions/outline.py
index d6a16e993..364cec6dd 100644
--- a/lib/extensions/outline.py
+++ b/lib/extensions/outline.py
@@ -3,48 +3,80 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-import inkex
-from shapely import concave_hull
-from shapely.geometry import LineString, MultiPolygon
+from inkex import Boolean, Path, errormsg
+from shapely import offset_curve
+from shapely.geometry import LineString, MultiPolygon, Polygon
from ..i18n import _
+from ..svg import PIXELS_PER_MM
from ..svg.tags import SVG_PATH_TAG
+from ..utils.geometry import ensure_multi_line_string
+from ..utils.smoothing import smooth_path
from .base import InkstitchExtension
class Outline(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.arg_parser.add_argument("-r", "--ratio", type=float, default=0.0, dest="ratio")
- self.arg_parser.add_argument("-a", "--allow-holes", type=inkex.Boolean, default=False, dest="allow_holes")
+ self.arg_parser.add_argument("-k", "--keep-original", type=Boolean, default=False, dest="keep_original")
+ self.arg_parser.add_argument("-b", "--buffer", type=float, default=0.001, dest="buffer")
+ self.arg_parser.add_argument("-s", "--smoothness", type=float, default=0.3, dest="smoothness")
+ self.arg_parser.add_argument("-t", "--threshold", type=float, default=10.0, dest="threshold")
+ self.arg_parser.add_argument("-i", "--inset", type=float, default=0.001, dest="inset")
def effect(self):
if not self.svg.selection:
- inkex.errormsg(_("Please select one or more shapes to convert to their outline."))
+ errormsg(_("Please select one or more shapes to convert to their outline."))
return
+ self.threshold = self.options.threshold * PIXELS_PER_MM
+ self.shape_buffer = max(self.options.buffer * PIXELS_PER_MM, 0.001)
+ self.smoothness = self.options.smoothness * PIXELS_PER_MM
+ self.inset = self.options.inset * PIXELS_PER_MM
+
for element in self.svg.selection:
self.element_to_outline(element)
+ def get_outline(self, element):
+ d = ''
+ transform = element.composed_transform()
+ path = Path(element.get_path()).transform(transform).break_apart()
+ for subpath in path:
+ points = subpath.end_points
+ shape = LineString(points).buffer(self.shape_buffer)
+ outline = ensure_multi_line_string(offset_curve(shape, -self.inset))
+
+ interiors = []
+ for interior in outline.geoms:
+ if Polygon(interior).area < self.threshold:
+ continue
+ interior_path = smooth_path(interior.coords, self.smoothness)
+ if len(interior_path) > 2:
+ interiors.append(Polygon(interior_path))
+ outline = MultiPolygon(interiors)
+
+ for geom in outline.geoms:
+ d += str(Path(geom.exterior.coords).transform(-transform))
+ return d
+
def element_to_outline(self, element):
if element.tag_name == 'g':
for element in element.iterdescendants(SVG_PATH_TAG):
self.element_to_outline(element)
return
-
- path = element.get_path()
- path = path.end_points
- hull = concave_hull(LineString(path), ratio=self.options.ratio, allow_holes=self.options.allow_holes)
- if isinstance(hull, LineString):
+ elif element.tag_name != 'path':
+ element_id = element.label or element.get_id()
+ errormsg(_("{element_id} is not a path element. "
+ "This extension is designed to generate an outline of an embroidery pattern.").format(element_id=element_id))
return
- if not isinstance(hull, MultiPolygon):
- hull = MultiPolygon([hull])
- d = ''
- for geom in hull.geoms:
- d += 'M '
- for x, y in geom.exterior.coords:
- d += f'{x}, {y} '
- d += "Z"
+ d = self.get_outline(element)
+ if not d:
+ errormsg(_("Could not generate path from element {element_id} with the given settings.").format(element_id=element_id))
+ return
- element.set('d', d)
+ if self.options.keep_original:
+ new_element = element.duplicate()
+ new_element.set('d', d)
+ else:
+ element.set('d', d)
diff --git a/templates/outline.xml b/templates/outline.xml
index b9db75002..06997c2d8 100644
--- a/templates/outline.xml
+++ b/templates/outline.xml
@@ -11,8 +11,21 @@
- 0
- false
+ false
+
+
+
+ 10
+ 0.001
+ 0.3
+
+
+
+ 0