You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			603 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			603 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
A module providing some utility functions regarding Bézier path manipulation.
 | 
						|
"""
 | 
						|
 | 
						|
from functools import lru_cache
 | 
						|
import math
 | 
						|
import warnings
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
from matplotlib import _api
 | 
						|
 | 
						|
 | 
						|
# same algorithm as 3.8's math.comb
 | 
						|
@np.vectorize
 | 
						|
@lru_cache(maxsize=128)
 | 
						|
def _comb(n, k):
 | 
						|
    if k > n:
 | 
						|
        return 0
 | 
						|
    k = min(k, n - k)
 | 
						|
    i = np.arange(1, k + 1)
 | 
						|
    return np.prod((n + 1 - i)/i).astype(int)
 | 
						|
 | 
						|
 | 
						|
class NonIntersectingPathException(ValueError):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
# some functions
 | 
						|
 | 
						|
 | 
						|
def get_intersection(cx1, cy1, cos_t1, sin_t1,
 | 
						|
                     cx2, cy2, cos_t2, sin_t2):
 | 
						|
    """
 | 
						|
    Return the intersection between the line through (*cx1*, *cy1*) at angle
 | 
						|
    *t1* and the line through (*cx2*, *cy2*) at angle *t2*.
 | 
						|
    """
 | 
						|
 | 
						|
    # line1 => sin_t1 * (x - cx1) - cos_t1 * (y - cy1) = 0.
 | 
						|
    # line1 => sin_t1 * x + cos_t1 * y = sin_t1*cx1 - cos_t1*cy1
 | 
						|
 | 
						|
    line1_rhs = sin_t1 * cx1 - cos_t1 * cy1
 | 
						|
    line2_rhs = sin_t2 * cx2 - cos_t2 * cy2
 | 
						|
 | 
						|
    # rhs matrix
 | 
						|
    a, b = sin_t1, -cos_t1
 | 
						|
    c, d = sin_t2, -cos_t2
 | 
						|
 | 
						|
    ad_bc = a * d - b * c
 | 
						|
    if abs(ad_bc) < 1e-12:
 | 
						|
        raise ValueError("Given lines do not intersect. Please verify that "
 | 
						|
                         "the angles are not equal or differ by 180 degrees.")
 | 
						|
 | 
						|
    # rhs_inverse
 | 
						|
    a_, b_ = d, -b
 | 
						|
    c_, d_ = -c, a
 | 
						|
    a_, b_, c_, d_ = (k / ad_bc for k in [a_, b_, c_, d_])
 | 
						|
 | 
						|
    x = a_ * line1_rhs + b_ * line2_rhs
 | 
						|
    y = c_ * line1_rhs + d_ * line2_rhs
 | 
						|
 | 
						|
    return x, y
 | 
						|
 | 
						|
 | 
						|
def get_normal_points(cx, cy, cos_t, sin_t, length):
 | 
						|
    """
 | 
						|
    For a line passing through (*cx*, *cy*) and having an angle *t*, return
 | 
						|
    locations of the two points located along its perpendicular line at the
 | 
						|
    distance of *length*.
 | 
						|
    """
 | 
						|
 | 
						|
    if length == 0.:
 | 
						|
        return cx, cy, cx, cy
 | 
						|
 | 
						|
    cos_t1, sin_t1 = sin_t, -cos_t
 | 
						|
    cos_t2, sin_t2 = -sin_t, cos_t
 | 
						|
 | 
						|
    x1, y1 = length * cos_t1 + cx, length * sin_t1 + cy
 | 
						|
    x2, y2 = length * cos_t2 + cx, length * sin_t2 + cy
 | 
						|
 | 
						|
    return x1, y1, x2, y2
 | 
						|
 | 
						|
 | 
						|
# BEZIER routines
 | 
						|
 | 
						|
# subdividing bezier curve
 | 
						|
# http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-sub.html
 | 
						|
 | 
						|
 | 
						|
def _de_casteljau1(beta, t):
 | 
						|
    next_beta = beta[:-1] * (1 - t) + beta[1:] * t
 | 
						|
    return next_beta
 | 
						|
 | 
						|
 | 
						|
def split_de_casteljau(beta, t):
 | 
						|
    """
 | 
						|
    Split a Bézier segment defined by its control points *beta* into two
 | 
						|
    separate segments divided at *t* and return their control points.
 | 
						|
    """
 | 
						|
    beta = np.asarray(beta)
 | 
						|
    beta_list = [beta]
 | 
						|
    while True:
 | 
						|
        beta = _de_casteljau1(beta, t)
 | 
						|
        beta_list.append(beta)
 | 
						|
        if len(beta) == 1:
 | 
						|
            break
 | 
						|
    left_beta = [beta[0] for beta in beta_list]
 | 
						|
    right_beta = [beta[-1] for beta in reversed(beta_list)]
 | 
						|
 | 
						|
    return left_beta, right_beta
 | 
						|
 | 
						|
 | 
						|
def find_bezier_t_intersecting_with_closedpath(
 | 
						|
        bezier_point_at_t, inside_closedpath, t0=0., t1=1., tolerance=0.01):
 | 
						|
    """
 | 
						|
    Find the intersection of the Bézier curve with a closed path.
 | 
						|
 | 
						|
    The intersection point *t* is approximated by two parameters *t0*, *t1*
 | 
						|
    such that *t0* <= *t* <= *t1*.
 | 
						|
 | 
						|
    Search starts from *t0* and *t1* and uses a simple bisecting algorithm
 | 
						|
    therefore one of the end points must be inside the path while the other
 | 
						|
    doesn't. The search stops when the distance of the points parametrized by
 | 
						|
    *t0* and *t1* gets smaller than the given *tolerance*.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    bezier_point_at_t : callable
 | 
						|
        A function returning x, y coordinates of the Bézier at parameter *t*.
 | 
						|
        It must have the signature::
 | 
						|
 | 
						|
            bezier_point_at_t(t: float) -> tuple[float, float]
 | 
						|
 | 
						|
    inside_closedpath : callable
 | 
						|
        A function returning True if a given point (x, y) is inside the
 | 
						|
        closed path. It must have the signature::
 | 
						|
 | 
						|
            inside_closedpath(point: tuple[float, float]) -> bool
 | 
						|
 | 
						|
    t0, t1 : float
 | 
						|
        Start parameters for the search.
 | 
						|
 | 
						|
    tolerance : float
 | 
						|
        Maximal allowed distance between the final points.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    t0, t1 : float
 | 
						|
        The Bézier path parameters.
 | 
						|
    """
 | 
						|
    start = bezier_point_at_t(t0)
 | 
						|
    end = bezier_point_at_t(t1)
 | 
						|
 | 
						|
    start_inside = inside_closedpath(start)
 | 
						|
    end_inside = inside_closedpath(end)
 | 
						|
 | 
						|
    if start_inside == end_inside and start != end:
 | 
						|
        raise NonIntersectingPathException(
 | 
						|
            "Both points are on the same side of the closed path")
 | 
						|
 | 
						|
    while True:
 | 
						|
 | 
						|
        # return if the distance is smaller than the tolerance
 | 
						|
        if np.hypot(start[0] - end[0], start[1] - end[1]) < tolerance:
 | 
						|
            return t0, t1
 | 
						|
 | 
						|
        # calculate the middle point
 | 
						|
        middle_t = 0.5 * (t0 + t1)
 | 
						|
        middle = bezier_point_at_t(middle_t)
 | 
						|
        middle_inside = inside_closedpath(middle)
 | 
						|
 | 
						|
        if start_inside ^ middle_inside:
 | 
						|
            t1 = middle_t
 | 
						|
            if end == middle:
 | 
						|
                # Edge case where infinite loop is possible
 | 
						|
                # Caused by large numbers relative to tolerance
 | 
						|
                return t0, t1
 | 
						|
            end = middle
 | 
						|
        else:
 | 
						|
            t0 = middle_t
 | 
						|
            if start == middle:
 | 
						|
                # Edge case where infinite loop is possible
 | 
						|
                # Caused by large numbers relative to tolerance
 | 
						|
                return t0, t1
 | 
						|
            start = middle
 | 
						|
            start_inside = middle_inside
 | 
						|
 | 
						|
 | 
						|
class BezierSegment:
 | 
						|
    """
 | 
						|
    A d-dimensional Bézier segment.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    control_points : (N, d) array
 | 
						|
        Location of the *N* control points.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, control_points):
 | 
						|
        self._cpoints = np.asarray(control_points)
 | 
						|
        self._N, self._d = self._cpoints.shape
 | 
						|
        self._orders = np.arange(self._N)
 | 
						|
        coeff = [math.factorial(self._N - 1)
 | 
						|
                 // (math.factorial(i) * math.factorial(self._N - 1 - i))
 | 
						|
                 for i in range(self._N)]
 | 
						|
        self._px = (self._cpoints.T * coeff).T
 | 
						|
 | 
						|
    def __call__(self, t):
 | 
						|
        """
 | 
						|
        Evaluate the Bézier curve at point(s) *t* in [0, 1].
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        t : (k,) array-like
 | 
						|
            Points at which to evaluate the curve.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        (k, d) array
 | 
						|
            Value of the curve for each point in *t*.
 | 
						|
        """
 | 
						|
        t = np.asarray(t)
 | 
						|
        return (np.power.outer(1 - t, self._orders[::-1])
 | 
						|
                * np.power.outer(t, self._orders)) @ self._px
 | 
						|
 | 
						|
    def point_at_t(self, t):
 | 
						|
        """
 | 
						|
        Evaluate the curve at a single point, returning a tuple of *d* floats.
 | 
						|
        """
 | 
						|
        return tuple(self(t))
 | 
						|
 | 
						|
    @property
 | 
						|
    def control_points(self):
 | 
						|
        """The control points of the curve."""
 | 
						|
        return self._cpoints
 | 
						|
 | 
						|
    @property
 | 
						|
    def dimension(self):
 | 
						|
        """The dimension of the curve."""
 | 
						|
        return self._d
 | 
						|
 | 
						|
    @property
 | 
						|
    def degree(self):
 | 
						|
        """Degree of the polynomial. One less the number of control points."""
 | 
						|
        return self._N - 1
 | 
						|
 | 
						|
    @property
 | 
						|
    def polynomial_coefficients(self):
 | 
						|
        r"""
 | 
						|
        The polynomial coefficients of the Bézier curve.
 | 
						|
 | 
						|
        .. warning:: Follows opposite convention from `numpy.polyval`.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        (n+1, d) array
 | 
						|
            Coefficients after expanding in polynomial basis, where :math:`n`
 | 
						|
            is the degree of the Bézier curve and :math:`d` its dimension.
 | 
						|
            These are the numbers (:math:`C_j`) such that the curve can be
 | 
						|
            written :math:`\sum_{j=0}^n C_j t^j`.
 | 
						|
 | 
						|
        Notes
 | 
						|
        -----
 | 
						|
        The coefficients are calculated as
 | 
						|
 | 
						|
        .. math::
 | 
						|
 | 
						|
            {n \choose j} \sum_{i=0}^j (-1)^{i+j} {j \choose i} P_i
 | 
						|
 | 
						|
        where :math:`P_i` are the control points of the curve.
 | 
						|
        """
 | 
						|
        n = self.degree
 | 
						|
        # matplotlib uses n <= 4. overflow plausible starting around n = 15.
 | 
						|
        if n > 10:
 | 
						|
            warnings.warn("Polynomial coefficients formula unstable for high "
 | 
						|
                          "order Bezier curves!", RuntimeWarning)
 | 
						|
        P = self.control_points
 | 
						|
        j = np.arange(n+1)[:, None]
 | 
						|
        i = np.arange(n+1)[None, :]  # _comb is non-zero for i <= j
 | 
						|
        prefactor = (-1)**(i + j) * _comb(j, i)  # j on axis 0, i on axis 1
 | 
						|
        return _comb(n, j) * prefactor @ P  # j on axis 0, self.dimension on 1
 | 
						|
 | 
						|
    def axis_aligned_extrema(self):
 | 
						|
        """
 | 
						|
        Return the dimension and location of the curve's interior extrema.
 | 
						|
 | 
						|
        The extrema are the points along the curve where one of its partial
 | 
						|
        derivatives is zero.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        dims : array of int
 | 
						|
            Index :math:`i` of the partial derivative which is zero at each
 | 
						|
            interior extrema.
 | 
						|
        dzeros : array of float
 | 
						|
            Of same size as dims. The :math:`t` such that :math:`d/dx_i B(t) =
 | 
						|
            0`
 | 
						|
        """
 | 
						|
        n = self.degree
 | 
						|
        if n <= 1:
 | 
						|
            return np.array([]), np.array([])
 | 
						|
        Cj = self.polynomial_coefficients
 | 
						|
        dCj = np.arange(1, n+1)[:, None] * Cj[1:]
 | 
						|
        dims = []
 | 
						|
        roots = []
 | 
						|
        for i, pi in enumerate(dCj.T):
 | 
						|
            r = np.roots(pi[::-1])
 | 
						|
            roots.append(r)
 | 
						|
            dims.append(np.full_like(r, i))
 | 
						|
        roots = np.concatenate(roots)
 | 
						|
        dims = np.concatenate(dims)
 | 
						|
        in_range = np.isreal(roots) & (roots >= 0) & (roots <= 1)
 | 
						|
        return dims[in_range], np.real(roots)[in_range]
 | 
						|
 | 
						|
 | 
						|
def split_bezier_intersecting_with_closedpath(
 | 
						|
        bezier, inside_closedpath, tolerance=0.01):
 | 
						|
    """
 | 
						|
    Split a Bézier curve into two at the intersection with a closed path.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    bezier : (N, 2) array-like
 | 
						|
        Control points of the Bézier segment. See `.BezierSegment`.
 | 
						|
    inside_closedpath : callable
 | 
						|
        A function returning True if a given point (x, y) is inside the
 | 
						|
        closed path. See also `.find_bezier_t_intersecting_with_closedpath`.
 | 
						|
    tolerance : float
 | 
						|
        The tolerance for the intersection. See also
 | 
						|
        `.find_bezier_t_intersecting_with_closedpath`.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    left, right
 | 
						|
        Lists of control points for the two Bézier segments.
 | 
						|
    """
 | 
						|
 | 
						|
    bz = BezierSegment(bezier)
 | 
						|
    bezier_point_at_t = bz.point_at_t
 | 
						|
 | 
						|
    t0, t1 = find_bezier_t_intersecting_with_closedpath(
 | 
						|
        bezier_point_at_t, inside_closedpath, tolerance=tolerance)
 | 
						|
 | 
						|
    _left, _right = split_de_casteljau(bezier, (t0 + t1) / 2.)
 | 
						|
    return _left, _right
 | 
						|
 | 
						|
 | 
						|
# matplotlib specific
 | 
						|
 | 
						|
 | 
						|
def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False):
 | 
						|
    """
 | 
						|
    Divide a path into two segments at the point where ``inside(x, y)`` becomes
 | 
						|
    False.
 | 
						|
    """
 | 
						|
    from .path import Path
 | 
						|
    path_iter = path.iter_segments()
 | 
						|
 | 
						|
    ctl_points, command = next(path_iter)
 | 
						|
    begin_inside = inside(ctl_points[-2:])  # true if begin point is inside
 | 
						|
 | 
						|
    ctl_points_old = ctl_points
 | 
						|
 | 
						|
    iold = 0
 | 
						|
    i = 1
 | 
						|
 | 
						|
    for ctl_points, command in path_iter:
 | 
						|
        iold = i
 | 
						|
        i += len(ctl_points) // 2
 | 
						|
        if inside(ctl_points[-2:]) != begin_inside:
 | 
						|
            bezier_path = np.concatenate([ctl_points_old[-2:], ctl_points])
 | 
						|
            break
 | 
						|
        ctl_points_old = ctl_points
 | 
						|
    else:
 | 
						|
        raise ValueError("The path does not intersect with the patch")
 | 
						|
 | 
						|
    bp = bezier_path.reshape((-1, 2))
 | 
						|
    left, right = split_bezier_intersecting_with_closedpath(
 | 
						|
        bp, inside, tolerance)
 | 
						|
    if len(left) == 2:
 | 
						|
        codes_left = [Path.LINETO]
 | 
						|
        codes_right = [Path.MOVETO, Path.LINETO]
 | 
						|
    elif len(left) == 3:
 | 
						|
        codes_left = [Path.CURVE3, Path.CURVE3]
 | 
						|
        codes_right = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
 | 
						|
    elif len(left) == 4:
 | 
						|
        codes_left = [Path.CURVE4, Path.CURVE4, Path.CURVE4]
 | 
						|
        codes_right = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]
 | 
						|
    else:
 | 
						|
        raise AssertionError("This should never be reached")
 | 
						|
 | 
						|
    verts_left = left[1:]
 | 
						|
    verts_right = right[:]
 | 
						|
 | 
						|
    if path.codes is None:
 | 
						|
        path_in = Path(np.concatenate([path.vertices[:i], verts_left]))
 | 
						|
        path_out = Path(np.concatenate([verts_right, path.vertices[i:]]))
 | 
						|
 | 
						|
    else:
 | 
						|
        path_in = Path(np.concatenate([path.vertices[:iold], verts_left]),
 | 
						|
                       np.concatenate([path.codes[:iold], codes_left]))
 | 
						|
 | 
						|
        path_out = Path(np.concatenate([verts_right, path.vertices[i:]]),
 | 
						|
                        np.concatenate([codes_right, path.codes[i:]]))
 | 
						|
 | 
						|
    if reorder_inout and not begin_inside:
 | 
						|
        path_in, path_out = path_out, path_in
 | 
						|
 | 
						|
    return path_in, path_out
 | 
						|
 | 
						|
 | 
						|
def inside_circle(cx, cy, r):
 | 
						|
    """
 | 
						|
    Return a function that checks whether a point is in a circle with center
 | 
						|
    (*cx*, *cy*) and radius *r*.
 | 
						|
 | 
						|
    The returned function has the signature::
 | 
						|
 | 
						|
        f(xy: tuple[float, float]) -> bool
 | 
						|
    """
 | 
						|
    r2 = r ** 2
 | 
						|
 | 
						|
    def _f(xy):
 | 
						|
        x, y = xy
 | 
						|
        return (x - cx) ** 2 + (y - cy) ** 2 < r2
 | 
						|
    return _f
 | 
						|
 | 
						|
 | 
						|
# quadratic Bezier lines
 | 
						|
 | 
						|
def get_cos_sin(x0, y0, x1, y1):
 | 
						|
    dx, dy = x1 - x0, y1 - y0
 | 
						|
    d = (dx * dx + dy * dy) ** .5
 | 
						|
    # Account for divide by zero
 | 
						|
    if d == 0:
 | 
						|
        return 0.0, 0.0
 | 
						|
    return dx / d, dy / d
 | 
						|
 | 
						|
 | 
						|
def check_if_parallel(dx1, dy1, dx2, dy2, tolerance=1.e-5):
 | 
						|
    """
 | 
						|
    Check if two lines are parallel.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    dx1, dy1, dx2, dy2 : float
 | 
						|
        The gradients *dy*/*dx* of the two lines.
 | 
						|
    tolerance : float
 | 
						|
        The angular tolerance in radians up to which the lines are considered
 | 
						|
        parallel.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    is_parallel
 | 
						|
        - 1 if two lines are parallel in same direction.
 | 
						|
        - -1 if two lines are parallel in opposite direction.
 | 
						|
        - False otherwise.
 | 
						|
    """
 | 
						|
    theta1 = np.arctan2(dx1, dy1)
 | 
						|
    theta2 = np.arctan2(dx2, dy2)
 | 
						|
    dtheta = abs(theta1 - theta2)
 | 
						|
    if dtheta < tolerance:
 | 
						|
        return 1
 | 
						|
    elif abs(dtheta - np.pi) < tolerance:
 | 
						|
        return -1
 | 
						|
    else:
 | 
						|
        return False
 | 
						|
 | 
						|
 | 
						|
def get_parallels(bezier2, width):
 | 
						|
    """
 | 
						|
    Given the quadratic Bézier control points *bezier2*, returns
 | 
						|
    control points of quadratic Bézier lines roughly parallel to given
 | 
						|
    one separated by *width*.
 | 
						|
    """
 | 
						|
 | 
						|
    # The parallel Bezier lines are constructed by following ways.
 | 
						|
    #  c1 and c2 are control points representing the start and end of the
 | 
						|
    #  Bezier line.
 | 
						|
    #  cm is the middle point
 | 
						|
 | 
						|
    c1x, c1y = bezier2[0]
 | 
						|
    cmx, cmy = bezier2[1]
 | 
						|
    c2x, c2y = bezier2[2]
 | 
						|
 | 
						|
    parallel_test = check_if_parallel(c1x - cmx, c1y - cmy,
 | 
						|
                                      cmx - c2x, cmy - c2y)
 | 
						|
 | 
						|
    if parallel_test == -1:
 | 
						|
        _api.warn_external(
 | 
						|
            "Lines do not intersect. A straight line is used instead.")
 | 
						|
        cos_t1, sin_t1 = get_cos_sin(c1x, c1y, c2x, c2y)
 | 
						|
        cos_t2, sin_t2 = cos_t1, sin_t1
 | 
						|
    else:
 | 
						|
        # t1 and t2 is the angle between c1 and cm, cm, c2.  They are
 | 
						|
        # also an angle of the tangential line of the path at c1 and c2
 | 
						|
        cos_t1, sin_t1 = get_cos_sin(c1x, c1y, cmx, cmy)
 | 
						|
        cos_t2, sin_t2 = get_cos_sin(cmx, cmy, c2x, c2y)
 | 
						|
 | 
						|
    # find c1_left, c1_right which are located along the lines
 | 
						|
    # through c1 and perpendicular to the tangential lines of the
 | 
						|
    # Bezier path at a distance of width. Same thing for c2_left and
 | 
						|
    # c2_right with respect to c2.
 | 
						|
    c1x_left, c1y_left, c1x_right, c1y_right = (
 | 
						|
        get_normal_points(c1x, c1y, cos_t1, sin_t1, width)
 | 
						|
    )
 | 
						|
    c2x_left, c2y_left, c2x_right, c2y_right = (
 | 
						|
        get_normal_points(c2x, c2y, cos_t2, sin_t2, width)
 | 
						|
    )
 | 
						|
 | 
						|
    # find cm_left which is the intersecting point of a line through
 | 
						|
    # c1_left with angle t1 and a line through c2_left with angle
 | 
						|
    # t2. Same with cm_right.
 | 
						|
    try:
 | 
						|
        cmx_left, cmy_left = get_intersection(c1x_left, c1y_left, cos_t1,
 | 
						|
                                              sin_t1, c2x_left, c2y_left,
 | 
						|
                                              cos_t2, sin_t2)
 | 
						|
        cmx_right, cmy_right = get_intersection(c1x_right, c1y_right, cos_t1,
 | 
						|
                                                sin_t1, c2x_right, c2y_right,
 | 
						|
                                                cos_t2, sin_t2)
 | 
						|
    except ValueError:
 | 
						|
        # Special case straight lines, i.e., angle between two lines is
 | 
						|
        # less than the threshold used by get_intersection (we don't use
 | 
						|
        # check_if_parallel as the threshold is not the same).
 | 
						|
        cmx_left, cmy_left = (
 | 
						|
            0.5 * (c1x_left + c2x_left), 0.5 * (c1y_left + c2y_left)
 | 
						|
        )
 | 
						|
        cmx_right, cmy_right = (
 | 
						|
            0.5 * (c1x_right + c2x_right), 0.5 * (c1y_right + c2y_right)
 | 
						|
        )
 | 
						|
 | 
						|
    # the parallel Bezier lines are created with control points of
 | 
						|
    # [c1_left, cm_left, c2_left] and [c1_right, cm_right, c2_right]
 | 
						|
    path_left = [(c1x_left, c1y_left),
 | 
						|
                 (cmx_left, cmy_left),
 | 
						|
                 (c2x_left, c2y_left)]
 | 
						|
    path_right = [(c1x_right, c1y_right),
 | 
						|
                  (cmx_right, cmy_right),
 | 
						|
                  (c2x_right, c2y_right)]
 | 
						|
 | 
						|
    return path_left, path_right
 | 
						|
 | 
						|
 | 
						|
def find_control_points(c1x, c1y, mmx, mmy, c2x, c2y):
 | 
						|
    """
 | 
						|
    Find control points of the Bézier curve passing through (*c1x*, *c1y*),
 | 
						|
    (*mmx*, *mmy*), and (*c2x*, *c2y*), at parametric values 0, 0.5, and 1.
 | 
						|
    """
 | 
						|
    cmx = .5 * (4 * mmx - (c1x + c2x))
 | 
						|
    cmy = .5 * (4 * mmy - (c1y + c2y))
 | 
						|
    return [(c1x, c1y), (cmx, cmy), (c2x, c2y)]
 | 
						|
 | 
						|
 | 
						|
def make_wedged_bezier2(bezier2, width, w1=1., wm=0.5, w2=0.):
 | 
						|
    """
 | 
						|
    Being similar to `get_parallels`, returns control points of two quadratic
 | 
						|
    Bézier lines having a width roughly parallel to given one separated by
 | 
						|
    *width*.
 | 
						|
    """
 | 
						|
 | 
						|
    # c1, cm, c2
 | 
						|
    c1x, c1y = bezier2[0]
 | 
						|
    cmx, cmy = bezier2[1]
 | 
						|
    c3x, c3y = bezier2[2]
 | 
						|
 | 
						|
    # t1 and t2 is the angle between c1 and cm, cm, c3.
 | 
						|
    # They are also an angle of the tangential line of the path at c1 and c3
 | 
						|
    cos_t1, sin_t1 = get_cos_sin(c1x, c1y, cmx, cmy)
 | 
						|
    cos_t2, sin_t2 = get_cos_sin(cmx, cmy, c3x, c3y)
 | 
						|
 | 
						|
    # find c1_left, c1_right which are located along the lines
 | 
						|
    # through c1 and perpendicular to the tangential lines of the
 | 
						|
    # Bezier path at a distance of width. Same thing for c3_left and
 | 
						|
    # c3_right with respect to c3.
 | 
						|
    c1x_left, c1y_left, c1x_right, c1y_right = (
 | 
						|
        get_normal_points(c1x, c1y, cos_t1, sin_t1, width * w1)
 | 
						|
    )
 | 
						|
    c3x_left, c3y_left, c3x_right, c3y_right = (
 | 
						|
        get_normal_points(c3x, c3y, cos_t2, sin_t2, width * w2)
 | 
						|
    )
 | 
						|
 | 
						|
    # find c12, c23 and c123 which are middle points of c1-cm, cm-c3 and
 | 
						|
    # c12-c23
 | 
						|
    c12x, c12y = (c1x + cmx) * .5, (c1y + cmy) * .5
 | 
						|
    c23x, c23y = (cmx + c3x) * .5, (cmy + c3y) * .5
 | 
						|
    c123x, c123y = (c12x + c23x) * .5, (c12y + c23y) * .5
 | 
						|
 | 
						|
    # tangential angle of c123 (angle between c12 and c23)
 | 
						|
    cos_t123, sin_t123 = get_cos_sin(c12x, c12y, c23x, c23y)
 | 
						|
 | 
						|
    c123x_left, c123y_left, c123x_right, c123y_right = (
 | 
						|
        get_normal_points(c123x, c123y, cos_t123, sin_t123, width * wm)
 | 
						|
    )
 | 
						|
 | 
						|
    path_left = find_control_points(c1x_left, c1y_left,
 | 
						|
                                    c123x_left, c123y_left,
 | 
						|
                                    c3x_left, c3y_left)
 | 
						|
    path_right = find_control_points(c1x_right, c1y_right,
 | 
						|
                                     c123x_right, c123y_right,
 | 
						|
                                     c3x_right, c3y_right)
 | 
						|
 | 
						|
    return path_left, path_right
 |