~~NOTOC~~
|<100% 25% - >|
^ \\ 3D PRINTING AND DESIGN REFERENCE DOCUMENT\\ \\ ^^
^ Document Title:|Generating an analemma svg with Python code|
^ Document No.:|1729092037|
^ Author(s):|jattie|
^ Contributor(s):| |
**REVISION HISTORY**
|< 100% 10% - - 10% 17% 10% >|
^ \\ Revision\\ \\ ^\\ Details of Modification(s)^\\ Reason for modification^ \\ Date ^ \\ By ^
| [[:doku.php?id=03_designing_for_3d_printing:03_analemma_with_python&do=revisions|0]] |Draft release|Python script to generate analemma svg| 2024/10/16 15:20 | jattie |
----
====== Analemma SVG ======
The work here is inspired by the designs and meticulous documentation provided by [[https://www.printables.com/@yba2cuo3_12119|@yba2cuo3]] on [[https://www.printables.com/|printables]]. ((https://www.printables.com/model/1036568-curved-analemma-plate-for-a-heliochronometer-sundi)) I wanted a pure python solutions without using blender.
As a 3D hobbyist we sometime want to do complicated things that is hard to do accurately in 3D design tools. Since I discovered the use of SVG's and python tools to generate them, I wondered if I can reproduce an Analemma to use on a sundial using Python code. After many wasted hours and trail and error working out angular math in python I can report that it is in fact quite feasible.
The catch was converting to radians.
===== The python code =====
With lots of credit to copilot, I eventually managed to crack the problem.
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
usecolour=True
# Function to calculate the analemma
def calculate_analemma():
days = np.arange(0, 365)
declination = []
equation_of_time = []
for day in days:
B = (360 / 365) * (day - 81)
EoT = 9.87 * np.sin(np.radians(2 * B)) - 7.53 * np.cos(np.radians(B)) - 1.5 * np.sin(np.radians(B))
decl = 23.45 * np.sin(np.radians((360 / 365) * (day - 81)))
equation_of_time.append(EoT)
declination.append(decl)
return days, declination, equation_of_time
# Calculate analemma
days, declination, equation_of_time = calculate_analemma()
# Plot the analemma with reduced width to 1/4
plt.figure(figsize=(2, 6)) # Reduced width to 1/4
if usecolour:
plt.plot(equation_of_time, declination, label='Analemma', linewidth=3)
else:
plt.plot(equation_of_time, declination, label='Analemma', linewidth=3, color='black')
# Add plot points for the first day of every month
first_days = [datetime(2023, month, 1) for month in range(1, 13)]
first_days_indices = [(day - datetime(2023, 1, 1)).days for day in first_days]
month_labels = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D']
c=0 # index counter for declanation offset
for i, label in zip(first_days_indices, month_labels):
dec_offset=[-1.5, 0.0, 0.0, 0.0, 0.0, 0.0, 2.3, 0.0, 0.0, 0.0, 0.0, 0.0] # keep the labels off the lines
if usecolour:
plt.plot(equation_of_time[i], declination[i], 'ro') # Red points for the first day of each month
else:
plt.plot(equation_of_time[i], declination[i], 'ko') # Red points for the first day of each month
plt.text(equation_of_time[i] + 2.0, declination[i] + dec_offset[c], label, fontsize=12, ha='left', weight='bold') # Move labels higher
c+=1 # increment counter
plt.xlabel('Equation of Time (minutes)')
plt.ylabel('Declination (degrees)')
#plt.title('Analemma with First Day of Each Month')
plt.gca().invert_yaxis() # Flip the plot on the y-axis
plt.grid(False) # Remove gridlines
# Remove the plot box
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['left'].set_visible(False)
plt.gca().spines['bottom'].set_visible(False)
# Remove the legend
plt.legend().set_visible(False)
# Add a small dot to the center point of the plot for alignment purposes
center_x = (max(equation_of_time) + min(equation_of_time)) / 2
center_y = (max(declination) + min(declination)) / 2
if usecolour:
plt.plot(center_x, center_y, 'bo') # Blue dot at the center
else:
plt.plot(center_x, center_y, 'ko') # Black dot at the center
# Save the plot as an SVG file
if usecolour:
plt.savefig("analemma_plot_colour.svg", format="svg", bbox_inches='tight')
else:
plt.savefig("analemma_plot.svg", format="svg", bbox_inches='tight')
plt.show()
print("plot saved: 'analemma_plot.svg'.")
===== The final result =====
The final result is perfect for importing and extruding on a CAD package supporting SVG imports.
{{:03_designing_for_3d_printing:analemma_plot_colour.svg|}}{{:03_designing_for_3d_printing:analemma_plot.svg|}}
Hover over and right click to save the svg
This format converted from a plot renders acceptably, but fails to properly import into at least Fusion 360. It is not very useful for 3D printing purposed. Some online tool may do a better job at converting this properly for CAD use.