inkstitch/lib/elements/clone.py

169 wiersze
6.2 KiB
Python
Czysty Zwykły widok Historia

2021-03-12 04:17:19 +00:00
# Authors: see git history
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from math import atan2, degrees, radians
from inkex import CubicSuperPath, Path, Transform
2020-05-16 21:01:00 +00:00
from ..commands import is_command_symbol
2020-05-16 21:01:00 +00:00
from ..i18n import _
from ..svg.path import get_node_transform
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_USE_TAG,
XLINK_HREF)
2020-05-16 21:01:00 +00:00
from ..utils import cache
from .element import EmbroideryElement, param
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') or None
@property
@param('flip_angle',
_('Flip angle'),
tooltip=_("Flip automatically calucalted angle if it appears to be wrong."),
type='boolean')
@cache
def flip_angle(self):
return self.get_boolean_param('flip_angle')
2020-05-16 21:01:00 +00:00
def get_cache_key_data(self, previous_stitch):
source_node = get_clone_source(self.node)
source_elements = self.clone_to_element(source_node)
return [element.get_cache_key(previous_stitch) for element in source_elements]
2020-05-16 21:01:00 +00:00
def clone_to_element(self, node):
from .utils import node_to_elements
return node_to_elements(node, True)
2020-05-16 21:01:00 +00:00
2021-08-07 16:37:17 +00:00
def to_stitch_groups(self, last_patch=None):
2020-05-16 21:01:00 +00:00
patches = []
source_node = get_clone_source(self.node)
if source_node.tag not in EMBROIDERABLE_TAGS:
return []
old_transform = source_node.get('transform', '')
source_transform = source_node.composed_transform()
source_path = Path(source_node.get_path()).transform(source_transform)
transform = Transform(source_node.get('transform', '')) @ -source_transform
transform @= self.node.composed_transform() @ Transform(source_node.get('transform', ''))
source_node.set('transform', transform)
old_angle = float(source_node.get(INKSTITCH_ATTRIBS['angle'], 0))
if self.clone_fill_angle is None:
rot = transform.add_rotate(-old_angle)
angle = self._get_rotation(rot, source_node, source_path)
if angle is not None:
source_node.set(INKSTITCH_ATTRIBS['angle'], angle)
2020-05-16 21:01:00 +00:00
else:
source_node.set(INKSTITCH_ATTRIBS['angle'], self.clone_fill_angle)
2020-05-16 21:01:00 +00:00
elements = self.clone_to_element(source_node)
2020-05-16 21:01:00 +00:00
for element in elements:
stitch_groups = element.to_stitch_groups(last_patch)
patches.extend(stitch_groups)
2020-05-16 21:01:00 +00:00
source_node.set('transform', old_transform)
source_node.set(INKSTITCH_ATTRIBS['angle'], old_angle)
2020-05-16 21:01:00 +00:00
return patches
def _get_rotation(self, transform, source_node, source_path):
try:
rotation = transform.rotation_degrees()
except ValueError:
source_path = CubicSuperPath(source_path)[0]
clone_path = Path(source_node.get_path()).transform(source_node.composed_transform())
clone_path = CubicSuperPath(clone_path)[0]
angle_source = atan2(source_path[1][1][1] - source_path[0][1][1], source_path[1][1][0] - source_path[0][1][0])
angle_clone = atan2(clone_path[1][1][1] - clone_path[0][1][1], clone_path[1][1][0] - clone_path[0][1][0])
angle_embroidery = radians(-float(source_node.get(INKSTITCH_ATTRIBS['angle'], 0)))
diff = angle_source - angle_embroidery
rotation = degrees(diff + angle_clone)
if self.flip_angle:
rotation = -degrees(diff - angle_clone)
return -rotation
2020-06-04 17:15:16 +00:00
def get_clone_style(self, style_name, node, default=None):
style = node.style[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):
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):
return node.href