From bd43e007753c1e5baf1f8b191944378beb68c6ec Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Mon, 28 Feb 2022 16:24:51 +0100 Subject: [PATCH] Cutwork segmentation (#1582) * add cutwork segmentation extension * simulator: option to not render jump stitches --- electron/src/renderer/assets/js/simulator.js | 13 ++ .../src/renderer/assets/style/simulator.css | 4 - .../src/renderer/components/Simulator.vue | 5 +- electron/src/renderer/main.js | 2 + lib/extensions/__init__.py | 4 +- lib/extensions/cutwork_segmentation.py | 188 ++++++++++++++++++ lib/lettering/font.py | 4 +- templates/cutwork_segmentation.xml | 56 ++++++ 8 files changed, 269 insertions(+), 7 deletions(-) create mode 100644 lib/extensions/cutwork_segmentation.py create mode 100644 templates/cutwork_segmentation.xml diff --git a/electron/src/renderer/assets/js/simulator.js b/electron/src/renderer/assets/js/simulator.js index 21859d80d..fa963bbeb 100644 --- a/electron/src/renderer/assets/js/simulator.js +++ b/electron/src/renderer/assets/js/simulator.js @@ -49,6 +49,7 @@ export default { showColorChanges: false, showStops: false, showNeedlePenetrationPoints: false, + renderJumps: true, showRealisticPreview: false, showCursor: true } @@ -70,6 +71,10 @@ export default { } }) }, + renderJumps() { + this.renderedStitch = 1 + this.renderFrame() + }, showRealisticPreview() { let animating = this.animating this.stop() @@ -291,6 +296,14 @@ export default { renderFrame() { while (this.renderedStitch < this.currentStitch) { this.renderedStitch += 1 + if (!this.renderJumps && this.stitches[this.renderedStitch].jump){ + if (this.showRealisticPreview) { + this.realisticPaths[this.renderedStitch].hide(); + } else { + this.stitchPaths[this.renderedStitch].hide(); + } + continue; + } if (this.showRealisticPreview) { this.realisticPaths[this.renderedStitch].show() } else { diff --git a/electron/src/renderer/assets/style/simulator.css b/electron/src/renderer/assets/style/simulator.css index 6673c69df..8a79de98f 100644 --- a/electron/src/renderer/assets/style/simulator.css +++ b/electron/src/renderer/assets/style/simulator.css @@ -172,10 +172,6 @@ fieldset.show-commands span { vertical-align: top; } -fieldset.show-commands span.npp { - text-align: right; -} - fieldset.show-commands span:first-of-type { padding: 0 5px; } diff --git a/electron/src/renderer/components/Simulator.vue b/electron/src/renderer/components/Simulator.vue index 26bada6c4..22b25446f 100644 --- a/electron/src/renderer/components/Simulator.vue +++ b/electron/src/renderer/components/Simulator.vue @@ -215,7 +215,7 @@ - + +
+ +
diff --git a/electron/src/renderer/main.js b/electron/src/renderer/main.js index 684860986..1afbca3ab 100644 --- a/electron/src/renderer/main.js +++ b/electron/src/renderer/main.js @@ -24,6 +24,7 @@ import { faExchangeAlt, faEye, faFrog, + faLink, faHippo, faHorse, faInfo, @@ -60,6 +61,7 @@ library.add( faExchangeAlt, faEye, faFrog, + faLink, faHippo, faHorse, faInfo, diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 2d3445785..83dd19c1f 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -11,6 +11,7 @@ from .cleanup import Cleanup from .convert_to_satin import ConvertToSatin from .convert_to_stroke import ConvertToStroke from .cut_satin import CutSatin +from .cutwork_segmentation import CutworkSegmentation from .duplicate_params import DuplicateParams from .embroider_settings import EmbroiderSettings from .flip import Flip @@ -68,4 +69,5 @@ __all__ = extensions = [StitchPlanPreview, Simulator, Reorder, DuplicateParams, - EmbroiderSettings] + EmbroiderSettings, + CutworkSegmentation] diff --git a/lib/extensions/cutwork_segmentation.py b/lib/extensions/cutwork_segmentation.py new file mode 100644 index 000000000..d17cfd76c --- /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) diff --git a/lib/lettering/font.py b/lib/lettering/font.py index f0aac11e1..104e4b29d 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -353,4 +353,6 @@ class Font(object): """ elements = nodes_to_elements(group.iterdescendants(SVG_PATH_TAG)) - auto_satin(elements, preserve_order=True, trim=trim) + + if elements: + auto_satin(elements, preserve_order=True, trim=trim) diff --git a/templates/cutwork_segmentation.xml b/templates/cutwork_segmentation.xml new file mode 100644 index 000000000..f20ff082f --- /dev/null +++ b/templates/cutwork_segmentation.xml @@ -0,0 +1,56 @@ + + + Cutwork segmentation + org.inkstitch.cutwork_segmentation + cutwork_segmentation + + all + + + + + + + + + 112 + 157 + 0x990000ff + + + + 158 + 23 + 0xe5a50aff + + + + 22 + 68 + 0x009900ff + + + + 67 + 113 + 0x000099ff + + + true + false + + + + + + + + + + + + + +