inkstitch/lib/elements/clone.py

164 wiersze
5.9 KiB
Python

# 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
from ..commands import is_command_symbol
from ..i18n import _
from ..svg.path import get_node_transform
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_USE_TAG,
XLINK_HREF)
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')
def clone_to_element(self, node):
from .utils import node_to_elements
return node_to_elements(node, True)
def to_stitch_groups(self, last_patch=None):
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)
else:
source_node.set(INKSTITCH_ATTRIBS['angle'], self.clone_fill_angle)
elements = self.clone_to_element(source_node)
for element in elements:
stitch_groups = element.to_stitch_groups(last_patch)
patches.extend(stitch_groups)
source_node.set('transform', old_transform)
source_node.set(INKSTITCH_ATTRIBS['angle'], old_angle)
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
def get_clone_style(self, style_name, node, default=None):
style = node.style[style_name] or default
return style
def center(self, source_node):
transform = get_node_transform(self.node.getparent())
center = self.node.bounding_box(transform).center
return center
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