handle self-intersecting paths in convert to satin

pull/608/head
Lex Neva 2020-01-31 22:16:48 -05:00
rodzic 6a62e0d82b
commit d52373a32a
1 zmienionych plików z 27 dodań i 22 usunięć

Wyświetl plik

@ -1,18 +1,17 @@
from copy import deepcopy
from itertools import chain, groupby
import math
from itertools import chain, groupby
import inkex
from numpy import diff, sign, setdiff1d
import numpy
from numpy import diff, setdiff1d, sign
from shapely import geometry as shgeo
from .base import InkstitchExtension
from ..elements import Stroke
from ..i18n import _
from ..svg import get_correction_transform, PIXELS_PER_MM
from ..svg import PIXELS_PER_MM, get_correction_transform
from ..svg.tags import SVG_PATH_TAG
from ..utils import Point
from .base import InkstitchExtension
class SelfIntersectionError(Exception):
@ -49,24 +48,27 @@ class ConvertToSatin(InkstitchExtension):
# ignore paths with just one point -- they're not visible to the user anyway
continue
self.fix_loop(path)
try:
rails, rungs = self.path_to_satin(path, element.stroke_width, style_args)
except SelfIntersectionError:
inkex.errormsg(
_("Cannot convert %s to a satin column because it intersects itself. Try breaking it up into multiple paths.") %
element.node.get('id'))
# revert any changes we've made
self.document = deepcopy(self.original_document)
return
parent.insert(index, self.satin_to_svg_node(rails, rungs, correction_transform, path_style))
for satin in self.convert_path_to_satins(path, element.stroke_width, style_args, correction_transform, path_style):
parent.insert(index, satin)
index += 1
parent.remove(element.node)
def convert_path_to_satins(self, path, stroke_width, style_args, correction_transform, path_style):
try:
rails, rungs = self.path_to_satin(path, stroke_width, style_args)
yield self.satin_to_svg_node(rails, rungs, correction_transform, path_style)
except SelfIntersectionError:
# The path intersects itself. Split it in two and try doing the halves
# individually.
half = int(len(path) / 2.0)
halves = [path[:half + 1], path[half:]]
for path in halves:
for satin in self.convert_path_to_satins(path, stroke_width, style_args, correction_transform, path_style):
yield satin
def fix_loop(self, path):
if path[0] == path[-1]:
# Looping paths seem to confuse shapely's parallel_offset(). It loses track
@ -107,13 +109,16 @@ class ConvertToSatin(InkstitchExtension):
return args
def path_to_satin(self, path, stroke_width, style_args):
if Point(*path[0]).distance(Point(*path[-1])) < 1:
raise SelfIntersectionError()
path = shgeo.LineString(path)
left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
if not isinstance(left_rail, shgeo.LineString) or \
not isinstance(right_rail, shgeo.LineString):
not isinstance(right_rail, shgeo.LineString):
# If the parallel offsets come out as anything but a LineString, that means the
# path intersects itself, when taking its stroke width into consideration. See
# the last example for parallel_offset() in the Shapely documentation:
@ -248,7 +253,7 @@ class ConvertToSatin(InkstitchExtension):
# arbitrarily rejects the rung if there was one less than 2
# millimeters before this one.
if last_rung_center is not None and \
(rung_center - last_rung_center).length() < 2 * PIXELS_PER_MM:
(rung_center - last_rung_center).length() < 2 * PIXELS_PER_MM:
continue
else:
last_rung_center = rung_center