kopia lustrzana https://github.com/inkstitch/inkstitch
Fill to satin (#3406)
rodzic
be66297da6
commit
51ee6c7a08
|
@ -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 |
|
@ -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,
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
Ładowanie…
Reference in New Issue