66from collections import defaultdict
77
88from inkex import Boolean , Group , Path , PathElement
9- from shapely .geometry import LineString , MultiLineString , Point
9+ from shapely .geometry import LineString , MultiLineString , MultiPoint , Point
1010from shapely .ops import linemerge , snap , split , substring
1111
1212from ..elements import FillStitch , Stroke
1313from ..gui .abort_message import AbortMessageApp
1414from ..i18n import _
1515from ..svg import get_correction_transform
16- from ..utils import ensure_multi_line_string
16+ from ..utils import ensure_multi_line_string , roll_linear_ring
1717from .base import InkstitchExtension
1818
1919
@@ -222,18 +222,101 @@ def _combined_segments_to_satin_geoms(self, combined_rails, combined_rungs, sati
222222 for segment_index in set (segments ):
223223 segment_geoms .extend (list (satin_segments [segment_index ].geoms ))
224224 satin_rails = ensure_multi_line_string (linemerge (segment_geoms ))
225- if len (satin_rails .geoms ) != 2 :
225+ if len (satin_rails .geoms ) == 1 :
226+ satin_rails = self ._fix_single_rail_issue (satin_rails , segments , satin_segments , set (combined_rungs [i ]))
227+ if satin_rails is None :
228+ continue
229+ elif len (satin_rails .geoms ) != 2 :
226230 continue
231+
232+ # adjust rail direction and starting points
227233 satin_rails = [self ._adjust_rail_direction (satin_rails )]
234+ satin_rails = [self ._adjust_closed_path_starting_point (satin_rails , self .rungs [combined_rungs [i ][0 ]])]
235+
228236 segment_geoms = []
229237 for rung_index in set (combined_rungs [i ]):
230238 rung = self .rungs [rung_index ]
231239 # satin behaves bad if a rung is positioned directly at the beginning/end section
232- if rung .distance (Point (satin_rails [0 ].geoms [0 ].coords [0 ])) > 1 :
240+ start = Point (satin_rails [0 ].geoms [0 ].coords [0 ])
241+ end = Point (satin_rails [0 ].geoms [1 ].coords [- 1 ])
242+ if rung .distance (start ) > 1 and rung .distance (end ) > 1 :
233243 segment_geoms .append (ensure_multi_line_string (rung ))
244+
234245 combined_satins .append (satin_rails + segment_geoms )
235246 return combined_satins
236247
248+ def _adjust_closed_path_starting_point (self , rails , rung ):
249+ # closed paths may need adjustments of the starting point
250+ rail1 = rails [0 ].geoms [0 ]
251+ rail2 = rails [0 ].geoms [1 ]
252+ if rail1 .coords [0 ] == rail1 .coords [- 1 ]:
253+ rail1 = self ._adjust_rail_starting_point (rail1 , rail1 .intersection (rung ))
254+ rail2 = self ._adjust_rail_starting_point (rail2 , rail2 .intersection (rung ))
255+ return MultiLineString ([rail1 , rail2 ])
256+
257+ def _adjust_rail_starting_point (self , rail , point ):
258+ if point .geom_type == "Point" :
259+ position = rail .project (point )
260+ return LineString (roll_linear_ring (rail , position ))
261+ return rail
262+
263+ def _fix_single_rail_issue (self , satin_rails , segments , satin_segments , combined_rungs ):
264+ # This is a special case where the two satin rails have been combined into one.
265+ # It can happen if we try to convert for example a B with a single satin column
266+ # (it starts and ends at the center).
267+ # ---
268+ # | \
269+ # | /
270+ # |==x
271+ # | \
272+ # | /
273+ # ---
274+ # We can face two situations:
275+ # 1. the rung at the intersection is within the combined_rungs, in this case we need to watch out for a rung which is bridged twice
276+ # 2. the rung isn't within the selection, adjacing bridged segments have only one connecting rung
277+
278+ # Case 1: the rung is within the selection and is bridged twice
279+ intersection = self ._fix_single_rail_issue_rung_included (satin_rails , combined_rungs )
280+ if intersection .is_empty :
281+ # Case 2: check for segments with only one adjacent rung
282+ intersection = self ._fix_single_rail_issue_rung_excluded (satin_segments , segments , combined_rungs )
283+ if intersection .geom_type == 'MultiPoint' :
284+ position = satin_rails .project (intersection .geoms [0 ])
285+ satin_rails = LineString (roll_linear_ring (satin_rails .geoms [0 ], position ))
286+ return ensure_multi_line_string (split (satin_rails , intersection ))
287+ return None
288+
289+ def _fix_single_rail_issue_rung_included (self , satin_rails , combined_rungs ):
290+ for rung in combined_rungs :
291+ rung_bridges = []
292+ for bridge , rungs in self .bridged_rungs .items ():
293+ if rung in rungs :
294+ rung_bridges .append (1 )
295+ if len (rung_bridges ) > 1 :
296+ rung_geom = snap (self .rungs [rung ], satin_rails , 0.001 )
297+ return satin_rails .intersection (rung_geom )
298+ return Point ()
299+
300+ def _fix_single_rail_issue_rung_excluded (self , satin_segments , segments , combined_rungs ):
301+ single_rung_segments = []
302+ for segment_index in set (segments ):
303+ geom = satin_segments [segment_index ]
304+ segment_rungs = []
305+ for rung_index in combined_rungs :
306+ if geom .distance (self .rungs [rung_index ]) < 0.001 :
307+ segment_rungs .append (rung_index )
308+ if len (segment_rungs ) == 1 :
309+ single_rung_segments .append (geom )
310+ if len (single_rung_segments ) == 2 :
311+ points = []
312+ for seg in single_rung_segments :
313+ segment_end_points = []
314+ for g in seg .geoms :
315+ segment_end_points .extend ([g .coords [0 ], g .coords [- 1 ]])
316+ points .append (segment_end_points )
317+ return MultiPoint (points [0 ]).intersection (MultiPoint (points [1 ]))
318+ return Point ()
319+
237320 def _get_segments (self , intersection_points ): # noqa: C901
238321 '''Combine line sections to satin segments (find the rails that belong together)'''
239322 line_section_multi = MultiLineString (self .line_sections )
@@ -275,9 +358,9 @@ def _get_segments(self, intersection_points): # noqa: C901
275358 if len (rung_list ) != 2 :
276359 continue
277360 for rung in s_rungs :
361+ if bridge in used_bridges :
362+ continue
278363 if rung in rung_list :
279- if bridge in used_bridges :
280- continue
281364 rung1 = rung_list [0 ]
282365 rung2 = rung_list [1 ]
283366 segment = self ._get_bridged_segment (rung1 , rung2 , intersection_points , line_section_multi )
@@ -294,6 +377,21 @@ def _get_segments(self, intersection_points): # noqa: C901
294377 # IF users define their rungs well, they won't have a problem if we just ignore these sections
295378 # otherwise they will see some sort of gap, they can close it manually if they want
296379 pass
380+
381+ # create segments for unused bridge segments
382+ unused_bridges = set (self .bridged_rungs .keys ()) - set (used_bridges )
383+ if unused_bridges :
384+ for bridge in unused_bridges :
385+ rungs = self .bridged_rungs [bridge ]
386+ if len (rungs ) != 2 :
387+ continue
388+ segment = self ._get_bridged_segment (rungs [0 ], rungs [1 ], intersection_points , line_section_multi )
389+ if not segment :
390+ continue
391+ satin_segments .append (segment )
392+ rung_segments [rungs [0 ]].append (segment_index )
393+ rung_segments [rungs [1 ]].append (segment_index )
394+ segment_index += 1
297395 return rung_segments , satin_segments
298396
299397 def _get_bridged_segment (self , rung1 , rung2 , intersection_points , line_section_multi ):
@@ -485,8 +583,7 @@ def _validate_rungs(self):
485583 # these rungs (possibly) connect two rungs
486584 bridges .append (rung )
487585 elif intersection .geom_type == 'Point' :
488- # half rungs will can mark a bridge endpoint at an open end within the shape
489- # intersection_points.append(intersection)
586+ # half rungs can mark a bridge endpoint at an open end within the shape
490587 half_rungs .append (rung )
491588 # filter rungs when they are crossing other rungs. They could possibly produce bad line sections
492589 for i , rung in enumerate (rungs ):
@@ -511,9 +608,10 @@ def _validate_bridges(self, bridges, intersection_points):
511608 multi_rung = MultiLineString (self .rungs )
512609 # find elements marked as bridges, but don't intersect with any other rung.
513610 # they may be rungs drawn inside of a shape, so let's add them to the rungs and see if they are helpful
514- for bridge in bridges :
611+ for i , bridge in enumerate ( bridges ) :
515612 rung_intersections = bridge .intersection (multi_rung )
516- if rung_intersections .is_empty :
613+ bridge_intersections = bridge .intersection (MultiLineString ([b for j , b in enumerate (bridges ) if j != i ]))
614+ if rung_intersections .is_empty and not bridge_intersections .geom_type == "MultiPoint" :
517615 # doesn't intersect with any rungs, so it is a rung itself (when bridged)
518616 self .half_rungs .append (len (self .rungs ))
519617 self .rungs .append (bridge )
@@ -534,7 +632,6 @@ def _validate_bridges(self, bridges, intersection_points):
534632 distance1 = bridge .project (point1 ) - 0.1
535633 distance2 = bridge .project (point2 ) + 0.1
536634 validated_bridges .append (substring (bridge , distance1 , distance2 ))
537- validated_bridges .append (bridge )
538635 elif rung_intersections .geom_type == "Point" :
539636 # bridges a rung within the shape
540637 validated_bridges .append (bridge )
0 commit comments