updated from master
commit
657a9d6745
|
@ -191,7 +191,7 @@ def transform_segments_together(path, transformation):
|
||||||
transformed_segs = [transformation(seg) for seg in path]
|
transformed_segs = [transformation(seg) for seg in path]
|
||||||
joint_was_continuous = [sa.end == sb.start for sa, sb in path.joints()]
|
joint_was_continuous = [sa.end == sb.start for sa, sb in path.joints()]
|
||||||
|
|
||||||
for i, (sa, sb)in enumerate(path.joints()):
|
for i, (sa, sb) in enumerate(path.joints()):
|
||||||
if sa.end == sb.start:
|
if sa.end == sb.start:
|
||||||
transformed_segs[i].end = transformed_segs[(i + 1) % len(path)].start
|
transformed_segs[i].end = transformed_segs[(i + 1) % len(path)].start
|
||||||
return Path(*transformed_segs)
|
return Path(*transformed_segs)
|
||||||
|
@ -292,6 +292,7 @@ def scale(curve, sx, sy=None, origin=0j):
|
||||||
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.")
|
||||||
|
|
||||||
|
|
||||||
def transform(curve, tf):
|
def transform(curve, tf):
|
||||||
"""Transforms the curve by the homogeneous transformation matrix tf"""
|
"""Transforms the curve by the homogeneous transformation matrix tf"""
|
||||||
def to_point(p):
|
def to_point(p):
|
||||||
|
@ -567,8 +568,7 @@ def inv_arclength(curve, s, s_tol=ILENGTH_S_TOL, maxits=ILENGTH_MAXITS,
|
||||||
|
|
||||||
|
|
||||||
def crop_bezier(seg, t0, t1):
|
def crop_bezier(seg, t0, t1):
|
||||||
"""returns a cropped copy of this segment which starts at self.point(t0)
|
"""Crop a copy of this `self` from `self.point(t0)` to `self.point(t1)`."""
|
||||||
and ends at self.point(t1)."""
|
|
||||||
assert t0 < t1
|
assert t0 < t1
|
||||||
if t0 == 0:
|
if t0 == 0:
|
||||||
cropped_seg = seg.split(t1)[0]
|
cropped_seg = seg.split(t1)[0]
|
||||||
|
@ -595,6 +595,9 @@ class Line(object):
|
||||||
self.start = start
|
self.start = start
|
||||||
self.end = end
|
self.end = end
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.start, self.end))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Line(start=%s, end=%s)' % (self.start, self.end)
|
return 'Line(start=%s, end=%s)' % (self.start, self.end)
|
||||||
|
|
||||||
|
@ -851,6 +854,9 @@ class QuadraticBezier(object):
|
||||||
# used to know if self._length needs to be updated
|
# used to know if self._length needs to be updated
|
||||||
self._length_info = {'length': None, 'bpoints': None}
|
self._length_info = {'length': None, 'bpoints': None}
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.start, self.control, self.end))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'QuadraticBezier(start=%s, control=%s, end=%s)' % (
|
return 'QuadraticBezier(start=%s, control=%s, end=%s)' % (
|
||||||
self.start, self.control, self.end)
|
self.start, self.control, self.end)
|
||||||
|
@ -1110,6 +1116,9 @@ class CubicBezier(object):
|
||||||
self._length_info = {'length': None, 'bpoints': None, 'error': None,
|
self._length_info = {'length': None, 'bpoints': None, 'error': None,
|
||||||
'min_depth': None}
|
'min_depth': None}
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.start, self.control1, self.control2, self.end))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'CubicBezier(start=%s, control1=%s, control2=%s, end=%s)' % (
|
return 'CubicBezier(start=%s, control1=%s, control2=%s, end=%s)' % (
|
||||||
self.start, self.control1, self.control2, self.end)
|
self.start, self.control1, self.control2, self.end)
|
||||||
|
@ -1281,37 +1290,38 @@ class CubicBezier(object):
|
||||||
|
|
||||||
def intersect(self, other_seg, tol=1e-12):
|
def intersect(self, other_seg, tol=1e-12):
|
||||||
"""Finds the intersections of two segments.
|
"""Finds the intersections of two segments.
|
||||||
returns a list of tuples (t1, t2) such that
|
|
||||||
self.point(t1) == other_seg.point(t2).
|
Returns:
|
||||||
Note: This will fail if the two segments coincide for more than a
|
(list[tuple[float]]) a list of tuples (t1, t2) such that
|
||||||
finite collection of points."""
|
self.point(t1) == other_seg.point(t2).
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
This will fail if the two segments coincide for more than a
|
||||||
|
finite collection of points.
|
||||||
|
"""
|
||||||
if isinstance(other_seg, Line):
|
if isinstance(other_seg, Line):
|
||||||
return bezier_by_line_intersections(self, other_seg)
|
return bezier_by_line_intersections(self, other_seg)
|
||||||
elif (isinstance(other_seg, QuadraticBezier) or
|
elif (isinstance(other_seg, QuadraticBezier) or
|
||||||
isinstance(other_seg, CubicBezier)):
|
isinstance(other_seg, CubicBezier)):
|
||||||
assert self != other_seg
|
assert self != other_seg
|
||||||
longer_length = max(self.length(), other_seg.length())
|
longer_length = max(self.length(), other_seg.length())
|
||||||
return bezier_intersections(self, other_seg,
|
return bezier_intersections(
|
||||||
longer_length=longer_length,
|
self, other_seg, longer_length=longer_length, tol=tol, tol_deC=tol
|
||||||
tol=tol, tol_deC=tol)
|
)
|
||||||
elif isinstance(other_seg, Arc):
|
elif isinstance(other_seg, Arc):
|
||||||
t2t1s = other_seg.intersect(self)
|
return [(t1, t2) for t2, t1 in other_seg.intersect(self)]
|
||||||
return [(t1, t2) for t2, t1 in t2t1s]
|
|
||||||
elif isinstance(other_seg, Path):
|
elif isinstance(other_seg, Path):
|
||||||
raise TypeError(
|
raise TypeError("`other_seg` must be a path segment, not a "
|
||||||
"other_seg must be a path segment, not a Path object, use "
|
"`Path` object, use `Path.intersect()`.")
|
||||||
"Path.intersect().")
|
|
||||||
else:
|
else:
|
||||||
raise TypeError("other_seg must be a path segment.")
|
raise TypeError("`other_seg` must be a path segment.")
|
||||||
|
|
||||||
def bbox(self):
|
def bbox(self):
|
||||||
"""returns the bounding box for the segment in the form
|
"""returns bounding box in format (xmin, xmax, ymin, ymax)."""
|
||||||
(xmin, xmax, ymin, ymax)."""
|
|
||||||
return bezier_bounding_box(self)
|
return bezier_bounding_box(self)
|
||||||
|
|
||||||
def split(self, t):
|
def split(self, t):
|
||||||
"""returns two segments, whose union is this segment and which join at
|
"""Splits a copy of `self` at t and returns the two subsegments."""
|
||||||
self.point(t)."""
|
|
||||||
bpoints1, bpoints2 = split_bezier(self.bpoints(), t)
|
bpoints1, bpoints2 = split_bezier(self.bpoints(), t)
|
||||||
return CubicBezier(*bpoints1), CubicBezier(*bpoints2)
|
return CubicBezier(*bpoints1), CubicBezier(*bpoints2)
|
||||||
|
|
||||||
|
@ -1323,8 +1333,8 @@ class CubicBezier(object):
|
||||||
def radialrange(self, origin, return_all_global_extrema=False):
|
def radialrange(self, origin, return_all_global_extrema=False):
|
||||||
"""returns the tuples (d_min, t_min) and (d_max, t_max) which minimize
|
"""returns the tuples (d_min, t_min) and (d_max, t_max) which minimize
|
||||||
and maximize, respectively, the distance d = |self.point(t)-origin|."""
|
and maximize, respectively, the distance d = |self.point(t)-origin|."""
|
||||||
return bezier_radialrange(self, origin,
|
return bezier_radialrange(
|
||||||
return_all_global_extrema=return_all_global_extrema)
|
self, origin, return_all_global_extrema=return_all_global_extrema)
|
||||||
|
|
||||||
def rotated(self, degs, origin=None):
|
def rotated(self, degs, origin=None):
|
||||||
"""Returns a copy of self rotated by `degs` degrees (CCW) around the
|
"""Returns a copy of self rotated by `degs` degrees (CCW) around the
|
||||||
|
@ -1684,7 +1694,7 @@ class Arc(object):
|
||||||
if np.isclose(t_x_0, t_y_0):
|
if np.isclose(t_x_0, t_y_0):
|
||||||
t = (t_x_0 + t_y_0) / 2.0
|
t = (t_x_0 + t_y_0) / 2.0
|
||||||
elif np.isclose(t_x_0, t_y_1):
|
elif np.isclose(t_x_0, t_y_1):
|
||||||
t= (t_x_0 + t_y_1) / 2.0
|
t = (t_x_0 + t_y_1) / 2.0
|
||||||
elif np.isclose(t_x_1, t_y_0):
|
elif np.isclose(t_x_1, t_y_0):
|
||||||
t = (t_x_1 + t_y_0) / 2.0
|
t = (t_x_1 + t_y_0) / 2.0
|
||||||
elif np.isclose(t_x_1, t_y_1):
|
elif np.isclose(t_x_1, t_y_1):
|
||||||
|
@ -1702,33 +1712,48 @@ class Arc(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def centeriso(self, z):
|
def centeriso(self, z):
|
||||||
"""This is an isometry that translates and rotates self so that it
|
"""Isometry to a centered aligned ellipse.
|
||||||
is centered on the origin and has its axes aligned with the xy axes."""
|
|
||||||
|
This is an isometry that shifts and rotates `self`'s underlying
|
||||||
|
ellipse so that it's centered on the origin and has its axes
|
||||||
|
aligned with the xy-axes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
z (:obj:`complex` or :obj:`numpy.ndarray[complex]`): a point
|
||||||
|
to send through the above-described isometry.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(:obj:`complex` or :obj:`numpy.ndarray[complex]`) The point(s) f(z),
|
||||||
|
where f is the above described isometry of the xy-plane (i.e.
|
||||||
|
the one-dimensional complex plane).
|
||||||
|
"""
|
||||||
return (1/self.rot_matrix)*(z - self.center)
|
return (1/self.rot_matrix)*(z - self.center)
|
||||||
|
|
||||||
def icenteriso(self, zeta):
|
def icenteriso(self, zeta):
|
||||||
"""This is an isometry, the inverse of standardiso()."""
|
"""The inverse of the `centeriso()` method."""
|
||||||
return self.rot_matrix*zeta + self.center
|
return self.rot_matrix*zeta + self.center
|
||||||
|
|
||||||
def u1transform(self, z):
|
def u1transform(self, z):
|
||||||
"""This is an affine transformation (same as used in
|
"""Similar to the `centeriso()` method, but maps to the unit circle."""
|
||||||
self._parameterize()) that sends self to the unit circle."""
|
zeta = self.centeriso(z)
|
||||||
zeta = (1/self.rot_matrix)*(z - self.center) # same as centeriso(z)
|
|
||||||
x, y = real(zeta), imag(zeta)
|
x, y = real(zeta), imag(zeta)
|
||||||
return x/self.radius.real + 1j*y/self.radius.imag
|
return x/self.radius.real + 1j*y/self.radius.imag
|
||||||
|
|
||||||
def iu1transform(self, zeta):
|
def iu1transform(self, zeta):
|
||||||
"""This is an affine transformation, the inverse of
|
"""The inverse of the `u1transform()` method."""
|
||||||
self.u1transform()."""
|
|
||||||
x = real(zeta)
|
x = real(zeta)
|
||||||
y = imag(zeta)
|
y = imag(zeta)
|
||||||
z = x*self.radius.real + y*self.radius.imag
|
z = x*self.radius.real + y*self.radius.imag
|
||||||
return self.rot_matrix*z + self.center
|
return self.rot_matrix*z + self.center
|
||||||
|
|
||||||
def length(self, t0=0, t1=1, error=LENGTH_ERROR, min_depth=LENGTH_MIN_DEPTH):
|
def length(self, t0=0, t1=1, error=LENGTH_ERROR, min_depth=LENGTH_MIN_DEPTH):
|
||||||
"""The length of an elliptical large_arc segment requires numerical
|
"""Computes the length of the Arc segment, `self`, from t0 to t1.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
* The length of an elliptical large_arc segment requires numerical
|
||||||
integration, and in that case it's simpler to just do a geometric
|
integration, and in that case it's simpler to just do a geometric
|
||||||
approximation, as for cubic bezier curves."""
|
approximation, as for cubic bezier curves.
|
||||||
|
"""
|
||||||
assert 0 <= t0 <= 1 and 0 <= t1 <= 1
|
assert 0 <= t0 <= 1 and 0 <= t1 <= 1
|
||||||
|
|
||||||
if t0 == 0 and t1 == 1:
|
if t0 == 0 and t1 == 1:
|
||||||
|
@ -1752,8 +1777,17 @@ class Arc(object):
|
||||||
|
|
||||||
def ilength(self, s, s_tol=ILENGTH_S_TOL, maxits=ILENGTH_MAXITS,
|
def ilength(self, s, s_tol=ILENGTH_S_TOL, maxits=ILENGTH_MAXITS,
|
||||||
error=ILENGTH_ERROR, min_depth=ILENGTH_MIN_DEPTH):
|
error=ILENGTH_ERROR, min_depth=ILENGTH_MIN_DEPTH):
|
||||||
"""Returns a float, t, such that self.length(0, t) is approximately s.
|
"""Approximates the unique `t` such that self.length(0, t) = s.
|
||||||
See the inv_arclength() docstring for more details."""
|
|
||||||
|
Args:
|
||||||
|
s (float): A length between 0 and `self.length()`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(float) The t, such that self.length(0, t) is approximately s.
|
||||||
|
|
||||||
|
For more info:
|
||||||
|
See the inv_arclength() docstring.
|
||||||
|
"""
|
||||||
return inv_arclength(self, s, s_tol=s_tol, maxits=maxits, error=error,
|
return inv_arclength(self, s, s_tol=s_tol, maxits=maxits, error=error,
|
||||||
min_depth=min_depth)
|
min_depth=min_depth)
|
||||||
|
|
||||||
|
@ -1851,9 +1885,18 @@ class Arc(object):
|
||||||
not self.sweep, self.start)
|
not self.sweep, self.start)
|
||||||
|
|
||||||
def phase2t(self, psi):
|
def phase2t(self, psi):
|
||||||
"""Given phase -pi < psi <= pi,
|
"""Converts phase to t-value.
|
||||||
returns the t value such that
|
|
||||||
exp(1j*psi) = self.u1transform(self.point(t)).
|
I.e. given phase, psi, such that -np.pi < psi <= np.pi, approximates
|
||||||
|
the unique t-value such that `self.u1transform(self.point(t))` equals
|
||||||
|
`np.exp(1j*psi)`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
psi (float): The phase in radians.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(float): the corresponding t-value.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def _deg(rads, domain_lower_limit):
|
def _deg(rads, domain_lower_limit):
|
||||||
# Convert rads to degrees in [0, 360) domain
|
# Convert rads to degrees in [0, 360) domain
|
||||||
|
@ -1872,7 +1915,6 @@ class Arc(object):
|
||||||
degs = _deg(psi, domain_lower_limit=self.theta)
|
degs = _deg(psi, domain_lower_limit=self.theta)
|
||||||
return (degs - self.theta)/self.delta
|
return (degs - self.theta)/self.delta
|
||||||
|
|
||||||
|
|
||||||
def intersect(self, other_seg, tol=1e-12):
|
def intersect(self, other_seg, tol=1e-12):
|
||||||
"""NOT FULLY IMPLEMENTED. Finds the intersections of two segments.
|
"""NOT FULLY IMPLEMENTED. Finds the intersections of two segments.
|
||||||
returns a list of tuples (t1, t2) such that
|
returns a list of tuples (t1, t2) such that
|
||||||
|
@ -2002,11 +2044,19 @@ class Arc(object):
|
||||||
return intersections
|
return intersections
|
||||||
|
|
||||||
elif is_bezier_segment(other_seg):
|
elif is_bezier_segment(other_seg):
|
||||||
u1poly = self.u1transform(other_seg.poly())
|
# if self and other_seg intersect, they will itersect at the
|
||||||
|
# same points after being passed through the `u1transform`
|
||||||
|
# isometry. Since this isometry maps self to the unit circle,
|
||||||
|
# the intersections will be easy to find (just look for any
|
||||||
|
# points where other_seg is a distance of one from the origin.
|
||||||
|
# Moreoever, the t-values that the intersection happen at will
|
||||||
|
# be unchanged by the isometry.
|
||||||
|
u1poly = np.poly1d(self.u1transform(other_seg.poly()))
|
||||||
u1poly_mag2 = real(u1poly)**2 + imag(u1poly)**2
|
u1poly_mag2 = real(u1poly)**2 + imag(u1poly)**2
|
||||||
t2s = polyroots01(u1poly_mag2 - 1)
|
t2s = [t for t in polyroots01(u1poly_mag2 - 1) if 0 <= t <= 1]
|
||||||
t1s = [self.phase2t(phase(u1poly(t2))) for t2 in t2s]
|
t1s = [self.phase2t(phase(u1poly(t2))) for t2 in t2s]
|
||||||
return list(zip(t1s, t2s))
|
|
||||||
|
return [(t1, t2) for t1, t2 in zip(t1s, t2s) if 0 <= t1 <= 1]
|
||||||
|
|
||||||
elif isinstance(other_seg, Arc):
|
elif isinstance(other_seg, Arc):
|
||||||
assert other_seg != self
|
assert other_seg != self
|
||||||
|
@ -2043,19 +2093,23 @@ class Arc(object):
|
||||||
|
|
||||||
def point_in_seg_interior(point, seg):
|
def point_in_seg_interior(point, seg):
|
||||||
t = seg.point_to_t(point)
|
t = seg.point_to_t(point)
|
||||||
if t is None: return False
|
if (not t or
|
||||||
if np.isclose(t, 0.0, rtol=0.0, atol=1e-6): return False
|
np.isclose(t, 0.0, rtol=0.0, atol=1e-6) or
|
||||||
if np.isclose(t, 1.0, rtol=0.0, atol=1e-6): return False
|
np.isclose(t, 1.0, rtol=0.0, atol=1e-6)):
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If either end of either segment is in the interior
|
# If either end of either segment is in the interior
|
||||||
# of the other segment, then the Arcs overlap
|
# of the other segment, then the Arcs overlap
|
||||||
# in an infinite number of points, and we return
|
# in an infinite number of points, and we return
|
||||||
# "no intersections".
|
# "no intersections".
|
||||||
if point_in_seg_interior(self.start, other_seg): return []
|
if (
|
||||||
if point_in_seg_interior(self.end, other_seg): return []
|
point_in_seg_interior(self.start, other_seg) or
|
||||||
if point_in_seg_interior(other_seg.start, self): return []
|
point_in_seg_interior(self.end, other_seg) or
|
||||||
if point_in_seg_interior(other_seg.end, self): return []
|
point_in_seg_interior(other_seg.start, self) or
|
||||||
|
point_in_seg_interior(other_seg.end, self)
|
||||||
|
):
|
||||||
|
return []
|
||||||
|
|
||||||
# If they touch at their endpoint(s) and don't go
|
# If they touch at their endpoint(s) and don't go
|
||||||
# in "overlapping directions", then we accept that
|
# in "overlapping directions", then we accept that
|
||||||
|
@ -2398,6 +2452,9 @@ class Path(MutableSequence):
|
||||||
if 'tree_element' in kw:
|
if 'tree_element' in kw:
|
||||||
self._tree_element = kw['tree_element']
|
self._tree_element = kw['tree_element']
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((tuple(self._segments), self._closed))
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
return self._segments[index]
|
return self._segments[index]
|
||||||
|
|
||||||
|
@ -2848,10 +2905,10 @@ class Path(MutableSequence):
|
||||||
area_enclosed += integral(1) - integral(0)
|
area_enclosed += integral(1) - integral(0)
|
||||||
return area_enclosed
|
return area_enclosed
|
||||||
|
|
||||||
def seg2lines(seg):
|
def seg2lines(seg_):
|
||||||
"""Find piecewise-linear approximation of `seg`."""
|
"""Find piecewise-linear approximation of `seg`."""
|
||||||
num_lines = int(ceil(seg.length() / chord_length))
|
num_lines = int(ceil(seg_.length() / chord_length))
|
||||||
pts = [seg.point(t) for t in np.linspace(0, 1, num_lines+1)]
|
pts = [seg_.point(t) for t in np.linspace(0, 1, num_lines+1)]
|
||||||
return [Line(pts[i], pts[i+1]) for i in range(num_lines)]
|
return [Line(pts[i], pts[i+1]) for i in range(num_lines)]
|
||||||
|
|
||||||
assert self.isclosed()
|
assert self.isclosed()
|
||||||
|
@ -2865,20 +2922,29 @@ class Path(MutableSequence):
|
||||||
return area_without_arcs(Path(*bezier_path_approximation))
|
return area_without_arcs(Path(*bezier_path_approximation))
|
||||||
|
|
||||||
def intersect(self, other_curve, justonemode=False, tol=1e-12):
|
def intersect(self, other_curve, justonemode=False, tol=1e-12):
|
||||||
"""returns list of pairs of pairs ((T1, seg1, t1), (T2, seg2, t2))
|
"""Finds intersections of `self` with `other_curve`
|
||||||
giving the intersection points.
|
|
||||||
If justonemode==True, then returns just the first
|
Args:
|
||||||
intersection found.
|
other_curve: the path or path segment to check for intersections
|
||||||
tol is used to check for redundant intersections (see comment above
|
with `self`
|
||||||
the code block where tol is used).
|
justonemode (bool): if true, returns only the first
|
||||||
Note: If the two path objects coincide for more than a finite set of
|
intersection found.
|
||||||
points, this code will fail."""
|
tol (float): A tolerance used to check for redundant intersections
|
||||||
|
(see comment above the code block where tol is used).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(list[tuple[float, Curve, float]]): list of intersections, each
|
||||||
|
in the format ((T1, seg1, t1), (T2, seg2, t2)), where
|
||||||
|
self.point(T1) == seg1.point(t1) == seg2.point(t2) == other_curve.point(T2)
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
If the two path objects coincide for more than a finite set of
|
||||||
|
points, this code will iterate to max depth and/or raise an error.
|
||||||
|
"""
|
||||||
path1 = self
|
path1 = self
|
||||||
if isinstance(other_curve, Path):
|
path2 = other_curve if isinstance(other_curve, Path) else Path(other_curve)
|
||||||
path2 = other_curve
|
|
||||||
else:
|
|
||||||
path2 = Path(other_curve)
|
|
||||||
assert path1 != path2
|
assert path1 != path2
|
||||||
|
|
||||||
intersection_list = []
|
intersection_list = []
|
||||||
for seg1 in path1:
|
for seg1 in path1:
|
||||||
for seg2 in path2:
|
for seg2 in path2:
|
||||||
|
@ -2888,6 +2954,7 @@ class Path(MutableSequence):
|
||||||
T1 = path1.t2T(seg1, t1)
|
T1 = path1.t2T(seg1, t1)
|
||||||
T2 = path2.t2T(seg2, t2)
|
T2 = path2.t2T(seg2, t2)
|
||||||
intersection_list.append(((T1, seg1, t1), (T2, seg2, t2)))
|
intersection_list.append(((T1, seg1, t1), (T2, seg2, t2)))
|
||||||
|
|
||||||
if justonemode and intersection_list:
|
if justonemode and intersection_list:
|
||||||
return intersection_list[0]
|
return intersection_list[0]
|
||||||
|
|
||||||
|
@ -2909,8 +2976,7 @@ class Path(MutableSequence):
|
||||||
return intersection_list
|
return intersection_list
|
||||||
|
|
||||||
def bbox(self):
|
def bbox(self):
|
||||||
"""returns a bounding box for the input Path object in the form
|
"""returns bounding box in the form (xmin, xmax, ymin, ymax)."""
|
||||||
(xmin, xmax, ymin, ymax)."""
|
|
||||||
bbs = [seg.bbox() for seg in self._segments]
|
bbs = [seg.bbox() for seg in self._segments]
|
||||||
xmins, xmaxs, ymins, ymaxs = list(zip(*bbs))
|
xmins, xmaxs, ymins, ymaxs = list(zip(*bbs))
|
||||||
xmin = min(xmins)
|
xmin = min(xmins)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# External dependencies
|
# External dependencies
|
||||||
from __future__ import division, absolute_import, print_function
|
from __future__ import division, absolute_import, print_function
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
import sys
|
||||||
from math import sqrt, pi
|
from math import sqrt, pi
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -738,6 +739,47 @@ class ArcTest(TestCase):
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
class TestPath(TestCase):
|
class TestPath(TestCase):
|
||||||
|
|
||||||
|
def test_hash(self):
|
||||||
|
line1 = Line(600.5 + 350.5j, 650.5 + 325.5j)
|
||||||
|
arc1 = Arc(650 + 325j, 25 + 25j, -30, 0, 1, 700 + 300j)
|
||||||
|
arc2 = Arc(650 + 325j, 30 + 25j, -30, 0, 0, 700 + 300j)
|
||||||
|
cub1 = CubicBezier(650 + 325j, 25 + 25j, -30, 700 + 300j)
|
||||||
|
cub2 = CubicBezier(700 + 300j, 800 + 400j, 750 + 200j, 600 + 100j)
|
||||||
|
quad3 = QuadraticBezier(600 + 100j, 600, 600 + 300j)
|
||||||
|
linez = Line(600 + 300j, 600 + 350j)
|
||||||
|
|
||||||
|
bezpath = Path(line1, cub1, cub2, quad3)
|
||||||
|
bezpathz = Path(line1, cub1, cub2, quad3, linez)
|
||||||
|
path = Path(line1, arc1, cub2, quad3)
|
||||||
|
pathz = Path(line1, arc1, cub2, quad3, linez)
|
||||||
|
lpath = Path(linez)
|
||||||
|
qpath = Path(quad3)
|
||||||
|
cpath = Path(cub1)
|
||||||
|
apath = Path(arc1, arc2)
|
||||||
|
|
||||||
|
test_curves = [bezpath, bezpathz, path, pathz, lpath, qpath, cpath,
|
||||||
|
apath, line1, arc1, arc2, cub1, cub2, quad3, linez]
|
||||||
|
|
||||||
|
# this is necessary due to changes to the builtin `hash` function
|
||||||
|
if sys.version_info.major == 2:
|
||||||
|
expected_hashes = [
|
||||||
|
-5762846476463470127, -138736730317965290, -2005041722222729058,
|
||||||
|
8448700906794235291, -5178990533869800243, -4003140762934044601,
|
||||||
|
8575549467429100514, 5166859065265868968, 1373103287265872323,
|
||||||
|
-1022491904150314631, 4188352014604112779, -5090374009174854814,
|
||||||
|
-7093907105533857815, 2036243740727202243, -8108488067585685407]
|
||||||
|
else:
|
||||||
|
expected_hashes = [
|
||||||
|
-6073024107272494569, -2519772625496438197, 8726412907710383506,
|
||||||
|
2132930052750006195, 3112548573593977871, 991446120749438306,
|
||||||
|
-5589397644574569777, -4438808571483114580, -3125333407400456536,
|
||||||
|
-4418099728831808951, 702646573139378041, -6331016786776229094,
|
||||||
|
5053050772929443013, 6102272282813527681, -5385294438006156225,
|
||||||
|
]
|
||||||
|
|
||||||
|
for c, h in zip(test_curves, expected_hashes):
|
||||||
|
self.assertTrue(hash(c) == h, msg="hash {} was expected for curve = {}".format(h, c))
|
||||||
|
|
||||||
def test_circle(self):
|
def test_circle(self):
|
||||||
arc1 = Arc(0j, 100 + 100j, 0, 0, 0, 200 + 0j)
|
arc1 = Arc(0j, 100 + 100j, 0, 0, 0, 200 + 0j)
|
||||||
arc2 = Arc(200 + 0j, 100 + 100j, 0, 0, 0, 0j)
|
arc2 = Arc(200 + 0j, 100 + 100j, 0, 0, 0, 0j)
|
||||||
|
|
Loading…
Reference in New Issue