* Closes #113 Previous fix stopped working because numpy is more liberal dividing by zero and returning nan values. * Closes #71 * Closes #95 * Closes #94pull/149/head
parent
091394b5e3
commit
3a1fe8695d
|
@ -18,7 +18,7 @@ from itertools import tee
|
||||||
# in order to encourage code that generalizes to vector inputs
|
# in order to encourage code that generalizes to vector inputs
|
||||||
from numpy import sqrt, cos, sin, tan, arccos as acos, arcsin as asin, \
|
from numpy import sqrt, cos, sin, tan, arccos as acos, arcsin as asin, \
|
||||||
degrees, radians, log, pi, ceil
|
degrees, radians, log, pi, ceil
|
||||||
from numpy import exp, sqrt as csqrt, angle as phase
|
from numpy import exp, sqrt as csqrt, angle as phase, isnan
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from scipy.integrate import quad
|
from scipy.integrate import quad
|
||||||
|
@ -898,31 +898,30 @@ class QuadraticBezier(object):
|
||||||
|
|
||||||
if abs(a) < 1e-12:
|
if abs(a) < 1e-12:
|
||||||
s = abs(b)*(t1 - t0)
|
s = abs(b)*(t1 - t0)
|
||||||
elif abs(a_dot_b + abs(a)*abs(b)) < 1e-12:
|
|
||||||
tstar = abs(b)/(2*abs(a))
|
|
||||||
if t1 < tstar:
|
|
||||||
return abs(a)*(t0**2 - t1**2) - abs(b)*(t0 - t1)
|
|
||||||
elif tstar < t0:
|
|
||||||
return abs(a)*(t1**2 - t0**2) - abs(b)*(t1 - t0)
|
|
||||||
else:
|
else:
|
||||||
return abs(a)*(t1**2 + t0**2) - abs(b)*(t1 + t0) + \
|
c2 = 4 * (a.real ** 2 + a.imag ** 2)
|
||||||
abs(b)**2/(2*abs(a))
|
c1 = 4 * a_dot_b
|
||||||
else:
|
c0 = b.real ** 2 + b.imag ** 2
|
||||||
c2 = 4*(a.real**2 + a.imag**2)
|
|
||||||
c1 = 4*a_dot_b
|
|
||||||
c0 = b.real**2 + b.imag**2
|
|
||||||
|
|
||||||
beta = c1/(2*c2)
|
beta = c1 / (2 * c2)
|
||||||
gamma = c0/c2 - beta**2
|
gamma = c0 / c2 - beta ** 2
|
||||||
|
|
||||||
dq1_mag = sqrt(c2*t1**2 + c1*t1 + c0)
|
dq1_mag = sqrt(c2 * t1 ** 2 + c1 * t1 + c0)
|
||||||
dq0_mag = sqrt(c2*t0**2 + c1*t0 + c0)
|
dq0_mag = sqrt(c2 * t0 ** 2 + c1 * t0 + c0)
|
||||||
logarand = (sqrt(c2)*(t1 + beta) + dq1_mag) / \
|
logarand = (sqrt(c2) * (t1 + beta) + dq1_mag) / \
|
||||||
(sqrt(c2)*(t0 + beta) + dq0_mag)
|
(sqrt(c2) * (t0 + beta) + dq0_mag)
|
||||||
|
s = (t1 + beta) * dq1_mag - (t0 + beta) * dq0_mag + \
|
||||||
s = (t1 + beta)*dq1_mag - (t0 + beta)*dq0_mag + \
|
gamma * sqrt(c2) * log(logarand)
|
||||||
gamma*sqrt(c2)*log(logarand)
|
|
||||||
s /= 2
|
s /= 2
|
||||||
|
if isnan(s):
|
||||||
|
tstar = abs(b) / (2 * abs(a))
|
||||||
|
if t1 < tstar:
|
||||||
|
return abs(a) * (t0 ** 2 - t1 ** 2) - abs(b) * (t0 - t1)
|
||||||
|
elif tstar < t0:
|
||||||
|
return abs(a) * (t1 ** 2 - t0 ** 2) - abs(b) * (t1 - t0)
|
||||||
|
else:
|
||||||
|
return abs(a) * (t1 ** 2 + t0 ** 2) - abs(b) * (t1 + t0) + \
|
||||||
|
abs(b) ** 2 / (2 * abs(a))
|
||||||
|
|
||||||
if t0 == 1 and t1 == 0:
|
if t0 == 1 and t1 == 0:
|
||||||
self._length_info['length'] = s
|
self._length_info['length'] = s
|
||||||
|
@ -2445,7 +2444,10 @@ class Path(MutableSequence):
|
||||||
lengths = [each.length(error=error, min_depth=min_depth) for each in
|
lengths = [each.length(error=error, min_depth=min_depth) for each in
|
||||||
self._segments]
|
self._segments]
|
||||||
self._length = sum(lengths)
|
self._length = sum(lengths)
|
||||||
self._lengths = [each/self._length for each in lengths]
|
if self._length == 0:
|
||||||
|
self._lengths = lengths # all lengths are 0.
|
||||||
|
else:
|
||||||
|
self._lengths = [each / self._length for each in lengths]
|
||||||
|
|
||||||
def point(self, pos):
|
def point(self, pos):
|
||||||
|
|
||||||
|
@ -2522,7 +2524,10 @@ class Path(MutableSequence):
|
||||||
return self.start == self.end
|
return self.start == self.end
|
||||||
|
|
||||||
def _is_closable(self):
|
def _is_closable(self):
|
||||||
|
try:
|
||||||
end = self[-1].end
|
end = self[-1].end
|
||||||
|
except IndexError:
|
||||||
|
return True
|
||||||
for segment in self:
|
for segment in self:
|
||||||
if segment.start == end:
|
if segment.start == end:
|
||||||
return True
|
return True
|
||||||
|
@ -2574,7 +2579,8 @@ class Path(MutableSequence):
|
||||||
"""Returns a path d-string for the path object.
|
"""Returns a path d-string for the path object.
|
||||||
For an explanation of useSandT and use_closed_attrib, see the
|
For an explanation of useSandT and use_closed_attrib, see the
|
||||||
compatibility notes in the README."""
|
compatibility notes in the README."""
|
||||||
|
if len(self) == 0:
|
||||||
|
return ''
|
||||||
if use_closed_attrib:
|
if use_closed_attrib:
|
||||||
self_closed = self.iscontinuous() and self.isclosed()
|
self_closed = self.iscontinuous() and self.isclosed()
|
||||||
if self_closed:
|
if self_closed:
|
||||||
|
@ -2866,8 +2872,7 @@ class Path(MutableSequence):
|
||||||
# redundant intersection. This code block checks for and removes said
|
# redundant intersection. This code block checks for and removes said
|
||||||
# redundancies.
|
# redundancies.
|
||||||
if intersection_list:
|
if intersection_list:
|
||||||
pts = [seg1.point(_t1)
|
pts = [_seg1.point(_t1) for _T1, _seg1, _t1 in list(zip(*intersection_list))[0]]
|
||||||
for _T1, _seg1, _t1 in list(zip(*intersection_list))[0]]
|
|
||||||
indices2remove = []
|
indices2remove = []
|
||||||
for ind1 in range(len(pts)):
|
for ind1 in range(len(pts)):
|
||||||
for ind2 in range(ind1 + 1, len(pts)):
|
for ind2 in range(ind1 + 1, len(pts)):
|
||||||
|
|
|
@ -687,7 +687,6 @@ class ArcTest(unittest.TestCase):
|
||||||
self.assertAlmostEqual(d,0.0, delta=2)
|
self.assertAlmostEqual(d,0.0, delta=2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestPath(unittest.TestCase):
|
class TestPath(unittest.TestCase):
|
||||||
|
|
||||||
def test_circle(self):
|
def test_circle(self):
|
||||||
|
@ -1660,7 +1659,6 @@ class Test_intersect(unittest.TestCase):
|
||||||
assert_intersections(a0, a1, intersections, 0)
|
assert_intersections(a0, a1, intersections, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestPathTools(unittest.TestCase):
|
class TestPathTools(unittest.TestCase):
|
||||||
# moved from test_pathtools.py
|
# moved from test_pathtools.py
|
||||||
|
|
||||||
|
@ -1950,5 +1948,50 @@ class TestPathTools(unittest.TestCase):
|
||||||
self.assertTrue(enclosing_shape.is_contained_by(larger_shape))
|
self.assertTrue(enclosing_shape.is_contained_by(larger_shape))
|
||||||
|
|
||||||
|
|
||||||
|
class TestPathBugs(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_issue_113(self):
|
||||||
|
"""
|
||||||
|
Tests against issue regebro/svg.path#61 mathandy/svgpathtools#113
|
||||||
|
"""
|
||||||
|
p = Path('M 206.5,525 Q 162.5,583 162.5,583')
|
||||||
|
self.assertAlmostEqual(p.length(), 72.80109889280519)
|
||||||
|
p = Path('M 425.781 446.289 Q 410.40000000000003 373.047 410.4 373.047')
|
||||||
|
self.assertAlmostEqual(p.length(), 74.83959997888816)
|
||||||
|
p = Path('M 639.648 568.115 Q 606.6890000000001 507.568 606.689 507.568')
|
||||||
|
self.assertAlmostEqual(p.length(), 68.93645544992873)
|
||||||
|
p = Path('M 288.818 616.699 Q 301.025 547.3629999999999 301.025 547.363')
|
||||||
|
self.assertAlmostEqual(p.length(), 70.40235610403947)
|
||||||
|
p = Path('M 339.927 706.25 Q 243.92700000000002 806.25 243.927 806.25')
|
||||||
|
self.assertAlmostEqual(p.length(), 138.6217876093077)
|
||||||
|
p = Path('M 539.795 702.637 Q 548.0959999999999 803.4669999999999 548.096 803.467')
|
||||||
|
self.assertAlmostEqual(p.length(), 101.17111989594662)
|
||||||
|
p = Path('M 537.815 555.042 Q 570.1680000000001 499.1600000000001 570.168 499.16')
|
||||||
|
self.assertAlmostEqual(p.length(), 64.57177814649368)
|
||||||
|
p = Path('M 615.297 470.503 Q 538.797 694.5029999999999 538.797 694.503')
|
||||||
|
self.assertAlmostEqual(p.length(), 236.70287281737836)
|
||||||
|
|
||||||
|
def test_issue_71(self):
|
||||||
|
p = Path("M327 468z")
|
||||||
|
m = p.closed
|
||||||
|
q = p.d() # Failing to Crash is good.
|
||||||
|
|
||||||
|
def test_issue_95(self):
|
||||||
|
"""
|
||||||
|
Corrects:
|
||||||
|
https://github.com/mathandy/svgpathtools/issues/95
|
||||||
|
"""
|
||||||
|
p = Path('M261 166 L261 166')
|
||||||
|
self.assertEqual(p.length(), 0)
|
||||||
|
|
||||||
|
def test_issue_94(self):
|
||||||
|
# clipping rectangle
|
||||||
|
p1 = Path('M0.0 0.0 L27.84765625 0.0 L27.84765625 242.6669922 L0.0 242.6669922 z')
|
||||||
|
# clipping rectangle
|
||||||
|
p2 = Path('M166.8359375,235.5478516c0,3.7773438-3.0859375,6.8691406-6.8701172,6.8691406H7.1108398c-3.7749023,0-6.8608398-3.0917969-6.8608398-6.8691406V7.1201172C0.25,3.3427734,3.3359375,0.25,7.1108398,0.25h152.8549805c3.7841797,0,6.8701172,3.0927734,6.8701172,6.8701172v228.4277344z')
|
||||||
|
self.assertEqual(len(p1.intersect(p2)), len(p2.intersect(p1)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in New Issue