parent
fb916596e2
commit
8d5023939e
|
@ -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<T0 must cross discontinuity case
|
||||
if T1 < T0:
|
||||
if self.isclosed():
|
||||
if not self.isclosed():
|
||||
raise ValueError("This path is not closed, thus T0 must "
|
||||
"be less than T1.")
|
||||
else:
|
||||
|
@ -2189,7 +2277,6 @@ class Path(MutableSequence):
|
|||
new_path.append(seg1.cropped(0, t_seg1))
|
||||
return new_path
|
||||
|
||||
|
||||
def radialrange(self, origin, return_all_global_extrema=False):
|
||||
"""returns the tuples (d_min, t_min, idx_min), (d_max, t_max, idx_max)
|
||||
which minimize and maximize, respectively, the distance
|
||||
|
@ -2218,3 +2305,7 @@ class Path(MutableSequence):
|
|||
"""Returns a copy of self shifted by the complex quantity `z0` such
|
||||
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)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -3,7 +3,7 @@ import codecs
|
|||
import os
|
||||
|
||||
|
||||
VERSION = '1.3.2'
|
||||
VERSION = '1.3.3'
|
||||
AUTHOR_NAME = 'Andy Port'
|
||||
AUTHOR_EMAIL = 'AndyAPort@gmail.com'
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: svgpathtools
|
||||
Version: 1.3.2
|
||||
Version: 1.3.3
|
||||
Summary: A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
|
||||
Home-page: https://github.com/mathandy/svgpathtools
|
||||
Author: Andy Port
|
||||
Author-email: AndyAPort@gmail.com
|
||||
License: MIT
|
||||
Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.2
|
||||
Description-Content-Type: UNKNOWN
|
||||
Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.3
|
||||
Description:
|
||||
svgpathtools
|
||||
============
|
||||
|
@ -595,9 +594,8 @@ Description:
|
|||
of the 'parallel' offset curve."""
|
||||
nls = []
|
||||
for seg in path:
|
||||
ct = 1
|
||||
for k in range(steps):
|
||||
t = k / steps
|
||||
t = k / float(steps)
|
||||
offset_vector = offset_distance * seg.normal(t)
|
||||
nl = Line(seg.point(t), seg.point(t) + offset_vector)
|
||||
nls.append(nl)
|
||||
|
|
|
@ -3,7 +3,6 @@ LICENSE2.txt
|
|||
MANIFEST.in
|
||||
README.rst
|
||||
decorated_ellipse.svg
|
||||
disvg_output.svg
|
||||
offset_curves.svg
|
||||
output1.svg
|
||||
output2.svg
|
||||
|
@ -21,7 +20,7 @@ svgpathtools/path.py
|
|||
svgpathtools/paths2svg.py
|
||||
svgpathtools/polytools.py
|
||||
svgpathtools/smoothing.py
|
||||
svgpathtools/svg2paths.py
|
||||
svgpathtools/svg_to_paths.py
|
||||
svgpathtools.egg-info/PKG-INFO
|
||||
svgpathtools.egg-info/SOURCES.txt
|
||||
svgpathtools.egg-info/dependency_links.txt
|
||||
|
|
Loading…
Reference in New Issue