pull/3408/head
Kaalleen 2025-01-05 12:52:02 +01:00 zatwierdzone przez GitHub
rodzic be66297da6
commit 51ee6c7a08
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
6 zmienionych plików z 526 dodań i 7 usunięć

Wyświetl plik

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
width="99.999901mm"
height="100mm"
version="1.1"
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
viewBox="0 0 15109 15109.485"
id="svg5"
sodipodi:docname="fill_to_satin.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:inkstitch="http://inkstitch.org/namespace"><sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.0097187"
inkscape:cx="216.89209"
inkscape:cy="246.10815"
inkscape:window-width="1920"
inkscape:window-height="1131"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="g1"
showgrid="false" /><metadata
id="metadata3"><inkstitch:min_stitch_len_mm>0.3</inkstitch:min_stitch_len_mm><inkstitch:collapse_len_mm>3.0</inkstitch:collapse_len_mm><inkstitch:inkstitch_svg_version>3</inkstitch:inkstitch_svg_version></metadata><defs
id="defs1" />
<g
id="Layer_x0020_1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g6"
transform="matrix(3.72213,0,0,3.72213,-40581.951,-41363.2)"
style="opacity:1;stroke-width:0.268663"><path
class="fil1"
d="M 14667.311,12672.251 C 14670.674,12694.45 14689.847,12711.269 14712.719,12711.269 V 12711.269 C 14737.947,12711.269 14758.801,12690.75 14758.801,12665.186 V 12616.75 C 14758.801,12604.305 14764.856,12593.877 14775.283,12587.15 14786.047,12580.759 14798.492,12580.423 14809.256,12586.478 14837.174,12600.941 14855.674,12628.523 14855.674,12664.514 V 13602.296 C 14855.674,13651.741 14814.974,13692.441 14765.528,13692.441 H 14667.311 V 13841.45 C 14667.311,14399.478 14210.865,14855.923 13652.837,14855.923 H 12236.746 C 11678.719,14855.923 11221.937,14399.478 11221.937,13841.45 V 13706.232 H 11125.737 C 11074.946,13706.232 11033.573,13664.86 11033.573,13614.405 V 12679.986 C 11033.573,12629.532 11074.946,12588.159 11125.737,12588.159 H 11221.937 V 12425.359 C 11221.937,11867.331 11678.719,11410.886 12236.746,11410.886 H 13652.837 C 14210.865,11410.886 14667.311,11867.331 14667.311,12425.359 Z M 11150.292,13239.359 H 11186.282 C 11200.746,13239.359 11213.528,13246.423 11221.937,13256.85 V 13314.032 C 11213.528,13324.796 11200.746,13331.523 11186.282,13331.523 H 11150.292 C 11125.065,13331.523 11104.546,13310.669 11104.546,13285.441 V 13285.441 C 11104.546,13260.214 11125.065,13239.359 11150.292,13239.359 Z M 11168.455,13478.177 C 11193.683,13478.177 11214.201,13498.696 11214.201,13523.923 11214.201,13549.486 11193.683,13570.005 11168.455,13570.005 11142.892,13570.005 11122.374,13549.486 11122.374,13523.923 11122.374,13498.696 11142.892,13478.177 11168.455,13478.177 Z M 14705.319,13239.359 H 14741.311 C 14766.538,13239.359 14787.392,13260.214 14787.392,13285.441 V 13285.441 C 14787.392,13310.669 14766.538,13331.523 14741.311,13331.523 H 14705.319 C 14689.51,13331.523 14675.719,13323.45 14667.311,13311.005 V 13259.877 C 14675.719,13247.769 14689.51,13239.359 14705.319,13239.359 Z M 14723.483,13478.514 C 14748.711,13478.514 14768.892,13498.696 14768.892,13523.923 14768.892,13549.15 14748.711,13569.669 14723.483,13569.669 14698.256,13569.669 14677.738,13549.15 14677.738,13523.923 14677.738,13498.696 14698.256,13478.514 14723.483,13478.514 Z M 12236.746,11583.104 C 11773.91,11583.104 11394.492,11962.523 11394.492,12425.359 V 13841.45 C 11394.492,14304.287 11773.91,14683.706 12236.746,14683.706 H 13652.837 C 14115.674,14683.706 14495.093,14304.287 14495.093,13841.45 V 12425.359 C 14495.093,11962.523 14115.674,11583.104 13652.837,11583.104 Z"
id="path1"
style="fill:#003399;stroke:none;stroke-width:10.7404;stroke-dasharray:none"
sodipodi:nodetypes="cssssccssscsssscsssscsssscssccssssssssssssssssccsssssssssssssss" /></g>
<g
id="g1"
transform="matrix(0.951014 0 0 0.951014 1114.35 1312.15)"
style="stroke-width:1.05151"><path
id="path2"
style="fill:#bcbcbc;fill-opacity:1;stroke-width:120.295;stroke-linejoin:round"
sodipodi:type="inkscape:offset"
inkscape:radius="521.09528"
inkscape:original="M 8464.0547 1831.6582 L 4997.5117 1861.8086 C 4856.1909 1863.0871 4721.1611 1920.4298 4622.1055 2021.2324 L 2192.3125 4493.7891 C 2093.1704 4594.6337 2038.1605 4730.7389 2039.3906 4872.1504 L 2069.541 8338.498 C 2070.7673 8479.8872 2128.116 8614.9977 2228.9668 8714.1016 L 4701.5215 11143.895 C 4802.3983 11242.964 4938.5013 11297.899 5079.8848 11296.617 L 8546.2305 11266.467 C 8687.6198 11265.241 8822.7302 11207.893 8921.834 11107.043 L 11351.627 8634.4863 C 11450.648 8533.658 11505.583 8397.6378 11504.352 8256.3223 L 11474.201 4789.7773 C 11472.922 4648.4566 11415.577 4513.4285 11314.775 4414.373 L 8842.2207 1984.5801 C 8741.4246 1885.4864 8605.3984 1830.4805 8464.0547 1831.6582 z M 8252.6055 2899.5449 L 10410.057 5019.9492 L 10436.465 8044.873 L 8316.0605 10202.521 L 5291.332 10228.73 L 3133.6836 8108.5254 L 3107.2773 5083.5996 L 5227.6816 2925.9512 L 8252.6055 2899.5449 z "
d="M 8459.7129,1310.5801 A 521.14739,521.14739 0 0 0 8459.5234,1310.582 L 4992.9805,1340.7324 A 521.14739,521.14739 0 0 0 4992.7969,1340.7344 C 4713.5353,1343.2608 4446.1739,1456.8016 4250.4297,1655.998 L 1820.7188,4128.4687 C 1820.7107,4128.477 1820.7034,4128.4859 1820.6953,4128.4941 L 1820.6426,4128.5469 C 1624.778,4327.8162 1515.884,4597.2763 1518.3145,4876.6836 L 1548.4648,8343.0313 C 1550.8917,8622.4251 1664.4424,8889.9381 1863.7246,9085.7715 L 1863.7266,9085.7734 C 1863.7279,9085.7747 1863.7292,9085.7761 1863.7305,9085.7773 L 4336.2793,11515.564 A 521.14739,521.14739 0 0 0 4336.3984,11515.682 C 4535.7438,11711.455 4805.2284,11820.225 5084.6094,11817.691 L 8550.748,11787.543 C 8830.1468,11785.12 9097.6666,11671.571 9293.5039,11472.285 L 9293.5059,11472.283 11723.297,8999.7285 A 521.14739,521.14739 0 0 0 11723.414,8999.6094 C 11919.083,8800.3688 12027.858,8531.0479 12025.428,8251.791 V 8251.7891 L 11995.277,4785.2461 A 521.14739,521.14739 0 0 0 11995.275,4785.0605 C 11992.748,4505.7972 11879.204,4238.4418 11680.018,4042.7031 L 11680.016,4042.7012 9207.5391,1612.9863 C 9008.3554,1417.1668 8739.0223,1308.2528 8459.7129,1310.5801 Z M 8041.2969,3422.5059 9890.8652,5240.3125 9913.5059,7833.5781 8095.6973,9683.3164 5502.627,9705.7852 3652.875,7888.1328 3630.2363,5294.8945 5448.0605,3445.1426 Z" /><path
style="opacity:0.5;fill:none;fill-rule:evenodd;stroke:#53201c;stroke-width:58.7849;stroke-linejoin:bevel"
d="M 6696.4014,3616.0713 6779.6649,951.63941 M 9610.6236,6405.3984 H 12108.528 M 6696.4012,9361.2526 V 12067.316 M 3907.074,6738.4524 H 1117.7469 M 2824.6486,2408.7506 5010.3154,4594.4174 M 10693.049,2658.5411 8736.3568,4448.7062 M 10776.313,10402.046 8673.9092,8299.643 M 2783.0168,10443.678 4822.9725,8403.7223"
id="path3" /><path
d="M 1534.51 6738.45 L 1518.31 4876.68 L 1522.57 4772.47 L 1536.98 4669.94 L 1561.26 4569.73 L 1595.12 4472.54 L 1638.27 4379.13 L 1690.43 4290.23 L 1751.32 4206.45 L 1820.64 4128.55 L 1820.69 4128.47 L 1820.72 4128.47 L 3170.65 2754.76 L 4250.41 1655.99 L 4327.08 1585.36 L 4409.73 1523.05 L 4497.71 1469.37 L 4590.28 1424.61 L 4686.79 1389.07 L 4786.5 1363.04 L 4888.74 1346.83 L 4992.78 1340.73 L 4992.99 1340.73 L 6767.99 1325.29 L 8459.51 1310.58 L 8459.72 1310.58 L 8563.85 1314.87 L 8666.37 1329.31 L 8766.55 1353.61 L 8863.65 1387.48 L 8957.02 1430.63 L 9045.92 1482.79 L 9129.66 1543.67 L 9207.51 1612.98 L 10474.7 2858.28 L 11680 4042.71 L 11680 4042.71 L 11750.7 4119.35 L 11813 4201.99 L 11866.6 4289.97 L 11911.4 4382.54 L 11946.9 4479.05 L 11972.9 4578.77 L 11989.2 4681 L 11995.3 4785.04 L 11995.3 4785.25 L 12009.4 6405.39 L 12025.4 8251.77 L 12025.4 8251.77 L 12021.2 8355.94 L 12006.8 8458.43 L 11982.5 8558.6 L 11948.7 8655.71 L 11905.6 8749.07 L 11853.5 8837.98 L 11792.7 8921.71 L 11723.4 8999.61 L 11723.3 8999.73 L 10558.9 10184.6 L 9293.48 11472.3 L 9293.48 11472.3 L 9216.8 11542.9 L 9134.12 11605.3 L 9046.09 11659 L 8953.44 11703.7 L 8856.89 11739.3 L 8757.13 11765.3 L 8654.86 11781.5 L 8550.73 11787.5 L 6696.4 11803.7 L 5084.59 11817.7 L 4980.42 11813.5 L 4877.85 11799.1 L 4777.63 11774.8 L 4680.49 11741 L 4587.08 11698 L 4498.13 11645.8 L 4414.31 11585 L 4336.37 11515.7 L 4336.29 11515.6 L 3012.25 10214.4 L 1863.73 9085.78 L 1863.72 9085.78 L 1863.72 9085.74 L 1793.06 9009.07 L 1730.73 8926.38 L 1677.04 8838.36 L 1632.27 8745.75 L 1596.73 8649.19 L 1570.72 8549.39 L 1554.53 8447.12 L 1548.46 8343.04 L 1534.51 6738.45 M 3642.85 6738.45 L 3630.24 5294.91 L 4661.44 4245.54 L 5448.04 3445.12 L 6702.08 3434.19 L 8041.29 3422.5 L 8917.12 4283.29 L 9890.86 5240.3 L 9901.04 6405.39 L 9913.48 7833.55 L 9068.08 8693.83 L 8095.68 9683.29 L 6696.4 9695.44 L 5502.6 9705.78 L 4503.09 8723.59 L 3652.86 7888.11 L 3642.85 6738.45 M 6696.4 3616.08 L 6779.68 951.638 M 9610.61 6405.39 L 12108.5 6405.39 M 6696.4 9361.25 L 6696.4 12067.3 M 3907.05 6738.45 L 1117.75 6738.45 M 2824.65 2408.75 L 5010.31 4594.4 M 10693.1 2658.54 L 8736.37 4448.7 M 10776.3 10402 L 8673.9 8299.65 M 2783.01 10443.7 L 4822.95 8403.69"
style="stroke:#ff0000;fill:none;stroke-width:158.87762839;stroke-dasharray:none;stroke-opacity:1"
inkstitch:satin_column="True"
inkscape:label="Satin 1"
id="path4" /></g></g>
</svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 9.5 KiB

Wyświetl plik

@ -19,6 +19,7 @@ from .density_map import DensityMap
from .display_stacking_order import DisplayStackingOrder
from .duplicate_params import DuplicateParams
from .element_info import ElementInfo
from .fill_to_satin import FillToSatin
from .fill_to_stroke import FillToStroke
from .flip import Flip
from .generate_palette import GeneratePalette
@ -33,10 +34,10 @@ from .layer_commands import LayerCommands
from .lettering import Lettering
from .lettering_along_path import LetteringAlongPath
from .lettering_custom_font_dir import LetteringCustomFontDir
from .lettering_edit_json import LetteringEditJson
from .lettering_font_sample import LetteringFontSample
from .lettering_force_lock_stitches import LetteringForceLockStitches
from .lettering_generate_json import LetteringGenerateJson
from .lettering_edit_json import LetteringEditJson
from .lettering_remove_kerning import LetteringRemoveKerning
from .lettering_set_color_sort_index import LetteringSetColorSortIndex
from .letters_to_font import LettersToFont
@ -88,6 +89,7 @@ __all__ = extensions = [About,
DisplayStackingOrder,
DuplicateParams,
ElementInfo,
FillToSatin,
FillToStroke,
Flip,
GeneratePalette,

Wyświetl plik

@ -0,0 +1,405 @@
# 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 collections import defaultdict
from inkex import Boolean, Group, Path, PathElement
from shapely.geometry import LineString, MultiLineString, Point
from shapely.ops import linemerge, snap, split, substring
from ..elements import FillStitch, Stroke
from ..gui.abort_message import AbortMessageApp
from ..i18n import _
from ..svg import get_correction_transform
from ..utils import ensure_multi_line_string
from .base import InkstitchExtension
class FillToSatin(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.arg_parser.add_argument("--notebook")
self.arg_parser.add_argument("--skip_end_section", dest="skip_end_section", type=Boolean, default=False)
self.arg_parser.add_argument("--center", dest="center", type=Boolean, default=False)
self.arg_parser.add_argument("--contour", dest="contour", type=Boolean, default=False)
self.arg_parser.add_argument("--zigzag", dest="zigzag", type=Boolean, default=False)
self.arg_parser.add_argument("--keep_originals", dest="keep_originals", type=Boolean, default=False)
# geometries
self.line_sections = []
self.selected_rungs = []
self.rungs = [] # selection of valid rungs for the specific fill
# relations
self.rung_sections = defaultdict(list)
self.section_rungs = defaultdict(list)
self.bridged_sections = []
self.rung_segments = {}
self.satin_index = 1
def effect(self):
if not self.svg.selected or not self.get_elements():
self.print_error()
return
fill_elements = self._get_shapes()
if not fill_elements or not self.selected_rungs:
self.print_error()
return
for fill_element in fill_elements:
fill_shape = fill_element.shape
fill_linestrings = self._fill_to_linestrings(fill_shape)
for linestrings in fill_linestrings:
# Reset variables
self.rungs = []
self.line_sections = []
self.rung_sections = defaultdict(list)
self.section_rungs = defaultdict(list)
self.bridged_sections = []
self.rung_segments = {}
intersection_points, bridges = self._validate_rungs(linestrings)
self._generate_line_sections(linestrings)
self._define_relations(bridges)
if len(self.line_sections) == 2 and self.line_sections[0].distance(self.line_sections[1]) > 0:
# there is only one segment, add it directly
rails = [MultiLineString([self.line_sections[0], self.line_sections[1]])]
rungs = [ensure_multi_line_string(self.rungs[0])]
self._insert_satins(fill_element, [rails + rungs])
continue
else:
rung_segments, satin_segments = self._get_segments(intersection_points)
if len(self.rung_sections) == 2 and self.rung_sections[0] == self.rung_sections[1]:
combined_satins = self._get_two_rung_circle_geoms(rung_segments, satin_segments)
else:
combined_satins = self._get_satin_geoms(rung_segments, satin_segments)
self._insert_satins(fill_element, combined_satins)
self._remove_originals()
def _insert_satins(self, fill_element, combined_satins):
'''Insert satin elements into the document'''
if not combined_satins:
return
group = fill_element.node.getparent()
index = group.index(fill_element.node) + 1
transform = get_correction_transform(fill_element.node)
style = f'stroke: {fill_element.color}; fill: none; stroke-width: {self.svg.viewport_to_unit("1px")};'
if len(combined_satins) > 1:
new_group = Group()
group.insert(index, new_group)
group = new_group
group.label = _("Satin Group")
index = 0
for i, satin in enumerate(combined_satins):
node = PathElement()
d = ""
for segment in satin:
for geom in segment.geoms:
d += str(Path(list(geom.coords)))
node.set('d', d)
node.set('style', style)
node.set('inkstitch:satin_column', True)
if self.options.center:
node.set('inkstitch:center_walk_underlay', True)
if self.options.contour:
node.set('inkstitch:contour_underlay', True)
if self.options.zigzag:
node.set('inkstitch:zigzag_underlay', True)
node.transform = transform
node.apply_transform()
node.label = _("Satin") + f" {self.satin_index}"
group.insert(index, node)
self.satin_index += 1
def _remove_originals(self):
'''Remove original elements - if requested'''
if not self.options.keep_originals:
for element in self.elements:
element.node.getparent().remove(element.node)
def _get_two_rung_circle_geoms(self, rung_segments, satin_segments):
'''Imagine a donut with two rungs: this is a special case where all segments connect to the very same two rungs'''
combined = defaultdict(list)
combined_rungs = defaultdict(list)
combined[0] = [0, 1]
combined_rungs[0] = [0, 1]
return self._combined_segments_to_satin_geoms(combined, combined_rungs, satin_segments)
def _get_satin_geoms(self, rung_segments, satin_segments):
'''Combine segments and return satin geometries'''
self.rung_segments = {rung: segments for rung, segments in rung_segments.items() if len(segments) == 2}
finished_rungs = []
finished_segments = []
combined_rails = defaultdict(list)
combined_rungs = defaultdict(list)
for rung, segments in self.rung_segments.items():
self._find_connected(rung, segments, rung, finished_rungs, finished_segments, combined_rails, combined_rungs)
unfinished = {i for i, segment in enumerate(satin_segments) if i not in finished_segments}
segment_count = len(satin_segments)
for i, segment in enumerate(unfinished):
index = segment_count + i + 1
combined_rails[index] = [segment]
return self._combined_segments_to_satin_geoms(combined_rails, combined_rungs, satin_segments)
def _combined_segments_to_satin_geoms(self, combined_rails, combined_rungs, satin_segments):
combined_satins = []
for i, segments in combined_rails.items():
segment_geoms = []
for segment_index in set(segments):
segment_geoms.extend(list(satin_segments[segment_index].geoms))
satin_rails = ensure_multi_line_string(linemerge(segment_geoms))
satin_rails = [self._adjust_rail_direction(satin_rails)]
segment_geoms = []
for rung_index in set(combined_rungs[i]):
rung = self.rungs[rung_index]
# satin behaves bad if a rung is positioned directly at the beginning/end section
if rung.distance(Point(satin_rails[0].geoms[0].coords[0])) > 1:
segment_geoms.append(ensure_multi_line_string(rung))
combined_satins.append(satin_rails + segment_geoms)
return combined_satins
def _get_segments(self, intersection_points): # noqa: C901
'''Combine line sections to satin segments (find the rails that belong together)'''
line_section_multi = MultiLineString(self.line_sections)
rung_segments = defaultdict(list)
satin_segments = []
segment_index = 0
finished_sections = []
for i, section in enumerate(self.line_sections):
if i in finished_sections:
continue
s_rungs = self.section_rungs[i]
if len(s_rungs) == 1:
if self.options.skip_end_section and len(self.rungs) > 1:
continue
segment = self._get_end_segment(section)
satin_segments.append(segment)
finished_sections.append(i)
for rung in s_rungs:
rung_segments[rung].append(segment_index)
segment_index += 1
elif len(s_rungs) == 2:
connected_section = self._get_connected_section(i, s_rungs)
if connected_section:
connect_index, segment = self._get_standard_segment(connected_section, s_rungs, section, finished_sections)
if segment is None:
continue
satin_segments.append(segment)
for rung in s_rungs:
rung_segments[rung].append(segment_index)
segment_index += 1
finished_sections.extend([i, connect_index])
elif i in self.bridged_sections:
segment = self._get_bridged_segment(section, s_rungs, intersection_points, line_section_multi)
if segment:
satin_segments.append(segment)
for rung in s_rungs:
rung_segments[rung].append(segment_index)
segment_index += 1
finished_sections.append(i)
else:
# sections with multiple rungs, open ends, not bridged
# IF users define their rungs well, they won't have a problem if we just ignore these sections
# otherwise they will see some sort of gap, they can close it manually if they want
pass
return rung_segments, satin_segments
def _get_end_segment(self, section):
section = section.simplify(0.5)
rail1 = substring(section, 0, 0.40009, True).coords
rail2 = substring(section, 0.50001, 1, True).coords
if len(rail1) > 2:
rail1 = rail1[:-1]
if len(rail2) > 2:
rail2 = rail2[1:]
segment = MultiLineString([LineString(rail1), LineString(rail2)])
return segment
def _get_standard_segment(self, connected_section, s_rungs, section, finished_sections):
section2 = None
segment = None
connect_index = None
if len(connected_section) == 1:
section2 = self.line_sections[connected_section[0]]
connect_index = connected_section[0]
else:
for connect in connected_section:
if connect in finished_sections:
continue
offset_rung = self.rungs[s_rungs[0]].offset_curve(0.01)
section_candidate = self.line_sections[connect]
if offset_rung.intersects(section) == offset_rung.intersects(section_candidate):
section2 = section_candidate
connect_index = connect
break
if section2 is not None:
segment = MultiLineString([section, section2])
return connect_index, segment
def _get_bridged_segment(self, section, s_rungs, intersection_points, line_section_multi):
segment = None
bridge_points = []
# create bridge
for rung in s_rungs:
rung_points = intersection_points[rung].geoms
for point in rung_points:
if point.distance(section) > 0.01:
bridge_points.append(point)
if len(bridge_points) == 2:
rung = self.rungs[s_rungs[0]]
bridge = LineString(bridge_points)
bridge = snap(bridge, line_section_multi, 0.0001)
segment = MultiLineString([section, bridge])
return segment
def _get_connected_section(self, index, s_rungs):
rung_section_list = []
for rung in s_rungs:
connections = self.rung_sections[rung]
rung_section_list.append(connections)
connected_section = list(set(rung_section_list[0]) & set(rung_section_list[1]))
connected_section.remove(index)
return connected_section
def _adjust_rail_direction(self, satin_rails):
# See also elements/satin_column.py (_get_rails_to_reverse)
rails = list(satin_rails.geoms)
lengths = []
lengths_reverse = []
for i in range(10):
distance = i / 10
point0 = rails[0].interpolate(distance, normalized=True)
point1 = rails[1].interpolate(distance, normalized=True)
point1_reverse = rails[1].interpolate(1 - distance, normalized=True)
lengths.append(point0.distance(point1))
lengths_reverse.append(point0.distance(point1_reverse))
if sum(lengths) > sum(lengths_reverse):
rails[0] = rails[0].reverse()
return MultiLineString(rails)
def _find_connected(self, rung, segments, first_rung, finished_rungs, finished_segments, combined_rails, combined_rungs):
'''Group combinable segments'''
if rung in finished_rungs:
return
finished_rungs.append(rung)
combined_rails[first_rung].extend(segments)
combined_rungs[first_rung].append(rung)
finished_segments.extend(segments)
for segment in segments:
connected = self._get_combinable_segments(segment, segments)
if not connected:
continue
for connected_rung, connected_segments in connected.items():
self._find_connected(
connected_rung,
connected_segments,
first_rung, finished_rungs,
finished_segments,
combined_rails,
combined_rungs
)
def _get_combinable_segments(self, segment, segments_in):
'''Finds the segments which are neighboring this segment'''
return {rung: segments for rung, segments in self.rung_segments.items() if segment in segments and segments_in != segments}
def _generate_line_sections(self, fill_linestrings):
'''Splits the fill outline into sections. Splitter is a MultiLineString with all available rungs'''
rungs = MultiLineString(self.rungs)
for line in fill_linestrings:
sections = list(ensure_multi_line_string(split(line, rungs)).geoms)
if len(sections) > 1:
# merge end and start section
sections[0] = linemerge(MultiLineString([sections[0], sections[-1]]))
del sections[-1]
self.line_sections.extend(sections)
def _define_relations(self, bridges):
''' Defines information about the relations between line_sections and rungs
rung_sections: dictionary with rung_index: neighboring sections
section_rungs: dictionary with section_id: neighboring rungs
bridged_sections: list of sections which the user marked for bridging
'''
for i, section in enumerate(self.line_sections):
if not section.intersection(bridges).is_empty:
self.bridged_sections.append(i)
for j, rung in enumerate(self.rungs):
if section.distance(rung) < 0.01:
self.section_rungs[i].append(j)
self.rung_sections[j].append(i)
def _validate_rungs(self, linestrings):
''' Returns only valid rungs and bridge section markers'''
multi_line_string = MultiLineString(linestrings)
valid_rungs = []
bridge_indicators = []
intersection_points = []
for rung in self.selected_rungs:
intersection = multi_line_string.intersection(rung)
if intersection.geom_type == 'MultiPoint' and len(intersection.geoms) == 2:
valid_rungs.append(rung)
intersection_points.append(intersection)
elif intersection.geom_type == 'Point':
# these rungs help to indicate how the satin section should be connected
bridge_indicators.append(rung)
self.rungs = valid_rungs
return intersection_points, MultiLineString(bridge_indicators)
def _fill_to_linestrings(self, fill_shape):
'''Takes a fill shape (Multipolygon) and returns the shape as a list of linestrings'''
fill_linestrings = []
for polygon in fill_shape.geoms:
linestrings = ensure_multi_line_string(polygon.boundary, 1)
fill_linestrings.append(list(linestrings.geoms))
return fill_linestrings
def _get_shapes(self):
'''Filter selected elements. Take rungs and fills.'''
fill_elements = []
nodes = []
warned = False
for element in self.elements:
if element.node in nodes and not warned:
self.print_error(
(f'{element.node.label} ({element.node.get_id()}): ' + _("This element has a fill and a stroke.\n\n"
"Rungs only have a stroke color and fill elements a fill color."))
)
warned = True
nodes.append(element.node)
if isinstance(element, FillStitch):
fill_elements.append(element)
elif isinstance(element, Stroke):
self.selected_rungs.extend(list(element.as_multi_line_string().geoms))
return fill_elements
def print_error(self, message=_("Please select a fill object and rungs.")):
'''We did not receive the rigth elements, inform user'''
app = AbortMessageApp(
message,
_("https://inkstitch.org/satin-tools#fill-to-satin")
)
app.MainLoop()

Wyświetl plik

@ -227,7 +227,7 @@ class FillToStroke(InkstitchExtension):
continue
def _close_gaps(self, lines, cut_lines):
snaped_lines = []
snapped_lines = []
lines = MultiLineString(lines)
for i, line in enumerate(lines.geoms):
# for each cutline check if a line starts or ends close to it
@ -239,16 +239,16 @@ class FillToStroke(InkstitchExtension):
l_l = lines.difference(line)
for cut_line in cut_lines:
distance = start.distance(l_l)
if cut_line.distance(start) < 0.6:
if cut_line.distance(start) < 1:
distance = start.distance(l_l)
new_start_point = self._extend_line(line.coords[0], line.coords[1], distance)
coords[0] = nearest_points(Point(list(new_start_point)), l_l)[1]
if cut_line.distance(end) < 0.6:
if cut_line.distance(end) < 1:
distance = end.distance(l_l)
new_end_point = self._extend_line(line.coords[-1], line.coords[-2], distance)
coords[-1] = nearest_points(Point(list(new_end_point)), l_l)[1]
snaped_lines.append(LineString(coords))
return snaped_lines
snapped_lines.append(LineString(coords))
return snapped_lines
def _extend_line(self, p1, p2, distance):
start_point = InkstitchPoint.from_tuple(p1)

Wyświetl plik

@ -577,7 +577,7 @@ class Font(object):
group.append(color_group)
def _get_color_sorted_elements(self, group, transform_key): # noqa: 901
def _get_color_sorted_elements(self, group, transform_key): # noqa: C901
elements_by_color = defaultdict(list)
last_parent = None

Wyświetl plik

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Fill to satin</name>
<id>org.{{ id_inkstitch }}.fill_to_satin</id>
<param name="extension" type="string" gui-hidden="true">fill_to_satin</param>
<param name="notebook" type="notebook">
<page name="options" gui-text="Options">
<param name="skip_end_section" type="boolean" gui-text="Start / end at rung"
gui-description="Needs at least 2 rungs">false</param>
<spacer />
<separator />
<spacer />
<param name="center" type="boolean" gui-text="Center-walk underlay">false</param>
<param name="contour" type="boolean" gui-text="Contour underlay">false</param>
<param name="zigzag" type="boolean" gui-text="Zig-zag underlay">false</param>
<spacer />
<separator />
<spacer />
<param name="keep_originals" type="boolean" gui-text="Keep original paths">false</param>
</page>
<page name="info" gui-text="Help">
<label>This extension takes in a fill and rung elements and converts them into satin(s).</label>
<spacer />
<label>More information on our website</label>
<label appearance="url">https://inkstitch.org/docs/satin-tools/#fill-to-satin</label>
</page>
</param>
<effect>
<object-type>all</object-type>
<icon>{{ icon_path }}inx/fill_to_satin.svg</icon>
<menu-tip>Convert fill elements to satin</menu-tip>
<effects-menu>
<submenu name="{{ menu_inkstitch }}" translatable="no">
<submenu name="Tools: Satin" />
</submenu>
</effects-menu>
</effect>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>