summaryrefslogtreecommitdiff
path: root/lib/extensions/gradient_blocks.py
diff options
context:
space:
mode:
authorKaalleen <reni@allenka.de>2023-05-13 21:11:27 +0200
committerKaalleen <reni@allenka.de>2023-05-13 21:11:27 +0200
commit111fd8f0ef7bc8e62322c38a7a7467c453f85331 (patch)
tree3d7d427422c23898e128646eb10b0477244dd03a /lib/extensions/gradient_blocks.py
parent3a441427da441e1c13c349453ae642de9fdc7ab2 (diff)
* move all gradient methods to extension
* add underlay to single color elements to compensate density
Diffstat (limited to 'lib/extensions/gradient_blocks.py')
-rw-r--r--lib/extensions/gradient_blocks.py110
1 files changed, 96 insertions, 14 deletions
diff --git a/lib/extensions/gradient_blocks.py b/lib/extensions/gradient_blocks.py
index d80d5340..5d8318b6 100644
--- a/lib/extensions/gradient_blocks.py
+++ b/lib/extensions/gradient_blocks.py
@@ -3,17 +3,18 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-from math import degrees
+from math import degrees, pi
-from inkex import DirectedLineSegment, PathElement, errormsg
+from inkex import DirectedLineSegment, PathElement, Transform, errormsg
+from shapely import geometry as shgeo
+from shapely.affinity import affine_transform, rotate
from shapely.geometry import Point
-from shapely.ops import nearest_points
+from shapely.ops import nearest_points, split
from ..commands import add_commands
from ..elements import FillStitch
-from ..elements.gradient_fill import gradient_shapes_and_attributes
from ..i18n import _
-from ..svg import get_correction_transform
+from ..svg import PIXELS_PER_MM, get_correction_transform
from ..svg.tags import INKSTITCH_ATTRIBS
from .commands import CommandsExtension
from .duplicate_params import get_inkstitch_attributes
@@ -56,35 +57,45 @@ class GradientBlocks(CommandsExtension):
fill_shapes.reverse()
attributes.reverse()
+ if self.options.end_row_spacing != 0:
+ end_row_spacing = self.options.end_row_spacing
+ else:
+ end_row_spacing = element.row_spacing / PIXELS_PER_MM * 2
+ end_row_spacing = f'{end_row_spacing: .2f}'
+
previous_color = None
previous_element = None
for i, shape in enumerate(fill_shapes):
color = attributes[i]['color']
style['fill'] = color
- end_row_spacing = attributes[i]['end_row_spacing'] or None
+ is_gradient = attributes[i]['is_gradient']
angle = degrees(attributes[i]['angle'])
+ angle = f'{angle: .2f}'
d = "M " + " ".join([f'{x}, {y}' for x, y in list(shape.exterior.coords)]) + " Z"
block = PathElement(attrib={
"id": self.uniqueId("path"),
"style": str(style),
"transform": correction_transform,
"d": d,
- INKSTITCH_ATTRIBS['angle']: f'{angle: .2f}'
+ INKSTITCH_ATTRIBS['angle']: angle
})
# apply parameters from original element
params = get_inkstitch_attributes(element.node)
for attrib in params:
block.attrib[attrib] = str(element.node.attrib[attrib])
- # set end_row_spacing
- if end_row_spacing:
- if self.options.end_row_spacing != 0:
- end_row_spacing = self.options.end_row_spacing
- block.set('inkstitch:end_row_spacing_mm', f'{end_row_spacing: .2f}')
- else:
- block.pop('inkstitch:end_row_spacing_mm')
# disable underlay and underpath
block.set('inkstitch:fill_underlay', False)
block.set('inkstitch:underpath', False)
+ # set end_row_spacing
+ if is_gradient:
+ block.set('inkstitch:end_row_spacing_mm', end_row_spacing)
+ else:
+ block.pop('inkstitch:end_row_spacing_mm')
+ # use underlay to compensate for higher density in the gradient parts
+ block.set('inkstitch:fill_underlay', True)
+ block.set('inkstitch:fill_underlay_angle', angle)
+ block.set('inkstitch:fill_underlay_row_spacing_mm', end_row_spacing)
+
parent.insert(index, block)
if previous_color == color:
@@ -109,6 +120,77 @@ class GradientBlocks(CommandsExtension):
return Point(pos)
+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
+ is_gradient = False
+ 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({'color': previous_color, 'angle': stitch_angle, 'is_gradient': is_gradient})
+ polygons.append(poly)
+ attributes.append({'color': color, 'angle': stitch_angle + pi, 'is_gradient': is_gradient})
+ else:
+ shape_rest.append(poly)
+ shape = shgeo.MultiPolygon(shape_rest)
+ previous_color = color
+ is_gradient = True
+ # 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, 'is_gradient': is_gradient})
+ stitch_angle += pi
+ else:
+ is_gradient = False
+ for s in shape.geoms:
+ polygons.append(s)
+ attributes.append({'color': stop_styles[-1]['stop-color'], 'angle': stitch_angle, 'is_gradient': is_gradient})
+ return polygons, attributes
+
+
if __name__ == '__main__':
e = GradientBlocks()
e.effect()