diff options
| -rw-r--r-- | lib/extensions/outline.py | 72 | ||||
| -rw-r--r-- | templates/outline.xml | 17 |
2 files changed, 67 insertions, 22 deletions
diff --git a/lib/extensions/outline.py b/lib/extensions/outline.py index d6a16e99..364cec6d 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 b9db7500..06997c2d 100644 --- a/templates/outline.xml +++ b/templates/outline.xml @@ -11,8 +11,21 @@ </submenu> </effects-menu> </effect> - <param name="ratio" type="float" appearance="full" gui-text="Ratio" precision="2" min="0" max="1">0</param> - <param name="allow-holes" type="boolean" gui-text="Allow Holes">false</param> + <param name="keep-original" type="boolean" gui-text="Keep original element(s)">false</param> + <spacer /> + <separator /> + <spacer /> + <param name="threshold" type="float" appearance="full" precision="2" min="0" max="100" + gui-text="Threshold (mm²)" gui-description="Removes smaller shape areas than that">10</param> + <param name="buffer" type="float" appearance="full" precision="3" min="0.001" max="1" + gui-text="Buffer (mm)" gui-description="Enlarge strokes by this amount">0.001</param> + <param name="smoothness" type="float" appearance="full" precision="2" min="0.01" max="5" + gui-text="Smoothness (mm)" gui-description="Smooth path by this amount">0.3</param> + <spacer /> + <separator /> + <spacer /> + <param name="inset" type="float" appearance="full" precision="3" min="0" max="5" + gui-text="Inset (mm)" gui-description="Counteract the buffer effect">0</param> <script> {{ command_tag | safe }} </script> |
