From ff073a4bd6172200fe79e0c080751a23afea4ec1 Mon Sep 17 00:00:00 2001
From: Kaalleen <36401965+kaalleen@users.noreply.github.com>
Date: Sat, 22 Feb 2025 14:19:47 +0100
Subject: [PATCH] Add selection to knockdown fill extension (#3526)
---
icons/inx/fill_knockdown.svg | 77 ++++++++++++++++++++++
lib/extensions/__init__.py | 2 +
lib/extensions/knockdown_fill.py | 106 +++++++++++++++++++++++++++++++
templates/knockdown_fill.xml | 40 ++++++++++++
4 files changed, 225 insertions(+)
create mode 100644 icons/inx/fill_knockdown.svg
create mode 100644 lib/extensions/knockdown_fill.py
create mode 100644 templates/knockdown_fill.xml
diff --git a/icons/inx/fill_knockdown.svg b/icons/inx/fill_knockdown.svg
new file mode 100644
index 000000000..2a46ce944
--- /dev/null
+++ b/icons/inx/fill_knockdown.svg
@@ -0,0 +1,77 @@
+
+
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py
index 91c205479..9b25ff918 100644
--- a/lib/extensions/__init__.py
+++ b/lib/extensions/__init__.py
@@ -30,6 +30,7 @@ from .install import Install
from .install_custom_palette import InstallCustomPalette
from .jump_to_stroke import JumpToStroke
from .jump_to_trim import JumpToTrim
+from .knockdown_fill import KnockdownFill
from .layer_commands import LayerCommands
from .lettering import Lettering
from .lettering_along_path import LetteringAlongPath
@@ -103,6 +104,7 @@ __all__ = extensions = [About,
InstallCustomPalette,
JumpToStroke,
JumpToTrim,
+ KnockdownFill,
LayerCommands,
Lettering,
LetteringAlongPath,
diff --git a/lib/extensions/knockdown_fill.py b/lib/extensions/knockdown_fill.py
new file mode 100644
index 000000000..35459016d
--- /dev/null
+++ b/lib/extensions/knockdown_fill.py
@@ -0,0 +1,106 @@
+# 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=int(self.options.mitre_limit)
+ )
+ 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)
diff --git a/templates/knockdown_fill.xml b/templates/knockdown_fill.xml
new file mode 100644
index 000000000..a58f8c0ad
--- /dev/null
+++ b/templates/knockdown_fill.xml
@@ -0,0 +1,40 @@
+
+
+ Selection to Knockdown Fill
+ org.{{ id_inkstitch }}.knockdown_fill
+ knockdown_fill
+
+ all
+ {{ icon_path }}inx/fill_knockdown.svg
+ Generates a knockdown stitch with a specified offset around selection
+
+
+
+
+
+
+
+
+ true
+ 1
+
+
+
+
+
+ 5.0
+
+
+
+
+
+
+
+
+
+
+
+
+