Return a `Path` for the unit circle arc from angles *theta1* to *theta2* (in degrees). *theta2* is unwrapped to produce the shortest arc within 360 degrees. That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to *theta2* - 360 and not a full
(cls, theta1, theta2, n=None, is_wedge=False)
| 970 | |
| 971 | @classmethod |
| 972 | def arc(cls, theta1, theta2, n=None, is_wedge=False): |
| 973 | """ |
| 974 | Return a `Path` for the unit circle arc from angles *theta1* to |
| 975 | *theta2* (in degrees). |
| 976 | |
| 977 | *theta2* is unwrapped to produce the shortest arc within 360 degrees. |
| 978 | That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to |
| 979 | *theta2* - 360 and not a full circle plus some extra overlap. |
| 980 | |
| 981 | As a special case, if the span *theta2* - *theta1* is within |
| 982 | floating-point tolerance of a whole number of turns, a complete circle |
| 983 | is drawn. |
| 984 | |
| 985 | If *n* is provided, it is the number of spline segments to make. |
| 986 | If *n* is not provided, the number of spline segments is |
| 987 | determined based on the delta between *theta1* and *theta2*. |
| 988 | |
| 989 | Masionobe, L. 2003. `Drawing an elliptical arc using |
| 990 | polylines, quadratic or cubic Bezier curves |
| 991 | <https://web.archive.org/web/20190318044212/http://www.spaceroots.org/documents/ellipse/index.html>`_. |
| 992 | """ |
| 993 | halfpi = np.pi * 0.5 |
| 994 | |
| 995 | eta1 = theta1 |
| 996 | n_turns = (theta2 - theta1) / 360 |
| 997 | nearest_turn = np.rint(n_turns) |
| 998 | is_full_circle = nearest_turn != 0 and abs(n_turns - nearest_turn) <= 1e-12 |
| 999 | # We unwrap *theta2* to the shortest arc within 360 degrees. |
| 1000 | # Full circles need special handling as floating point errors can |
| 1001 | # make a full circle have 360° + eps, which would be unwrapped |
| 1002 | # to eps only, i.e. collapsing the full circle to an infinitesimal arc. |
| 1003 | # The threshold of 1e-12 is a defensive choice: Much larger than |
| 1004 | # numeric precision errors (~1e-15) but still smaller than any |
| 1005 | # expected real-world arcs. |
| 1006 | if is_full_circle: |
| 1007 | eta2 = theta1 + 360 |
| 1008 | else: |
| 1009 | eta2 = theta2 - 360 * np.floor(n_turns) |
| 1010 | eta1, eta2 = np.deg2rad([eta1, eta2]) |
| 1011 | |
| 1012 | # number of curve segments to make |
| 1013 | if n is None: |
| 1014 | n = int(2 ** np.ceil((eta2 - eta1) / halfpi)) |
| 1015 | if n < 1: |
| 1016 | raise ValueError("n must be >= 1 or None") |
| 1017 | |
| 1018 | deta = (eta2 - eta1) / n |
| 1019 | t = np.tan(0.5 * deta) |
| 1020 | alpha = np.sin(deta) * (np.sqrt(4.0 + 3.0 * t * t) - 1) / 3.0 |
| 1021 | |
| 1022 | steps = np.linspace(eta1, eta2, n + 1, True) |
| 1023 | cos_eta = np.cos(steps) |
| 1024 | sin_eta = np.sin(steps) |
| 1025 | |
| 1026 | xA = cos_eta[:-1] |
| 1027 | yA = sin_eta[:-1] |
| 1028 | xA_dot = -yA |
| 1029 | yA_dot = xA |