partially fixed scale fcn and improved tests

pull/60/head
Andy 2018-06-08 22:13:18 -07:00
parent f932036fb5
commit fd95b5609f
2 changed files with 97 additions and 35 deletions

View File

@ -224,25 +224,25 @@ def scale(curve, sx, sy=None, origin=0j):
else: else:
isy = 1j*sy isy = 1j*sy
def transform(z, sx=sx, sy=sy, origin=origin): def transform(z, origin=origin):
zeta = z - origin zeta = z - origin
return x*zeta.real + isy*zeta.imag + origin return sx*zeta.real + isy*zeta.imag + origin
if isinstance(curve, Path): if isinstance(curve, Path):
return Path(*[scale(seg, sx, sy, origin) for seg in curve]) return Path(*[scale(seg, sx, sy, origin) for seg in curve])
elif is_bezier_segment(curve): elif is_bezier_segment(curve):
return bpoints2bezier([transform(z) for z in curve.bpoints()]) return bpoints2bezier([transform(z) for z in curve.bpoints()])
elif isinstance(curve, Arc): elif isinstance(curve, Arc):
if y is None or y == x: if sy is None or sy == sx:
return Arc(start=transform(curve.start), return Arc(start=transform(curve.start),
radius=transform(radius, origin=0), radius=transform(curve.radius, origin=0),
rotation=curve.rotation, rotation=curve.rotation,
large_arc=curve.large_arc, large_arc=curve.large_arc,
sweep=curve.sweep, sweep=curve.sweep,
end=transform(curve.end)) end=transform(curve.end))
else: else:
raise Excpetion("For `Arc` objects, only scale transforms " raise Exception("For `Arc` objects, only scale transforms "
"with sx==sy are implemenented.") "with sx==sy are implemented.")
else: else:
raise TypeError("Input `curve` should be a Path, Line, " raise TypeError("Input `curve` should be a Path, Line, "
"QuadraticBezier, CubicBezier, or Arc object.") "QuadraticBezier, CubicBezier, or Arc object.")
@ -2042,9 +2042,9 @@ class Path(MutableSequence):
0) == previous.unit_tangent(1) 0) == previous.unit_tangent(1)
def T2t(self, T): def T2t(self, T):
"""returns the segment index, seg_idx, and segment parameter, t, """returns the segment index, `seg_idx`, and segment parameter, `t`,
corresponding to the path parameter T. In other words, this is the corresponding to the path parameter `T`. In other words, this is the
inverse of the Path.t2T() method.""" inverse of the `Path.t2T()` method."""
if T == 1: if T == 1:
return len(self)-1, 1 return len(self)-1, 1
if T == 0: if T == 0:

View File

@ -3,7 +3,7 @@ from __future__ import division, absolute_import, print_function
import unittest import unittest
from math import sqrt, pi from math import sqrt, pi
from operator import itemgetter from operator import itemgetter
from numpy import poly1d, linspace import numpy as np
# Internal dependencies # Internal dependencies
from svgpathtools import * from svgpathtools import *
@ -727,6 +727,7 @@ class TestPath(unittest.TestCase):
p_open.cropped(1, 0) p_open.cropped(1, 0)
def test_transform_scale(self): def test_transform_scale(self):
line1 = Line(600.5 + 350.5j, 650.5 + 325.5j) line1 = Line(600.5 + 350.5j, 650.5 + 325.5j)
arc1 = Arc(650 + 325j, 25 + 25j, -30, 0, 1, 700 + 300j) arc1 = Arc(650 + 325j, 25 + 25j, -30, 0, 1, 700 + 300j)
arc2 = Arc(650 + 325j, 30 + 25j, -30, 0, 0, 700 + 300j) arc2 = Arc(650 + 325j, 30 + 25j, -30, 0, 0, 700 + 300j)
@ -744,44 +745,105 @@ class TestPath(unittest.TestCase):
cpath = Path(cub1) cpath = Path(cub1)
apath = Path(arc1, arc2) apath = Path(arc1, arc2)
test_curves = ([bezpath, bezpathz, path, pathz, lpath, qpath, cpath, apath] + test_curves = [bezpath, bezpathz, path, pathz, lpath, qpath, cpath,
[line1, arc1, arc2, cub1, cub2, quad3, linez]) apath, line1, arc1, arc2, cub1, cub2, quad3, linez]
for path_orig in test_curves: def scale_a_point(pt, sx, sy=None, origin=0j):
if sy is None:
sy = sx
zeta = pt - origin
pt_vec = [[zeta.real],
[zeta.imag],
[1]]
transform = [[sx, 0, origin.real],
[0, sy, origin.imag]]
return complex(*np.dot(transform, pt_vec).ravel())
for curve in test_curves:
# generate a random point and a random scaling
t = np.random.rand()
pt = curve.point(t)
# random diagonal transformation
sx = 2 * np.random.rand()
sy = 2 * np.random.rand()
# random origin
origin = (10 * (np.random.rand() - 0.5) +
10j * (np.random.rand() - 0.5))
# Note: `sx != sy` cases are not implemented for `Arc` objects
has_arc = (isinstance(curve, Arc) or
isinstance(curve, Path) and
any(isinstance(seg, Arc) for seg in curve))
# case where no `sy` and no `origin` given
ans = scale_a_point(pt, sx, None)
self.assertAlmostEqual(ans, curve.scaled(sx).point(t))
# case where no `sy` and random `origin` given
ans = scale_a_point(pt, sx, None, origin)
self.assertAlmostEqual(ans,
curve.scaled(sx, origin=origin).point(t))
# the cases with sx != sy are not yet imp
if isinstance(curve, Arc):
continue
# case where `sx != sy`, and no `origin` given
ans = scale_a_point(pt, sx, sy)
if has_arc:
self.assertRaises(Exception, curve.scaled(sx, sy).point(t))
else:
self.assertAlmostEqual(ans, curve.scaled(sx, sy).point(t))
# case where `sx != sy`, and random `origin` given
ans = scale_a_point(pt, sx, sy, origin)
if has_arc:
self.assertRaises(Exception,
curve.scaled(sx, sy, origin).point(t))
else:
self.assertAlmostEqual(ans,
curve.scaled(sx, sy, origin).point(t))
# more tests for scalar (i.e. `sx == sy`) case
for curve in test_curves:
# scale by 2 around (100, 100) # scale by 2 around (100, 100)
path_trns = path_orig.scaled(2.0, complex(100, 100)) scaled_curve = curve.scaled(2.0, complex(100, 100))
# expected length # expected length
len_orig = path_orig.length() len_orig = curve.length()
len_trns = path_trns.length() len_trns = scaled_curve.length()
self.assertAlmostEqual(len_orig * 2.0, len_trns) self.assertAlmostEqual(len_orig * 2.0, len_trns)
# expected positions # expected positions
for T in linspace(0.0, 1.0, num=100): for T in np.linspace(0.0, 1.0, num=100):
pt_orig = path_orig.point(T) pt_orig = curve.point(T)
pt_trns = path_trns.point(T) pt_trns = scaled_curve.point(T)
pt_xpct = (pt_orig - complex(100, 100)) * 2.0 + complex(100, 100) pt_xpct = (pt_orig - complex(100, 100)) * 2.0 + complex(100, 100)
self.assertAlmostEqual(pt_xpct, pt_trns) self.assertAlmostEqual(pt_xpct, pt_trns)
for path_orig in test_curves:
# scale by 0.3 around (0, -100) # scale by 0.3 around (0, -100)
# the 'almost equal' test fails at the 7th decimal place for # the 'almost equal' test fails at the 7th decimal place for
# some length and position tests here. # some length and position tests here.
path_trns = path_orig.scaled(0.3, complex(0, -100)) scaled_curve = curve.scaled(0.3, complex(0, -100))
# expected length # expected length
len_orig = path_orig.length() len_orig = curve.length()
len_trns = path_trns.length() len_trns = scaled_curve.length()
self.assertAlmostEqual(len_orig * 0.3, len_trns, delta = 0.000001) self.assertAlmostEqual(len_orig * 0.3, len_trns, delta=0.000001)
# expected positions # expected positions
for T in linspace(0.0, 1.0, num=100): for T in np.linspace(0.0, 1.0, num=100):
pt_orig = path_orig.point(T) pt_orig = curve.point(T)
pt_trns = path_trns.point(T) pt_trns = scaled_curve.point(T)
pt_xpct = (pt_orig - complex(0, -100)) * 0.3 + complex(0, -100) pt_xpct = (pt_orig - complex(0, -100)) * 0.3 + complex(0, -100)
self.assertAlmostEqual(pt_xpct, pt_trns, delta = 0.000001) self.assertAlmostEqual(pt_xpct, pt_trns, delta=0.000001)
class Test_ilength(unittest.TestCase): class Test_ilength(unittest.TestCase):
@ -1136,10 +1198,10 @@ class TestPathTools(unittest.TestCase):
# Case: Line # Case: Line
pcoeffs = [(-1.7-2j), (6+2j)] pcoeffs = [(-1.7-2j), (6+2j)]
p = poly1d(pcoeffs) p = np.poly1d(pcoeffs)
correct_bpoints = [(6+2j), (4.3+0j)] correct_bpoints = [(6+2j), (4.3+0j)]
# Input poly1d object # Input np.poly1d object
bez = poly2bez(p) bez = poly2bez(p)
bpoints = bez.bpoints() bpoints = bez.bpoints()
self.assertAlmostEqual(distfcn(bpoints, correct_bpoints), 0) self.assertAlmostEqual(distfcn(bpoints, correct_bpoints), 0)
@ -1150,10 +1212,10 @@ class TestPathTools(unittest.TestCase):
# Case: Quadratic # Case: Quadratic
pcoeffs = [(29.5+15.5j), (-31-19j), (7.5+5.5j)] pcoeffs = [(29.5+15.5j), (-31-19j), (7.5+5.5j)]
p = poly1d(pcoeffs) p = np.poly1d(pcoeffs)
correct_bpoints = [(7.5+5.5j), (-8-4j), (6+2j)] correct_bpoints = [(7.5+5.5j), (-8-4j), (6+2j)]
# Input poly1d object # Input np.poly1d object
bez = poly2bez(p) bez = poly2bez(p)
bpoints = bez.bpoints() bpoints = bez.bpoints()
self.assertAlmostEqual(distfcn(bpoints, correct_bpoints), 0) self.assertAlmostEqual(distfcn(bpoints, correct_bpoints), 0)
@ -1164,10 +1226,10 @@ class TestPathTools(unittest.TestCase):
# Case: Cubic # Case: Cubic
pcoeffs = [(-18.5-12.5j), (34.5+16.5j), (-18-6j), (6+2j)] pcoeffs = [(-18.5-12.5j), (34.5+16.5j), (-18-6j), (6+2j)]
p = poly1d(pcoeffs) p = np.poly1d(pcoeffs)
correct_bpoints = [(6+2j), 0j, (5.5+3.5j), (4+0j)] correct_bpoints = [(6+2j), 0j, (5.5+3.5j), (4+0j)]
# Input poly1d object # Input np.poly1d object
bez = poly2bez(p) bez = poly2bez(p)
bpoints = bez.bpoints() bpoints = bez.bpoints()
self.assertAlmostEqual(distfcn(bpoints, correct_bpoints), 0) self.assertAlmostEqual(distfcn(bpoints, correct_bpoints), 0)