summaryrefslogtreecommitdiff
path: root/lib/extensions/knockdown_fill.py
blob: 8815efa5c5856c39ce4bdf8c4a8e7ca45dff9af4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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=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)