from __future__ import division from math import * from inkscape_helper.Coordinate import Coordinate class Ellipse(object): """Used as a base class for EllipticArc.""" nr_points = 1024 #used for piecewise linear circumference calculation (ellipse circumference is tricky to calculate) # approximate circumfere: c = pi * (3 * (a + b) - sqrt(10 * a * b + 3 * (a ** 2 + b ** 2))) def __init__(self, x_radius, y_radius): self.y_radius = y_radius self.x_radius = x_radius self.distances = [0] theta = 0 self.angle_step = 2 * pi / self.nr_points for i in range(self.nr_points): prev_dist = self.distances[-1] prev_coord = self.coordinate_at_theta(theta) theta += self.angle_step x, y = x_radius * cos(theta), y_radius * sin(theta) self.distances.append(prev_dist + hypot(prev_coord.x - x, prev_coord.y - y)) @property def circumference(self): return self.distances[-1] def curvature(self, theta): c = self.coordinate_at_theta(theta) return (self.x_radius*self.y_radius)/((cos(theta)**2*self.y_radius**2 + sin(theta)**2*self.x_radius**2)**(3/2)) def tangent(self, theta): angle = self.theta_at_angle(theta) return Coordinate(cos(angle), sin(angle)) def coordinate_at_theta(self, theta): """Coordinate of the point at theta.""" return Coordinate(self.x_radius * cos(theta), self.y_radius * sin(theta)) def dist_from_theta(self, theta_start, theta_end): """Distance accross the surface from point at angle theta_end to point at angle theta_end. Measured in positive (CCW) sense.""" #print 'thetas ', theta_start, theta_end # TODO: figure out why are there so many with same start and end? # make sure thetas are between 0 and 2 * pi theta_start %= 2 * pi theta_end %= 2 * pi i1 = int(theta_start / self.angle_step) p1 = theta_start % self.angle_step l1 = self.distances[i1 + 1] - self.distances[i1] i2 = int(theta_end / self.angle_step) p2 = theta_end % self.angle_step l2 = self.distances[i2 + 1] - self.distances[i2] if theta_start <= theta_end: len = self.distances[i2] - self.distances[i1] + l2 * p2 - l1 * p1 else: len = self.circumference + self.distances[i2] - self.distances[i1] return len def theta_from_dist(self, theta_start, dist): """Returns the angle that you get when starting at theta_start and moving a distance (dist) in CCW direction""" si = int(theta_start / self.angle_step) % self.nr_points p = theta_start % self.angle_step piece_length = self.distances[si + 1] - self.distances[si] start_dist = self.distances[si] + p * piece_length end_dist = dist + start_dist if end_dist > self.circumference: # wrap around zero angle end_dist -= self.circumference min_idx = 0 max_idx = self.nr_points while max_idx - min_idx > 1: # binary search half_idx = min_idx + (max_idx - min_idx) // 2 if self.distances[half_idx] < end_dist: min_idx = half_idx else: max_idx = half_idx step_dist = self.distances[max_idx] - self.distances[min_idx] return (min_idx + (end_dist - self.distances[min_idx]) / step_dist) * self.angle_step def theta_at_angle(self, angle): cf = 0 if angle > pi / 2: cf = pi if angle > 3 * pi / 2: cf = 2 * pi return atan(self.x_radius/self.y_radius * tan(angle)) + cf def skewTransform(self, l, a2, b2): x0 = a2**2 x1 = b2**2 x2 = l**2 x3 = x0*x2 x4 = x0 - x1 + x3 x5 = 2*a2*b2 x6 = x0 + x1 + x3 x7 = sqrt((-x5 + x6)*(x5 + x6)) x9 = 1/(x4 - x7) x10 = x6 - x7 x11 = l*x10 x12 = b2**4 x13 = 4*x12 x14 = x10**2 x15 = 4*x1 x16 = sqrt(-x10*x15 + x13 + x14*x2 + x14) x17 = 2*atan(x9*(x11 - x16)) x18 = sqrt(2) x19 = sqrt(x10) x20 = b2*x18*x19/2 x21 = x0/2 x22 = x1/2 x23 = x2*x21 x24 = x21 - x22 + x23 x25 = x7/2 x27 = 1/(x24 - x25) x28 = x21 + x22 + x23 x29 = x28 - x25 x30 = l*x29 x31 = x14/4 x32 = 2*x1 x33 = sqrt(x12 + x2*x31 - x29*x32 + x31) x34 = 2*atan(x27*(x30 - x33)) x35 = x20*sqrt(1/(-x1*cos(x34)**2 + x29))*sin(x34) x36 = x18/2 x37 = -x19*x36 x39 = 2*atan(x9*(x11 + x16)) x40 = 2*atan(x27*(x30 + x33)) x41 = x20*sqrt(1/(-x1*cos(x40)**2 + x29))*sin(x40) x42 = 1/(x4 + x7) x43 = x6 + x7 x44 = l*x43 x45 = x43**2 x46 = sqrt(x13 - x15*x43 + x2*x45 + x45) x47 = 2*atan(x42*(x44 - x46)) x48 = sqrt(x43) x49 = b2*x18*x48/2 x50 = 1/(x24 + x25) x51 = x25 + x28 x52 = l*x51 x53 = x45/4 x54 = sqrt(x12 + x2*x53 - x32*x51 + x53) x55 = 2*atan(x50*(x52 - x54)) x56 = x49*sqrt(1/(-x1*cos(x55)**2 + x51))*sin(x55) x57 = -x36*x48 x59 = 2*atan(x42*(x44 + x46)) x60 = 2*atan(x50*(x52 + x54)) x61 = x49*sqrt(1/(-x1*cos(x60)**2 + x51))*sin(x60) #solutions (alpha, a1, b1) (x17, -x35, x19*x36) (x17, x35, x37) (x39, -x41, x19*x36) (x39, x41, x37) (x47, -x56, x36*x48) (x47, x56, x57) (x59, -x61, x36*x48) (x59, x61, x57)