inkstitch/lib/extensions/knockdown_fill.py

107 wiersze
4.8 KiB
Python

# Authors: see git history
#
# Copyright (c) 2025 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from inkex import Boolean, Path, PathElement
from shapely import union_all
from shapely.geometry import LineString, Polygon
from ..stitches.ripple_stitch import ripple_stitch
from ..svg import PIXELS_PER_MM, get_correction_transform
from ..utils.geometry import ensure_multi_polygon
from .base import InkstitchExtension
class KnockdownFill(InkstitchExtension):
'''
This extension generates a shape around all selected shapes and inserts it into the document
'''
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.arg_parser.add_argument("--notebook")
self.arg_parser.add_argument("-k", "--keep-holes", type=Boolean, default=True, dest="keep_holes")
self.arg_parser.add_argument("-o", "--offset", type=float, default=0, dest="offset")
self.arg_parser.add_argument("-j", "--join-style", type=str, default="1", dest="join_style")
self.arg_parser.add_argument("-m", "--mitre-limit", type=float, default=5.0, dest="mitre_limit")
# TODO: Layer options: underlay, row spacing, angle
def effect(self):
if not self.get_elements():
return
polygons = []
for element in self.elements:
if element.name == "FillStitch":
# take expand value into account
shape = element.shrink_or_grow_shape(element.shape, element.expand)
# MultiPolygon
for polygon in shape.geoms:
polygons.append(polygon)
elif element.name == "SatinColumn":
# plot points on rails, so we get the actual satin size (including pull compensation)
rail_pairs = zip(*element.plot_points_on_rails(
0.3,
element.pull_compensation_px,
element.pull_compensation_percent / 100)
)
rails = []
for rail in rail_pairs:
rails.append(LineString(rail))
polygon = Polygon(list(rails[0].coords) + list(rails[1].reverse().coords)).buffer(0)
polygons.append(polygon)
elif element.name == "Stroke":
if element.stroke_method == 'ripple_stitch':
# for ripples this is going to be a bit complicated, so let's follow the stitch plan
stitches = ripple_stitch(element)
linestring = LineString(stitches)
polygons.append(linestring.buffer(0.15 * PIXELS_PER_MM, cap_style='flat'))
elif element.stroke_method == 'zigzag_stitch':
# zigzag stitch depends on the width of the stroke and pull compensation settings
polygons.append(element.as_multi_line_string().buffer((element.stroke_width + element.pull_compensation) / 2, cap_style='flat'))
else:
polygons.append(element.as_multi_line_string().buffer(0.15 * PIXELS_PER_MM, cap_style='flat'))
combined_shape = union_all(polygons)
combined_shape = combined_shape.buffer(
self.options.offset * PIXELS_PER_MM,
cap_style=int(self.options.join_style),
join_style=int(self.options.join_style),
mitre_limit=float(max(self.options.mitre_limit, 0.1))
)
combined_shape = combined_shape.simplify(0.3)
combined_shape = ensure_multi_polygon(combined_shape)
self.insert_knockdown_elements(combined_shape)
def insert_knockdown_elements(self, combined_shape):
first = self.svg.selection.rendering_order()[0]
try:
parent = first.getparent()
index = parent.index(first)
except AttributeError:
parent = self.svg
index = 0
transform = get_correction_transform(first)
for polygon in combined_shape.geoms:
d = str(Path(polygon.exterior.coords))
if self.options.keep_holes:
for interior in polygon.interiors:
d += str(Path(interior.coords))
path = PathElement()
path.set('d', d)
path.label = self.svg.get_unique_id('Knockdown ')
path.set('transform', transform)
path.set('inkstitch:row_spacing_mm', '2.6')
path.set('inkstitch:fill_underlay_angle', '60 -60')
path.set('inkstitch:fill_underlay_max_stitch_length_mm', '3')
path.set('inkstitch:fill_underlay_row_spacing_mm', '2.6')
path.set('inkstitch:underlay_underpath', 'False')
path.set('inkstitch:underpath', 'False')
path.set('inkstitch:staggers', '2')
path.set('style', 'fill:black;')
parent.insert(index, path)