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