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
+
+
+
+
+
+
+
+
+
+
+
+
+
+