File indexing completed on 2025-01-19 03:59:54
0001 import math 0002 0003 from ..nvector import NVector 0004 from ..objects.bezier import BezierPoint 0005 0006 0007 ## @todo Just output a Bezier object 0008 class Ellipse: 0009 def __init__(self, center, radii, xrot): 0010 """ 0011 @param center 2D vector, center of the ellipse 0012 @param radii 2D vector, x/y radius of the ellipse 0013 @param xrot Angle between the main axis of the ellipse and the x axis (in radians) 0014 """ 0015 self.center = center 0016 self.radii = radii 0017 self.xrot = xrot 0018 0019 def point(self, t): 0020 return NVector( 0021 self.center[0] 0022 + self.radii[0] * math.cos(self.xrot) * math.cos(t) 0023 - self.radii[1] * math.sin(self.xrot) * math.sin(t), 0024 0025 self.center[1] 0026 + self.radii[0] * math.sin(self.xrot) * math.cos(t) 0027 + self.radii[1] * math.cos(self.xrot) * math.sin(t) 0028 ) 0029 0030 def derivative(self, t): 0031 return NVector( 0032 - self.radii[0] * math.cos(self.xrot) * math.sin(t) 0033 - self.radii[1] * math.sin(self.xrot) * math.cos(t), 0034 0035 - self.radii[0] * math.sin(self.xrot) * math.sin(t) 0036 + self.radii[1] * math.cos(self.xrot) * math.cos(t) 0037 ) 0038 0039 def to_bezier(self, anglestart, angle_delta): 0040 points = [] 0041 angle1 = anglestart 0042 angle_left = abs(angle_delta) 0043 step = math.pi / 2 0044 sign = -1 if anglestart+angle_delta < angle1 else 1 0045 0046 # We need to fix the first handle 0047 firststep = min(angle_left, step) * sign 0048 alpha = self._alpha(firststep) 0049 q1 = self.derivative(angle1) * alpha 0050 points.append(BezierPoint(self.point(angle1), NVector(0, 0), q1)) 0051 0052 # Then we iterate until the angle has been completed 0053 tolerance = step / 2 0054 while angle_left > tolerance: 0055 lstep = min(angle_left, step) 0056 step_sign = lstep * sign 0057 angle2 = angle1 + step_sign 0058 angle_left -= abs(lstep) 0059 0060 alpha = self._alpha(step_sign) 0061 p2 = self.point(angle2) 0062 q2 = self.derivative(angle2) * alpha 0063 0064 points.append(BezierPoint(p2, -q2, q2)) 0065 angle1 = angle2 0066 return points 0067 0068 def _alpha(self, step): 0069 return math.sin(step) * (math.sqrt(4+3*math.tan(step/2)**2) - 1) / 3 0070 0071 @classmethod 0072 def from_svg_arc(cls, start, rx, ry, xrot, large, sweep, dest): 0073 rx = abs(rx) 0074 ry = abs(ry) 0075 0076 x1 = start[0] 0077 y1 = start[1] 0078 x2 = dest[0] 0079 y2 = dest[1] 0080 phi = math.pi * xrot / 180 0081 0082 x1p, y1p = _matrix_mul(phi, (start-dest)/2, -1) 0083 0084 cr = x1p ** 2 / rx**2 + y1p**2 / ry**2 0085 if cr > 1: 0086 s = math.sqrt(cr) 0087 rx *= s 0088 ry *= s 0089 0090 dq = rx**2 * y1p**2 + ry**2 * x1p**2 0091 pq = (rx**2 * ry**2 - dq) / dq 0092 cpm = math.sqrt(max(0, pq)) 0093 if large == sweep: 0094 cpm = -cpm 0095 cp = NVector(cpm * rx * y1p / ry, -cpm * ry * x1p / rx) 0096 c = _matrix_mul(phi, cp) + NVector((x1+x2)/2, (y1+y2)/2) 0097 theta1 = _angle(NVector(1, 0), NVector((x1p - cp[0]) / rx, (y1p - cp[1]) / ry)) 0098 deltatheta = _angle( 0099 NVector((x1p - cp[0]) / rx, (y1p - cp[1]) / ry), 0100 NVector((-x1p - cp[0]) / rx, (-y1p - cp[1]) / ry) 0101 ) % (2*math.pi) 0102 0103 if not sweep and deltatheta > 0: 0104 deltatheta -= 2*math.pi 0105 elif sweep and deltatheta < 0: 0106 deltatheta += 2*math.pi 0107 0108 return cls(c, NVector(rx, ry), phi), theta1, deltatheta 0109 0110 0111 def _matrix_mul(phi, p, sin_mul=1): 0112 c = math.cos(phi) 0113 s = math.sin(phi) * sin_mul 0114 0115 xr = c * p.x - s * p.y 0116 yr = s * p.x + c * p.y 0117 return NVector(xr, yr) 0118 0119 0120 def _angle(u, v): 0121 arg = math.acos(max(-1, min(1, u.dot(v) / (u.length * v.length)))) 0122 if u[0] * v[1] - u[1] * v[0] < 0: 0123 return -arg 0124 return arg