2020-05-16 21:01:00 +00:00
|
|
|
from math import atan, degrees
|
|
|
|
|
2021-03-04 17:40:53 +00:00
|
|
|
import inkex
|
2020-05-16 21:01:00 +00:00
|
|
|
|
|
|
|
from ..commands import is_command, is_command_symbol
|
|
|
|
from ..i18n import _
|
|
|
|
from ..svg.path import get_node_transform
|
|
|
|
from ..svg.svg import find_elements
|
2021-03-04 17:40:53 +00:00
|
|
|
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS,
|
|
|
|
SVG_POLYLINE_TAG, SVG_USE_TAG, XLINK_HREF)
|
2020-05-16 21:01:00 +00:00
|
|
|
from ..utils import cache
|
|
|
|
from .auto_fill import AutoFill
|
|
|
|
from .element import EmbroideryElement, param
|
|
|
|
from .fill import Fill
|
|
|
|
from .polyline import Polyline
|
|
|
|
from .satin_column import SatinColumn
|
|
|
|
from .stroke import Stroke
|
|
|
|
from .validation import ObjectTypeWarning, ValidationWarning
|
|
|
|
|
|
|
|
|
|
|
|
class CloneWarning(ValidationWarning):
|
|
|
|
name = _("Clone Object")
|
|
|
|
description = _("There are one or more clone objects in this document. "
|
|
|
|
"Ink/Stitch can work with single clones, but you are limited to set a very few parameters. ")
|
|
|
|
steps_to_solve = [
|
|
|
|
_("If you want to convert the clone into a real element, follow these steps:"),
|
|
|
|
_("* Select the clone"),
|
|
|
|
_("* Run: Edit > Clone > Unlink Clone (Alt+Shift+D)")
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class CloneSourceWarning(ObjectTypeWarning):
|
|
|
|
name = _("Clone is not embroiderable")
|
|
|
|
description = _("There are one ore more clone objects in this document. A clone must be a direct child of an embroiderable element. "
|
|
|
|
"Ink/Stitch cannot embroider clones of groups or other not embroiderable elements (text or image).")
|
|
|
|
steps_to_solve = [
|
|
|
|
_("Convert the clone into a real element:"),
|
|
|
|
_("* Select the clone."),
|
|
|
|
_("* Run: Edit > Clone > Unlink Clone (Alt+Shift+D)")
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class Clone(EmbroideryElement):
|
|
|
|
# A clone embroidery element is linked to an embroiderable element.
|
|
|
|
# It will be ignored if the source element is not a direct child of the xlink attribute.
|
|
|
|
|
|
|
|
element_name = "Clone"
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(Clone, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
@property
|
|
|
|
@param('clone', _("Clone"), type='toggle', inverse=False, default=True)
|
|
|
|
def clone(self):
|
|
|
|
return self.get_boolean_param("clone")
|
|
|
|
|
|
|
|
@property
|
|
|
|
@param('angle',
|
|
|
|
_('Custom fill angle'),
|
|
|
|
tooltip=_("This setting will apply a custom fill angle for the clone."),
|
|
|
|
unit='deg',
|
|
|
|
type='float')
|
|
|
|
@cache
|
|
|
|
def clone_fill_angle(self):
|
|
|
|
return self.get_float_param('angle', 0)
|
|
|
|
|
|
|
|
def clone_to_element(self, node):
|
|
|
|
# we need to determine if the source element is polyline, stroke, fill or satin
|
|
|
|
element = EmbroideryElement(node)
|
|
|
|
|
|
|
|
if node.tag == SVG_POLYLINE_TAG:
|
|
|
|
return [Polyline(node)]
|
|
|
|
|
2021-03-04 17:40:53 +00:00
|
|
|
elif element.get_boolean_param("satin_column") and self.get_clone_style("stroke", self.node):
|
2020-05-16 21:01:00 +00:00
|
|
|
return [SatinColumn(node)]
|
|
|
|
else:
|
|
|
|
elements = []
|
2021-03-04 17:40:53 +00:00
|
|
|
if element.get_style("fill", "black") and not element.get_style("stroke", 1) == "0":
|
2020-05-16 21:01:00 +00:00
|
|
|
if element.get_boolean_param("auto_fill", True):
|
|
|
|
elements.append(AutoFill(node))
|
|
|
|
else:
|
|
|
|
elements.append(Fill(node))
|
2021-03-04 17:40:53 +00:00
|
|
|
if element.get_style("stroke", self.node) is not None:
|
2020-05-16 21:01:00 +00:00
|
|
|
if not is_command(element.node):
|
|
|
|
elements.append(Stroke(node))
|
|
|
|
if element.get_boolean_param("stroke_first", False):
|
|
|
|
elements.reverse()
|
|
|
|
|
|
|
|
return elements
|
|
|
|
|
|
|
|
def to_patches(self, last_patch=None):
|
|
|
|
patches = []
|
|
|
|
|
|
|
|
source_node = get_clone_source(self.node)
|
|
|
|
if source_node.tag not in EMBROIDERABLE_TAGS:
|
|
|
|
return []
|
|
|
|
|
2021-03-04 17:40:53 +00:00
|
|
|
self.node.style = source_node.composed_style()
|
|
|
|
|
2020-05-16 21:01:00 +00:00
|
|
|
# a. a custom set fill angle
|
|
|
|
# b. calculated rotation for the cloned fill element to look exactly as it's source
|
|
|
|
param = INKSTITCH_ATTRIBS['angle']
|
|
|
|
if self.clone_fill_angle is not None:
|
|
|
|
angle = self.clone_fill_angle
|
|
|
|
else:
|
|
|
|
# clone angle
|
2021-03-04 17:40:53 +00:00
|
|
|
clone_mat = self.node.composed_transform()
|
2020-05-16 21:01:00 +00:00
|
|
|
clone_angle = degrees(atan(-clone_mat[1][0]/clone_mat[1][1]))
|
|
|
|
# source node angle
|
2021-03-04 17:40:53 +00:00
|
|
|
source_mat = source_node.composed_transform()
|
2020-05-16 21:01:00 +00:00
|
|
|
source_angle = degrees(atan(-source_mat[1][0]/source_mat[1][1]))
|
|
|
|
# source node fill angle
|
|
|
|
source_fill_angle = source_node.get(param, 0)
|
|
|
|
|
|
|
|
angle = clone_angle + float(source_fill_angle) - source_angle
|
2021-03-04 17:40:53 +00:00
|
|
|
self.node.set(param, str(angle))
|
2020-05-16 21:01:00 +00:00
|
|
|
|
2021-03-04 17:40:53 +00:00
|
|
|
elements = self.clone_to_element(self.node)
|
2020-05-16 21:01:00 +00:00
|
|
|
|
|
|
|
for element in elements:
|
|
|
|
patches.extend(element.to_patches(last_patch))
|
|
|
|
|
|
|
|
return patches
|
|
|
|
|
2020-06-04 17:15:16 +00:00
|
|
|
def get_clone_style(self, style_name, node, default=None):
|
2021-03-04 17:40:53 +00:00
|
|
|
style = inkex.styles.AttrFallbackStyle(node).get(style_name) or default
|
2020-06-04 17:15:16 +00:00
|
|
|
return style
|
|
|
|
|
2020-05-16 21:01:00 +00:00
|
|
|
def center(self, source_node):
|
2021-03-04 17:40:53 +00:00
|
|
|
transform = get_node_transform(self.node.getparent())
|
|
|
|
center = self.node.bounding_box(transform).center
|
|
|
|
return center
|
2020-05-16 21:01:00 +00:00
|
|
|
|
|
|
|
def validation_warnings(self):
|
|
|
|
source_node = get_clone_source(self.node)
|
|
|
|
if source_node.tag not in EMBROIDERABLE_TAGS:
|
|
|
|
point = self.center(source_node)
|
|
|
|
yield CloneSourceWarning(point)
|
|
|
|
else:
|
|
|
|
point = self.center(source_node)
|
|
|
|
yield CloneWarning(point)
|
|
|
|
|
|
|
|
|
|
|
|
def is_clone(node):
|
|
|
|
if node.tag == SVG_USE_TAG and node.get(XLINK_HREF) and not is_command_symbol(node):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def is_embroiderable_clone(node):
|
|
|
|
if is_clone(node) and get_clone_source(node).tag in EMBROIDERABLE_TAGS:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def get_clone_source(node):
|
|
|
|
source_id = node.get(XLINK_HREF)[1:]
|
|
|
|
xpath = ".//*[@id='%s']" % (source_id)
|
|
|
|
source_node = find_elements(node, xpath)[0]
|
|
|
|
return source_node
|