158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
|
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)
|