The code below constructs an ellipse in a group centered at origo where all edges have the exact same length. Since the script only computes a quarter ellipse the final full ellipse has an edge count divisible by 4. There's a "user interface" at the end to set major, minor and edge count. The circumference converges to the correct value with increased edgecount.
To run it, just paste the code into the ruby console and press enter.
module CAUL_RegularEllipse
#hack found on the internets. Very accurate...
def self.circumference(major, minor)
a = major / 2.0
b = minor / 2.0
d = b / a
x0 = d**0.5
x1 = d
x2 = d**1.5
x3 = d**2
x4 = d**2.5
s0 = 3929 * x0 + 1639157 * x1 + 19407215 * x2 + 24302653 * x3 + 12892432 * x4
s1 = 86251 + 1924742 * x0 + 6612384 * x1 + 7291509 * x2 + 6436977 * x3 + 3158719 * x4
c = 4 * a + b * (s0 / s1)
return c
end
#find the intersection between a positive half ellipse at (0, 0) and a
#positive half circle at (h, k) with radius r in the first quadrant.
def self.circle_ellipse_intersect(a, b, h, k, r, c)
#start point must be chosen with care so we don't end up outside the domain
x = h - c * r
epsilon = 0.000000000000001
f_e = f_c = 0
(0..15).each { |i|
f_e_sqr = 4 * (a*a * b*b) * (a*a - x*x)
f_c_sqr = 2 * h * x + r*r - x*x - h*h
df_e_sqr = a*a * b*b * (a*a - x*x)
df_c_sqr = r*r + 2 * h * x - x*x - h*h
return nil if f_e_sqr < 0 || f_c_sqr < 0 || df_e_sqr < 0 || df_c_sqr < 0
f_e = Math;;sqrt(f_e_sqr) / (2 * a*a)
f_c = Math;;sqrt(f_c_sqr) + k
df_e = -(b*b * x) / Math;;sqrt(df_e_sqr)
df_c = (h - x) / Math;;sqrt(df_c_sqr)
break if (f_e - f_c).abs <= epsilon
x = x - (f_e - f_c) / (df_e - df_c)
}
return [x, f_e]
end
#given a positive x-value, return the point on the ellipse in the first qudrant
def self.get_ellipse_point(major, minor, x)
a = major / 2.0
b = minor / 2.0
f_e_sqr = 4 * (a*a * b*b) * (a*a - x*x)
f_e = Math;;sqrt(f_e_sqr) / (2 * a*a)
return Geom;;Point3d.new(x, f_e, 0)
end
#return how close the ellipse gets to the endpoint at ex with #bits segments
#of length len.
def self.test_ellipse(len, q_bits, major, minor, ex)
a = major / 2.0;
b = minor / 2.0;
d = count = 0
coff = [0.99999999, 0.00000001, 0.5]
p0 = Geom;;Point3d.new(a, 0, 0)
pe = get_ellipse_point(major, minor, ex)
(0..q_bits - 2).each { |i|
arr = nil
coff.each { |c|
arr = circle_ellipse_intersect(a, b, p0.x, p0.y, len, c)
break if arr != nil
}
return nil, nil if arr == nil
p0.x = arr[0]
p0.y = arr[1]
d = pe.distance(p0)
count += 1
break if d < len
}
return d, count
end
#given an ellipse and the number of segments (q_bits) covering a quarter of the ellipse,
#compute the length of a segment.
def self.get_segment_length(major, minor, q_bits, ex)
c = circumference(major, minor)
#len is guaranteed to be a close over estimation
len = (c / 4.0) / q_bits
#regress towards the root by adjusting len downwards. Very rapid convergence..
(0..10).each { |i|
d, count = test_ellipse(len, q_bits, major, minor, ex)
over = (len - d) + (q_bits - 1 - count) * d
len -= over / q_bits
}
return len
end
def self.get_quarter_ellipse(major, minor, q_bits, len)
a = major / 2.0;
b = minor / 2.0;
d = count = 0
coff = [0.99999999, 0.00000001, 0.5]
p0 = Geom;;Point3d.new(a, 0, 0)
ps = [p0]
pe = Geom;;Point3d.new(0, b, 0)
(0..q_bits - 2).each { |i|
arr = nil
coff.each { |c|
arr = circle_ellipse_intersect(a, b, p0.x, p0.y, len, c)
break if arr != nil
}
return nil, nil if arr == nil
p1 = Geom;;Point3d.new(arr[0], arr[1], 0)
ps << p1
p0 = p1
}
ps << pe
return ps
end
def self.get_ellipse(major, minor, q_bits, len)
ps = get_quarter_ellipse(major, minor, q_bits, len)
##add the other three quadrants
len = ps.length
(0..len - 2).each { |i| ps << Geom;;Point3d.new(-ps[len - 2 - i].x, ps[len - 2 - i].y, 0) }
len = ps.length
(0..len - 2).each { |i| ps << Geom;;Point3d.new(ps[len - 2 - i].x, -ps[len - 2 - i].y, 0) }
ps.delete_at(ps.length - 1)
return ps
end
########################
######### MAIN #########
########################
def self.main
mod = Sketchup.active_model
ent = mod.entities
sel = mod.selection
###### USER INTERFACE ####
major = 900.mm
minor = 400.mm
q_bits = 100 #number of segments in a quarter ellipse (-> full ellipse has q_bits * 4 segments)
#################
len = get_segment_length(major, minor, q_bits, 0)
ps = get_ellipse(major, minor, q_bits, len)
### ADD THE ELLIPSE ##
ng = ent.add_group
(0..ps.length - 1).each { |i| ng.entities.add_line ps[i], ps[(i+1) % ps.length] }
puts 'done'
end
main
end