diff --git a/build/lib/svgpathtools/path.py b/build/lib/svgpathtools/path.py index ec23b71..49fd4b7 100644 --- a/build/lib/svgpathtools/path.py +++ b/build/lib/svgpathtools/path.py @@ -18,7 +18,8 @@ except: # Internal dependencies from .bezier import (bezier_intersections, bezier_bounding_box, split_bezier, - bezier_by_line_intersections, polynomial2bezier) + bezier_by_line_intersections, polynomial2bezier, + bezier2polynomial) from .misctools import BugException from .polytools import rational_limit, polyroots, polyroots01, imag, real @@ -97,6 +98,21 @@ def bbox2path(xmin, xmax, ymin, ymax): return Path(b, r, t.reversed(), l.reversed()) +def polyline(*points): + """Converts a list of points to a Path composed of lines connecting those + points (i.e. a linear spline or polyline). See also `polygon()`.""" + return Path(*[Line(points[i], points[i+1]) + for i in range(len(points) - 1)]) + + +def polygon(*points): + """Converts a list of points to a Path composed of lines connecting those + points, then closes the path by connecting the last point to the first. + See also `polyline()`.""" + return Path(*[Line(points[i], points[(i + 1) % len(points)]) + for i in range(len(points))]) + + # Conversion################################################################### def bpoints2bezier(bpoints): @@ -154,7 +170,7 @@ def rotate(curve, degs, origin=None): def transform(z): return exp(1j*radians(degs))*(z - origin) + origin - if origin == None: + if origin is None: if isinstance(curve, Arc): origin = curve.center else: @@ -192,6 +208,53 @@ def translate(curve, z0): "QuadraticBezier, CubicBezier, or Arc object.") +def scale(curve, sx, sy=None, origin=0j): + """Scales `curve`, about `origin`, by diagonal matrix `[[sx,0],[0,sy]]`. + + Notes: + ------ + * If `sy` is not specified, it is assumed to be equal to `sx` and + a scalar transformation of `curve` about `origin` will be returned. + I.e. + scale(curve, sx, origin).point(t) == + ((curve.point(t) - origin) * sx) + origin + """ + + if sy is None: + isy = 1j*sx + else: + isy = 1j*sy + + def _scale(z): + if sy is None: + return sx*z + return sx*z.real + isy*z.imag + + def scale_bezier(bez): + p = [_scale(c) for c in bez2poly(bez)] + p[-1] += origin - _scale(origin) + return poly2bez(p) + + if isinstance(curve, Path): + return Path(*[scale(seg, sx, sy, origin) for seg in curve]) + elif is_bezier_segment(curve): + return scale_bezier(curve) + elif isinstance(curve, Arc): + if sy is None or sy == sx: + return Arc(start=sx*(curve.start - origin) + origin, + radius=sx*curve.radius, + rotation=curve.rotation, + large_arc=curve.large_arc, + sweep=curve.sweep, + end=sx*(curve.end - origin) + origin) + else: + raise Exception("\nFor `Arc` objects, only scale transforms " + "with sx==sy are implemented.\n") + else: + raise TypeError("Input `curve` should be a Path, Line, " + "QuadraticBezier, CubicBezier, or Arc object.") + + def bezier_unit_tangent(seg, t): """Returns the unit tangent of the segment at t. @@ -557,7 +620,7 @@ class Line(object): d = (other_seg.start.imag, other_seg.end.imag) denom = ((a[1] - a[0])*(d[0] - d[1]) - (b[1] - b[0])*(c[0] - c[1])) - if denom == 0: + if np.isclose(denom, 0): return [] t1 = (c[0]*(b[0] - d[1]) - c[1]*(b[0] - d[0]) - @@ -622,6 +685,10 @@ class Line(object): that self.translated(z0).point(t) = self.point(t) + z0 for any t.""" return translate(self, z0) + def scaled(self, sx, sy=None, origin=0j): + """Scale transform. See `scale` function for further explanation.""" + return scale(self, sx=sx, sy=sy, origin=origin) + class QuadraticBezier(object): # For compatibility with old pickle files. @@ -866,6 +933,10 @@ class QuadraticBezier(object): that self.translated(z0).point(t) = self.point(t) + z0 for any t.""" return translate(self, z0) + def scaled(self, sx, sy=None, origin=0j): + """Scale transform. See `scale` function for further explanation.""" + return scale(self, sx=sx, sy=sy, origin=origin) + class CubicBezier(object): # For compatibility with old pickle files. @@ -1106,6 +1177,10 @@ class CubicBezier(object): that self.translated(z0).point(t) = self.point(t) + z0 for any t.""" return translate(self, z0) + def scaled(self, sx, sy=None, origin=0j): + """Scale transform. See `scale` function for further explanation.""" + return scale(self, sx=sx, sy=sy, origin=origin) + class Arc(object): def __init__(self, start, radius, rotation, large_arc, sweep, end, @@ -1285,10 +1360,13 @@ class Arc(object): # delta is the angular distance of the arc (w.r.t the circle) # theta is the angle between the positive x'-axis and the start point # on the circle + u1_real_rounded = u1.real + if u1.real > 1 or u1.real < -1: + u1_real_rounded = round(u1.real) if u1.imag > 0: - self.theta = degrees(acos(u1.real)) + self.theta = degrees(acos(u1_real_rounded)) elif u1.imag < 0: - self.theta = -degrees(acos(u1.real)) + self.theta = -degrees(acos(u1_real_rounded)) else: if u1.real > 0: # start is on pos u_x axis self.theta = 0 @@ -1668,6 +1746,10 @@ class Arc(object): that self.translated(z0).point(t) = self.point(t) + z0 for any t.""" return translate(self, z0) + def scaled(self, sx, sy=None, origin=0j): + """Scale transform. See `scale` function for further explanation.""" + return scale(self, sx=sx, sy=sy, origin=origin) + def is_bezier_segment(x): return (isinstance(x, Line) or @@ -1967,9 +2049,9 @@ class Path(MutableSequence): 0) == previous.unit_tangent(1) def T2t(self, T): - """returns the segment index, seg_idx, and segment parameter, t, - corresponding to the path parameter T. In other words, this is the - inverse of the Path.t2T() method.""" + """returns the segment index, `seg_idx`, and segment parameter, `t`, + corresponding to the path parameter `T`. In other words, this is the + inverse of the `Path.t2T()` method.""" if T == 1: return len(self)-1, 1 if T == 0: @@ -2042,15 +2124,15 @@ class Path(MutableSequence): if np.isclose(t, 0) and (seg_idx != 0 or self.end==self.start): previous_seg_in_path = self._segments[ (seg_idx - 1) % len(self._segments)] - if not seg.joins_smoothl_with(previous_seg_in_path): + if not seg.joins_smoothly_with(previous_seg_in_path): return float('inf') elif np.isclose(t, 1) and (seg_idx != len(self) - 1 or self.end==self.start): next_seg_in_path = self._segments[ (seg_idx + 1) % len(self._segments)] if not next_seg_in_path.joins_smoothly_with(seg): return float('inf') - dz = self.derivative(t) - ddz = self.derivative(t, n=2) + dz = self.derivative(T) + ddz = self.derivative(T, n=2) dx, dy = dz.real, dz.imag ddx, ddy = ddz.real, ddz.imag return abs(dx*ddy - dy*ddx)/(dx*dx + dy*dy)**1.5 @@ -2136,7 +2218,13 @@ class Path(MutableSequence): def cropped(self, T0, T1): """returns a cropped copy of the path.""" + assert 0 <= T0 <= 1 and 0 <= T1<= 1 assert T0 != T1 + assert not (T0 == 1 and T1 == 0) + + if T0 == 1 and 0 < T1 < 1 and self.isclosed(): + return self.cropped(0, T1) + if T1 == 1: seg1 = self[-1] t_seg1 = 1 @@ -2171,7 +2259,7 @@ class Path(MutableSequence): # T1