Initial import
[experiments/circle-surrounded-by-circles.git] / draw_circle_surrounded_by_circles.py
1 #!/usr/bin/env python3
2 #
3 # Draw a circle surrounded by other circles
4 #
5 # Copyright (C) 2018  Antonio Ospite <ao2@ao2.it>
6 # SPDX-License-Identifier: WTFPL
7 #
8 # Using a formula from:
9 # https://math.stackexchange.com/questions/12166/numbers-of-circles-around-a-circle
10
11 from math import pi, sin, cos, degrees
12 import svgwrite
13
14
15 def calc_inner_circle_radius(outer_circles_radius, number_of_circles):
16     if outer_circles_radius <= 0:
17         raise ValueError("Radius of outer circles cannot be zero")
18
19     if number_of_circles < 3:
20         raise ValueError("Number of outer circles cannot be smaller than 3")
21
22     # Ratio between the inner circle and the outer circles
23     ratio = sin(pi / number_of_circles) / (1 - sin(pi / number_of_circles))
24
25     inner_circle_radius = outer_circles_radius / ratio
26
27     return inner_circle_radius
28
29
30 def write_svg(filename, inner_circle_radius, outer_circles_radius,
31               number_of_circles, unit=""):
32     border = 10
33
34     width = height = (inner_circle_radius + outer_circles_radius + border) * 2
35
36     svg = svgwrite.Drawing(filename, profile='full',
37                            size=(str(width) + unit, str(height) + unit),
38                            viewBox=('0 0 %g %g') % (width, height))
39
40     cx_inner = cy_inner = width / 2
41
42     inner_circle = svgwrite.shapes.Circle(center=(cx_inner, cy_inner),
43                                           r=inner_circle_radius,
44                                           fill='none',
45                                           stroke='black',
46                                           stroke_width=0.5)
47     svg.add(inner_circle)
48
49     # Add a group for the first outer circle for extra flexibility.
50     outer_circle_group = svgwrite.container.Group()
51     svg.add(outer_circle_group)
52
53     cx_outer = cx_inner + sin(0) * (inner_circle_radius +
54                                     outer_circles_radius)
55     cy_outer = cy_inner + cos(0) * (inner_circle_radius +
56                                     outer_circles_radius)
57     outer_circle = svgwrite.shapes.Circle(center=(cx_outer, cy_outer),
58                                           r=outer_circles_radius,
59                                           fill='none',
60                                           stroke='black',
61                                           stroke_width=0.5)
62     outer_circle_group.add(outer_circle)
63
64     # Clone the group for the other outer circles to make it easier to keep
65     # visual symmetry by editing just the first group added above.
66     for i in range(1, number_of_circles):
67         theta = i * 2 * pi / number_of_circles
68         outer_circle_clone = svgwrite.container.Use(outer_circle_group,
69                                                     insert=(0, 0),
70                                                     transform="rotate(%f, %f, %f)" %
71                                                     (degrees(theta),
72                                                      cx_inner,
73                                                      cy_inner))
74         svg.add(outer_circle_clone)
75
76     svg.save()
77
78
79 def main():
80     outer_circles_radius = 2.5
81     number_of_circles = 22
82
83     inner_circle_radius = calc_inner_circle_radius(outer_circles_radius, number_of_circles)
84
85     write_svg("circle-surrounded-by-circles.svg", inner_circle_radius,
86               outer_circles_radius, number_of_circles, unit="mm")
87
88
89 if __name__ == "__main__":
90     main()