kopia lustrzana https://github.com/inkstitch/inkstitch
189 wiersze
8.3 KiB
Python
189 wiersze
8.3 KiB
Python
# 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)
|