diff options
| author | Kaalleen <36401965+kaalleen@users.noreply.github.com> | 2022-02-28 16:24:51 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-02-28 16:24:51 +0100 |
| commit | bd43e007753c1e5baf1f8b191944378beb68c6ec (patch) | |
| tree | df622596578e923dcdc52c78ac3ec93ff74b77b2 /lib/extensions/cutwork_segmentation.py | |
| parent | 5f815a832b06f47df6517f6866b787ffc1ce36bd (diff) | |
Cutwork segmentation (#1582)
* add cutwork segmentation extension
* simulator: option to not render jump stitches
Diffstat (limited to 'lib/extensions/cutwork_segmentation.py')
| -rw-r--r-- | lib/extensions/cutwork_segmentation.py | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/lib/extensions/cutwork_segmentation.py b/lib/extensions/cutwork_segmentation.py new file mode 100644 index 00000000..d17cfd76 --- /dev/null +++ b/lib/extensions/cutwork_segmentation.py @@ -0,0 +1,188 @@ +# Authors: see git history +# +# Copyright (c) 2022 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +from math import atan2, degrees + +import inkex +from lxml import etree +from shapely.geometry import LineString, Point + +from ..elements import Stroke +from ..i18n import _ +from ..svg import get_correction_transform +from ..svg.tags import INKSCAPE_LABEL, SVG_PATH_TAG +from .base import InkstitchExtension + + +class CutworkSegmentation(InkstitchExtension): + ''' + This will split up stroke elements according to their direction. + Overlapping angle definitions (user input) will result in overlapping paths. + This is wanted behaviour if the needles have a hard time to cut edges at the border of their specific angle capability. + ''' + 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("-as", "--a_start", type=int, default=0, dest="a_start") + self.arg_parser.add_argument("-ae", "--a_end", type=int, default=0, dest="a_end") + self.arg_parser.add_argument("-ac", "--a_color", type=inkex.Color, default=inkex.Color(0x808080FF), dest="a_color") + self.arg_parser.add_argument("-bs", "--b_start", type=int, default=0, dest="b_start") + self.arg_parser.add_argument("-be", "--b_end", type=int, default=0, dest="b_end") + self.arg_parser.add_argument("-bc", "--b_color", type=inkex.Color, default=inkex.Color(0x808080FF), dest="b_color") + self.arg_parser.add_argument("-cs", "--c_start", type=int, default=0, dest="c_start") + self.arg_parser.add_argument("-ce", "--c_end", type=int, default=0, dest="c_end") + self.arg_parser.add_argument("-cc", "--c_color", type=inkex.Color, default=inkex.Color(0x808080FF), dest="c_color") + self.arg_parser.add_argument("-ds", "--d_start", type=int, default=0, dest="d_start") + self.arg_parser.add_argument("-de", "--d_end", type=int, default=0, dest="d_end") + self.arg_parser.add_argument("-dc", "--d_color", type=inkex.Color, default=inkex.Color(0x808080FF), dest="d_color") + self.arg_parser.add_argument("-s", "--sort_by_color", type=inkex.Boolean, default=True, dest="sort_by_color") + self.arg_parser.add_argument("-k", "--keep_original", type=inkex.Boolean, default=False, dest="keep_original") + + def effect(self): + if not self.svg.selected: + inkex.errormsg(_("Please select one or more stroke elements.")) + return + + if not self.get_elements(): + return + + self.sectors = { + 1: {'id': 1, 'start': self.options.a_start, 'end': self.options.a_end, 'color': self.options.a_color, 'point_list': []}, + 2: {'id': 2, 'start': self.options.b_start, 'end': self.options.b_end, 'color': self.options.b_color, 'point_list': []}, + 3: {'id': 3, 'start': self.options.c_start, 'end': self.options.c_end, 'color': self.options.c_color, 'point_list': []}, + 4: {'id': 4, 'start': self.options.d_start, 'end': self.options.d_end, 'color': self.options.d_color, 'point_list': []} + } + + # remove sectors where the start angle equals the end angle (some setups only work with two needles instead of four) + self.sectors = {index: sector for index, sector in self.sectors.items() if sector['start'] != sector['end']} + + self.new_elements = [] + for element in self.elements: + if isinstance(element, Stroke): + + # save parent and index to be able to position and insert new elements later on + parent = element.node.getparent() + index = parent.index(element.node) + + for path in element.paths: + linestring = LineString(path) + # fill self.new_elements list with line segments + self._prepare_line_sections(element, linestring.coords) + + self._insert_elements(parent, element, index) + + self._remove_originals() + + def _get_sectors(self, angle): + sectors = [] + for sector in self.sectors.values(): + if self._in_sector(angle, sector): + sectors.append(sector) + return sectors + + def _in_sector(self, angle, sector): + stop = sector['end'] + 1 + if sector['start'] > stop: + return angle in range(sector['start'], 181) or angle in range(0, stop) + else: + return angle in range(sector['start'], stop) + + def _get_angle(self, p1, p2): + angle = round(degrees(atan2(p2.y - p1.y, p2.x - p1.x)) % 360) + if angle > 180: + angle -= 180 + return angle + + def _prepare_line_sections(self, element, coords): + prev_point = None + current_sectors = [] + + for index, point in enumerate(coords): + point = Point(*point) + if prev_point is None: + prev_point = point + continue + + angle = self._get_angle(point, prev_point) + sectors = self._get_sectors(angle) + + for sector in sectors: + self.sectors[sector['id']]['point_list'].append(prev_point) + # don't miss the last point + if index == len(coords) - 1: + self.sectors[sector['id']]['point_list'].append(point) + self._prepare_element(self.sectors[sector['id']], element) + + # if a segment ends, prepare the element and clear point_lists + for current in current_sectors: + if current not in sectors: + # add last point + self.sectors[current['id']]['point_list'].append(prev_point) + self._prepare_element(self.sectors[current['id']], element) + + prev_point = point + current_sectors = sectors + + def _prepare_element(self, sector, element): + point_list = sector['point_list'] + if len(point_list) < 2: + return + + color = str(self.path_style(element, str(sector['color']))) + + d = "M " + for point in point_list: + d += "%s,%s " % (point.x, point.y) + + stroke_element = etree.Element(SVG_PATH_TAG, + { + "style": color, + "transform": get_correction_transform(element.node), + "d": d + }) + self.new_elements.append([stroke_element, sector['id']]) + # clear point_list in self.sectors + self.sectors[sector['id']].update({'point_list': []}) + + def _insert_elements(self, parent, element, index): + self.new_elements.reverse() + if self.options.sort_by_color is True: + self.new_elements = sorted(self.new_elements, key=lambda x: x[1], reverse=True) + + group = self._insert_group(parent, _("Cutwork Group"), "__inkstitch_cutwork_group__", index) + + section = 0 + for element, section_id in self.new_elements: + # if sorted by color, add a subgroup for each knife + if self.options.sort_by_color: + if section_id != section: + section = section_id + section_group = self._insert_group(group, _("Needle #%s") % section, "__inkstitch_cutwork_needle_group__") + else: + section_group = group + + section_group.insert(0, element) + + def _insert_group(self, parent, label, group_id, index=0): + group = etree.Element("g", { + INKSCAPE_LABEL: "%s" % label, + "id": self.uniqueId("%s" % group_id) + }) + parent.insert(index, group) + return group + + def _remove_originals(self): + if self.options.keep_original: + return + + for element in self.elements: + if isinstance(element, Stroke): + parent = element.node.getparent() + parent.remove(element.node) + + def path_style(self, element, color): + # set stroke color and make it a running stitch - they don't want to cut zigzags + return inkex.Style(element.node.get('style', '')) + inkex.Style('stroke:%s;stroke-dasharray:6,1;' % color) |
