diff options
Diffstat (limited to 'lib/elements/clone.py')
| -rw-r--r-- | lib/elements/clone.py | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/lib/elements/clone.py b/lib/elements/clone.py new file mode 100644 index 00000000..0e7a5d63 --- /dev/null +++ b/lib/elements/clone.py @@ -0,0 +1,169 @@ +from copy import copy +from math import atan, degrees + +from simpletransform import (applyTransformToNode, applyTransformToPoint, + computeBBox, parseTransform) + +from ..commands import is_command, is_command_symbol +from ..i18n import _ +from ..svg.path import get_node_transform +from ..svg.svg import find_elements +from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, + SVG_POLYLINE_TAG, SVG_USE_TAG, XLINK_HREF) +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)] + + elif element.get_boolean_param("satin_column") and element.get_style("stroke"): + return [SatinColumn(node)] + else: + elements = [] + if element.get_style("fill", 'black') and not element.get_style('fill-opacity', 1) == "0": + if element.get_boolean_param("auto_fill", True): + elements.append(AutoFill(node)) + else: + elements.append(Fill(node)) + if element.get_style("stroke"): + 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 [] + + clone = copy(source_node) + + # set id + clone_id = 'clone__%s__%s' % (self.node.get('id', ''), clone.get('id', '')) + clone.set('id', clone_id) + + # apply transform + transform = get_node_transform(self.node) + applyTransformToNode(transform, clone) + + # set fill angle. Use either + # 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 + clone_mat = parseTransform(clone.get('transform', '')) + clone_angle = degrees(atan(-clone_mat[1][0]/clone_mat[1][1])) + # source node angle + source_mat = parseTransform(source_node.get('transform', '')) + 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 + clone.set(param, str(angle)) + + elements = self.clone_to_element(clone) + + for element in elements: + patches.extend(element.to_patches(last_patch)) + + return patches + + def center(self, source_node): + xmin, xmax, ymin, ymax = computeBBox([source_node]) + point = [(xmax-((xmax-xmin)/2)), (ymax-((ymax-ymin)/2))] + transform = get_node_transform(self.node) + applyTransformToPoint(transform, point) + return point + + 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 |
