Title: Make a ring of tangent circles inside another ring of tangent circles in Python
My earlier post Make a ring of small, tangent circles inside a larger circle in Python explained how to create a ring of small, tangent circles inside a larger circle. After seeing that result, I wondered how hard it would be to make a ring of even smaller circles inside that one. It turns out it's a bit harder but it's still manageable if you know the law of cosines and the quadratic formula.
Finding the Inner Circles
Consider the picture on the right which shows part of a ring of green circles with a ring of red circles inside it. The circles' radii and distances from the rings' centers are r/R and g/G respectively.
To make the following equations simpler, let's abbreviate c = cos(α) and s = sin(α).
The first equation is simple. (In these equations, I'm highlighting the unknown values R and r in red so it's easy to see which values are known and which are mysteries to be solved.)
The second equation is trickier. Looking at the triangle formed by the dashed lines in the picture, the law of cosines says:
If you don't remember the law of cosines (I don't), see this Wikipedia article.
Now it's just a matter of algebra. First we plug in the value for R in terms of r:
Expanding this out gives:
Now we can group the r terms:
Now let:
Now we can use the quadratic formula to find r:
Calculating the Inner Circles
The following get_nested_circles function returns a list holding the centers and radii of the nested circles.
def get_nested_circles(n, cx, cy, G, g):
alpha = 2 * math.pi / n / 2
s = math.sin(alpha)
c = math.cos(alpha)
A = 1/s**2 - 1
B = -2 * G * c / s - 2 * g
C = G**2 - g**2
r1 = (-B + math.sqrt(B**2 - 4 * A * C)) / (2 * A)
r2 = (-B - math.sqrt(B**2 - 4 * A * C)) / (2 * A)
r = r2
R = r / s
centers = []
theta = -math.pi / 2 + alpha
dtheta = 2 * math.pi / n
for i in range(n):
x = cx + R * math.cos(theta)
y = cy + R * math.sin(theta)
centers.append((x, y, r))
theta += dtheta
return centers
This function first creates variables holding the various values used in the earlier equations. It sets values for
alpha,
s,
c,
A,
B, and
C. It then uses the quadratic formula to calculate the two possible solutions
r1 and
r2.
The smaller solution is the one we want, so the code sets r = r2 and uses it to calculate R.
Next, the function sets theta equal to the angle to the first nested circle's center. The value -math.pi / 2 makes the angle point upward. The additional alpha offsets the first circle so its center angle is halfway between the center angles of two of the outer ring of circles.
The function then uses a loop to generate the n circles and adds their centers and radii to the circles list. When it's done, it returns the list.
Drawing the Circles
Here's the code that draws the circles.
# Draw the big circle.
rect = (cx - R, cy - R, cx + R, cy + R)
dr.ellipse(rect, outline='black')
# Draw the smaller inside circles.
centers = get_inner_circles(n, cx, cy, R)
for x, y, r in centers:
rect = (x-r, y-r, x+r, y+r)
dr.ellipse(rect, outline='green', fill='lightgreen', width=2)
# Get next level.
d = math.dist((cx, cy), (x, y))
centers = get_nested_circles(n, cx, cy, d, r)
for x, y, r in centers:
rect = (x-r, y-r, x+r, y+r)
dr.ellipse(rect, outline='red', fill='pink', width=2)
This snippet first draws the outer black circle. It then calls get_inner_circles (described in my previous post) to get the positions and radii of the smaller green circles and uses a loop to draw them.
The code then finds the distance between the center of the black circle (cx, cy) and the last green circle's center (x, y). That gives it the distance d in the picture and the equations shown earlier.
The program then calls get_nested_circles to get the data about the red circles and uses another loop to draw them.
Conclusion
Drawing a ring of circles inside an outer ring is a bit harder than drawing circles inside a circumscribed circle, but it's still doable. Download the example to experiment with it and to see additional details.
|