summaryrefslogtreecommitdiff
path: root/lib/elements
diff options
context:
space:
mode:
Diffstat (limited to 'lib/elements')
-rw-r--r--lib/elements/clone.py81
-rw-r--r--lib/elements/fill_stitch.py3
-rw-r--r--lib/elements/gradient_fill.py79
-rw-r--r--lib/elements/stroke.py23
4 files changed, 156 insertions, 30 deletions
diff --git a/lib/elements/clone.py b/lib/elements/clone.py
index d9185012..b5507e4f 100644
--- a/lib/elements/clone.py
+++ b/lib/elements/clone.py
@@ -3,12 +3,13 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-from math import atan, degrees
+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.svg import find_elements
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_USE_TAG,
XLINK_HREF)
from ..utils import cache
@@ -60,7 +61,16 @@ class Clone(EmbroideryElement):
type='float')
@cache
def clone_fill_angle(self):
- return self.get_float_param('angle', 0)
+ 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
@@ -73,33 +83,51 @@ class Clone(EmbroideryElement):
if source_node.tag not in EMBROIDERABLE_TAGS:
return []
- self.node.style = source_node.specified_style()
-
- # 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
+ 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:
- # clone angle
- clone_mat = self.node.composed_transform()
- clone_angle = degrees(atan(-clone_mat[1][0]/clone_mat[1][1]))
- # source node angle
- source_mat = source_node.composed_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
- self.node.set(param, str(angle))
-
- elements = self.clone_to_element(self.node)
+ source_node.set(INKSTITCH_ATTRIBS['angle'], self.clone_fill_angle)
+ elements = self.clone_to_element(source_node)
for element in elements:
- patches.extend(element.to_stitch_groups(last_patch))
+ 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
@@ -132,7 +160,4 @@ def is_embroiderable_clone(node):
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
+ return node.href
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py
index ca44e3cc..a5248952 100644
--- a/lib/elements/fill_stitch.py
+++ b/lib/elements/fill_stitch.py
@@ -216,7 +216,8 @@ class FillStitch(EmbroideryElement):
@property
@param('staggers',
_('Stagger rows this many times before repeating'),
- tooltip=_('Length of the cycle by which successive stitch rows are staggered. Fractional values are allowed and can have less visible diagonals than integer values.'),
+ tooltip=_('Length of the cycle by which successive stitch rows are staggered.'
+ 'Fractional values are allowed and can have less visible diagonals than integer values.'),
type='int',
sort_index=6,
select_items=[('fill_method', 0), ('fill_method', 2), ('fill_method', 3)],
diff --git a/lib/elements/gradient_fill.py b/lib/elements/gradient_fill.py
new file mode 100644
index 00000000..5ac49c4e
--- /dev/null
+++ b/lib/elements/gradient_fill.py
@@ -0,0 +1,79 @@
+from math import pi
+
+from inkex import DirectedLineSegment, Transform
+from shapely import geometry as shgeo
+from shapely.affinity import affine_transform, rotate
+from shapely.ops import split
+
+from ..svg import get_correction_transform
+
+
+def gradient_shapes_and_attributes(element, shape):
+ # e.g. url(#linearGradient872) -> linearGradient872
+ color = element.color[5:-1]
+ xpath = f'.//svg:defs/svg:linearGradient[@id="{color}"]'
+ gradient = element.node.getroottree().getroot().findone(xpath)
+ gradient.apply_transform()
+ point1 = (float(gradient.get('x1')), float(gradient.get('y1')))
+ point2 = (float(gradient.get('x2')), float(gradient.get('y2')))
+ # get 90° angle to calculate the splitting angle
+ line = DirectedLineSegment(point1, point2)
+ angle = line.angle - (pi / 2)
+ # Ink/Stitch somehow turns the stitch angle
+ stitch_angle = angle * -1
+ # create bbox polygon to calculate the length necessary to make sure that
+ # the gradient splitter lines will cut the entire design
+ bbox = element.node.bounding_box()
+ bbox_polygon = shgeo.Polygon([(bbox.left, bbox.top), (bbox.right, bbox.top),
+ (bbox.right, bbox.bottom), (bbox.left, bbox.bottom)])
+ # gradient stops
+ offsets = gradient.stop_offsets
+ stop_styles = gradient.stop_styles
+ # now split the shape according to the gradient stops
+ polygons = []
+ colors = []
+ attributes = []
+ previous_color = None
+ end_row_spacing = None
+ for i, offset in enumerate(offsets):
+ shape_rest = []
+ split_point = shgeo.Point(line.point_at_ratio(float(offset)))
+ length = split_point.hausdorff_distance(bbox_polygon)
+ split_line = shgeo.LineString([(split_point.x - length - 2, split_point.y),
+ (split_point.x + length + 2, split_point.y)])
+ split_line = rotate(split_line, angle, origin=split_point, use_radians=True)
+ transform = -Transform(get_correction_transform(element.node))
+ transform = list(transform.to_hexad())
+ split_line = affine_transform(split_line, transform)
+ offset_line = split_line.parallel_offset(1, 'right')
+ polygon = split(shape, split_line)
+ color = stop_styles[i]['stop-color']
+ # does this gradient line split the shape
+ offset_outside_shape = len(polygon.geoms) == 1
+ for poly in polygon.geoms:
+ if isinstance(poly, shgeo.Polygon) and element.shape_is_valid(poly):
+ if poly.intersects(offset_line):
+ if previous_color:
+ polygons.append(poly)
+ colors.append(previous_color)
+ attributes.append({'angle': stitch_angle, 'end_row_spacing': end_row_spacing, 'color': previous_color})
+ polygons.append(poly)
+ attributes.append({'angle': stitch_angle + pi, 'end_row_spacing': end_row_spacing, 'color': color})
+ else:
+ shape_rest.append(poly)
+ shape = shgeo.MultiPolygon(shape_rest)
+ previous_color = color
+ end_row_spacing = element.row_spacing * 2
+ # add left over shape(s)
+ if shape:
+ if offset_outside_shape:
+ for s in shape.geoms:
+ polygons.append(s)
+ attributes.append({'color': stop_styles[-2]['stop-color'], 'angle': stitch_angle, 'end_row_spacing': end_row_spacing})
+ stitch_angle += pi
+ else:
+ end_row_spacing = None
+ for s in shape.geoms:
+ polygons.append(s)
+ attributes.append({'color': stop_styles[-1]['stop-color'], 'angle': stitch_angle, 'end_row_spacing': end_row_spacing})
+ return polygons, attributes
diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py
index ce973c0e..2854adaf 100644
--- a/lib/elements/stroke.py
+++ b/lib/elements/stroke.py
@@ -39,6 +39,14 @@ class MultipleGuideLineWarning(ValidationWarning):
]
+class SmallZigZagWarning(ValidationWarning):
+ name = _("Small ZigZag")
+ description = _("This zig zag stitch has a stroke width smaller than 0.5 units.")
+ steps_to_solve = [
+ _("Set your stroke to be dashed to indicate running stitch. Any kind of dash will work.")
+ ]
+
+
class Stroke(EmbroideryElement):
element_name = _("Stroke")
@@ -481,6 +489,15 @@ class Stroke(EmbroideryElement):
return guide_lines['satin'][0]
return guide_lines['stroke'][0]
+ def _representative_point(self):
+ # if we just take the center of a line string we could end up on some point far away from the actual line
+ try:
+ coords = list(self.shape.coords)
+ except NotImplementedError:
+ # linear rings to not have a coordinate sequence
+ coords = list(self.shape.exterior.coords)
+ return coords[int(len(coords)/2)]
+
def validation_warnings(self):
if self.stroke_method == 1 and self.skip_start + self.skip_end >= self.line_count:
yield IgnoreSkipValues(self.shape.centroid)
@@ -489,4 +506,8 @@ class Stroke(EmbroideryElement):
if self.stroke_method == 1:
guide_lines = get_marker_elements(self.node, "guide-line", False, True, True)
if sum(len(x) for x in guide_lines.values()) > 1:
- yield MultipleGuideLineWarning(self.shape.centroid)
+ yield MultipleGuideLineWarning(self._representative_point())
+
+ stroke_width, units = parse_length_with_units(self.get_style("stroke-width", "1"))
+ if not self.dashed and stroke_width <= 0.5:
+ yield SmallZigZagWarning(self._representative_point())