2023-10-15 05:54:04 +00:00
|
|
|
# Authors: see git history
|
|
|
|
#
|
|
|
|
# Copyright (c) 2010 Authors
|
|
|
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
|
|
|
|
2024-05-09 08:32:08 +00:00
|
|
|
from inkex import Boolean, Path, errormsg
|
|
|
|
from shapely import offset_curve
|
|
|
|
from shapely.geometry import LineString, MultiPolygon, Polygon
|
2023-10-15 05:54:04 +00:00
|
|
|
|
|
|
|
from ..i18n import _
|
2024-05-09 08:32:08 +00:00
|
|
|
from ..svg import PIXELS_PER_MM
|
2023-10-15 05:54:04 +00:00
|
|
|
from ..svg.tags import SVG_PATH_TAG
|
2024-05-09 08:32:08 +00:00
|
|
|
from ..utils.geometry import ensure_multi_line_string
|
|
|
|
from ..utils.smoothing import smooth_path
|
2023-10-15 05:54:04 +00:00
|
|
|
from .base import InkstitchExtension
|
|
|
|
|
|
|
|
|
|
|
|
class Outline(InkstitchExtension):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
2024-05-09 08:32:08 +00:00
|
|
|
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")
|
2023-10-15 05:54:04 +00:00
|
|
|
|
|
|
|
def effect(self):
|
|
|
|
if not self.svg.selection:
|
2024-05-09 08:32:08 +00:00
|
|
|
errormsg(_("Please select one or more shapes to convert to their outline."))
|
2023-10-15 05:54:04 +00:00
|
|
|
return
|
|
|
|
|
2024-05-09 08:32:08 +00:00
|
|
|
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
|
|
|
|
|
2023-10-15 05:54:04 +00:00
|
|
|
for element in self.svg.selection:
|
|
|
|
self.element_to_outline(element)
|
|
|
|
|
2024-05-09 08:32:08 +00:00
|
|
|
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
|
|
|
|
|
2023-10-15 05:54:04 +00:00
|
|
|
def element_to_outline(self, element):
|
2024-05-11 06:19:23 +00:00
|
|
|
element_id = element.label or element.get_id()
|
2023-10-15 05:54:04 +00:00
|
|
|
if element.tag_name == 'g':
|
|
|
|
for element in element.iterdescendants(SVG_PATH_TAG):
|
|
|
|
self.element_to_outline(element)
|
|
|
|
return
|
2024-05-09 08:32:08 +00:00
|
|
|
elif element.tag_name != 'path':
|
|
|
|
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))
|
2023-10-15 05:54:04 +00:00
|
|
|
return
|
|
|
|
|
2024-05-09 08:32:08 +00:00
|
|
|
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
|
2023-10-15 05:54:04 +00:00
|
|
|
|
2024-05-09 08:32:08 +00:00
|
|
|
if self.options.keep_original:
|
|
|
|
new_element = element.duplicate()
|
|
|
|
new_element.set('d', d)
|
|
|
|
else:
|
|
|
|
element.set('d', d)
|