kopia lustrzana https://github.com/inkstitch/inkstitch
184 wiersze
7.9 KiB
Python
184 wiersze
7.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 degrees, pi
|
|
|
|
from inkex import (DirectedLineSegment, Group, LinearGradient, Path,
|
|
PathElement, Transform, errormsg)
|
|
from shapely import geometry as shgeo
|
|
from shapely.affinity import rotate
|
|
from shapely.ops import split
|
|
|
|
from ..elements import FillStitch
|
|
from ..i18n import _
|
|
from ..svg import PIXELS_PER_MM, get_correction_transform
|
|
from ..svg.tags import INKSTITCH_ATTRIBS
|
|
from .base import InkstitchExtension
|
|
from .duplicate_params import get_inkstitch_attributes
|
|
|
|
|
|
class GradientBlocks(InkstitchExtension):
|
|
'''
|
|
This will break apart fill objects with a gradient fill into solid color blocks with end_row_spacing.
|
|
'''
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
|
self.arg_parser.add_argument("--notebook", type=str, default=0.0)
|
|
self.arg_parser.add_argument("--options", type=str, default=0.0)
|
|
self.arg_parser.add_argument("--info", type=str, default=0.0)
|
|
self.arg_parser.add_argument("-e", "--end-row-spacing", type=float, default=0.0, dest="end_row_spacing")
|
|
|
|
def effect(self):
|
|
if not self.svg.selection:
|
|
errormsg(_("Please select at least one object with a gradient fill."))
|
|
return
|
|
|
|
if not self.get_elements():
|
|
return
|
|
|
|
elements = [element for element in self.elements if isinstance(element, FillStitch) and isinstance(element.gradient, LinearGradient)]
|
|
if not elements:
|
|
errormsg(_("Please select at least one object with a gradient fill."))
|
|
return
|
|
|
|
for element in elements:
|
|
parent = element.node.getparent()
|
|
correction_transform = get_correction_transform(element.node)
|
|
style = element.node.style
|
|
index = parent.index(element.node)
|
|
fill_shapes, attributes = gradient_shapes_and_attributes(element, element.shape, self.svg.viewport_to_unit(1))
|
|
# reverse order so we can always insert with the same index number
|
|
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}'
|
|
|
|
color_block_group = Group()
|
|
color_block_group.label = _("Color Gradient Blocks")
|
|
parent.insert(index, color_block_group)
|
|
|
|
for i, shape in enumerate(fill_shapes):
|
|
if shape.area < 15:
|
|
continue
|
|
color = attributes[i]['color']
|
|
style['fill'] = color
|
|
is_gradient = attributes[i]['is_gradient']
|
|
angle = degrees(attributes[i]['angle'])
|
|
angle = f'{angle: .2f}'
|
|
d = self._element_to_path(shape)
|
|
block = PathElement(attrib={
|
|
"id": self.uniqueId("path"),
|
|
"style": str(style),
|
|
"transform": correction_transform,
|
|
"d": d,
|
|
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])
|
|
# 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)
|
|
|
|
color_block_group.append(block)
|
|
element.node.delete()
|
|
|
|
def _element_to_path(self, shape):
|
|
coords = list(shape.exterior.coords)
|
|
for interior in shape.interiors:
|
|
coords.extend(interior.coords)
|
|
path = Path(coords)
|
|
path.close()
|
|
return str(path)
|
|
|
|
|
|
def gradient_shapes_and_attributes(element, shape, unit_multiplier):
|
|
# e.g. url(#linearGradient872) -> linearGradient872
|
|
gradient = element.gradient
|
|
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
|
|
transform = -Transform(get_correction_transform(element.node, child=True))
|
|
line = DirectedLineSegment(transform.apply_to_point(point1), transform.apply_to_point(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
|
|
# bounding_box returns the value in viewport units, we need to convert the length later to px
|
|
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) / unit_multiplier
|
|
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)
|
|
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 poly.is_valid:
|
|
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.run()
|