1 # SPDX-License-Identifier: GPL-2.0-or-later
4 from bpy
.app
.handlers
import persistent
7 from gpu_extras
.batch
import batch_for_shader
9 from mathutils
import Euler
, Vector
11 from math
import degrees
, radians
, pi
, sin
, cos
, asin
, acos
, tan
, floor
17 Store intermediate sun calculations
45 use_daylight_savings
= False
51 def move_sun(context
):
53 Cycle through all the selected objects and set their position and rotation
56 addon_prefs
= context
.preferences
.addons
[__package__
].preferences
57 sun_props
= context
.scene
.sun_pos_properties
59 if sun_props
.usage_mode
== "HDR":
60 nt
= context
.scene
.world
.node_tree
.nodes
61 env_tex
= nt
.get(sun_props
.hdr_texture
)
63 if sun
.bind_to_sun
!= sun_props
.bind_to_sun
:
64 # bind_to_sun was just toggled
65 sun
.bind_to_sun
= sun_props
.bind_to_sun
66 sun
.bind
.az_start_sun
= sun_props
.hdr_azimuth
68 sun
.bind
.az_start_env
= env_tex
.texture_mapping
.rotation
.z
70 if env_tex
and sun_props
.bind_to_sun
:
71 az
= sun_props
.hdr_azimuth
- sun
.bind
.az_start_sun
+ sun
.bind
.az_start_env
72 env_tex
.texture_mapping
.rotation
.z
= az
74 if sun_props
.sun_object
:
75 obj
= sun_props
.sun_object
76 obj
.location
= get_sun_vector(
77 sun_props
.hdr_azimuth
, sun_props
.hdr_elevation
) * sun_props
.sun_distance
79 rotation_euler
= Euler((sun_props
.hdr_elevation
- pi
/2,
80 0, -sun_props
.hdr_azimuth
))
82 set_sun_rotations(obj
, rotation_euler
)
85 local_time
= sun_props
.time
86 zone
= -sun_props
.UTC_zone
87 sun
.use_daylight_savings
= sun_props
.use_daylight_savings
88 if sun
.use_daylight_savings
:
91 if addon_prefs
.show_rise_set
:
92 calc_sunrise_sunset(rise
=True)
93 calc_sunrise_sunset(rise
=False)
95 azimuth
, elevation
= get_sun_coordinates(
96 local_time
, sun_props
.latitude
, sun_props
.longitude
,
97 zone
, sun_props
.month
, sun_props
.day
, sun_props
.year
)
100 sun
.elevation
= elevation
101 sun_vector
= get_sun_vector(azimuth
, elevation
)
103 if sun_props
.sky_texture
:
104 sky_node
= bpy
.context
.scene
.world
.node_tree
.nodes
.get(sun_props
.sky_texture
)
105 if sky_node
is not None and sky_node
.type == "TEX_SKY":
106 sky_node
.texture_mapping
.rotation
.z
= 0.0
107 sky_node
.sun_direction
= sun_vector
108 sky_node
.sun_elevation
= elevation
109 sky_node
.sun_rotation
= azimuth
112 if (sun_props
.sun_object
is not None
113 and sun_props
.sun_object
.name
in context
.view_layer
.objects
):
114 obj
= sun_props
.sun_object
115 obj
.location
= sun_vector
* sun_props
.sun_distance
116 rotation_euler
= Euler((elevation
- pi
/2, 0, -azimuth
))
117 set_sun_rotations(obj
, rotation_euler
)
120 if sun_props
.object_collection
is not None:
121 sun_objects
= sun_props
.object_collection
.objects
122 object_count
= len(sun_objects
)
123 if sun_props
.object_collection_type
== 'DIURNAL':
126 time_increment
= sun_props
.time_spread
/ (object_count
- 1)
127 local_time
= local_time
+ time_increment
* (object_count
- 1)
129 time_increment
= sun_props
.time_spread
131 for obj
in sun_objects
:
132 azimuth
, elevation
= get_sun_coordinates(
133 local_time
, sun_props
.latitude
,
134 sun_props
.longitude
, zone
,
135 sun_props
.month
, sun_props
.day
, sun_props
.year
)
136 obj
.location
= get_sun_vector(azimuth
, elevation
) * sun_props
.sun_distance
137 local_time
-= time_increment
138 obj
.rotation_euler
= ((elevation
- pi
/2, 0, -azimuth
))
141 day_increment
= 365 / object_count
142 day
= sun_props
.day_of_year
+ day_increment
* (object_count
- 1)
143 for obj
in sun_objects
:
144 dt
= (datetime
.date(sun_props
.year
, 1, 1) +
145 datetime
.timedelta(day
- 1))
146 azimuth
, elevation
= get_sun_coordinates(
147 local_time
, sun_props
.latitude
,
148 sun_props
.longitude
, zone
,
149 dt
.month
, dt
.day
, sun_props
.year
)
150 obj
.location
= get_sun_vector(azimuth
, elevation
) * sun_props
.sun_distance
152 obj
.rotation_euler
= (elevation
- pi
/2, 0, -azimuth
)
155 def day_of_year_to_month_day(year
, day_of_year
):
156 dt
= (datetime
.date(year
, 1, 1) + datetime
.timedelta(day_of_year
- 1))
157 return dt
.day
, dt
.month
160 def month_day_to_day_of_year(year
, month
, day
):
161 dt
= datetime
.date(year
, month
, day
)
162 return dt
.timetuple().tm_yday
165 def update_time(context
):
166 sun_props
= context
.scene
.sun_pos_properties
168 if sun_props
.use_day_of_year
:
169 day
, month
= day_of_year_to_month_day(sun_props
.year
,
170 sun_props
.day_of_year
)
173 sun
.day_of_year
= sun_props
.day_of_year
174 if sun_props
.day
!= day
:
176 if sun_props
.month
!= month
:
177 sun_props
.month
= month
179 day_of_year
= month_day_to_day_of_year(
180 sun_props
.year
, sun_props
.month
, sun_props
.day
)
181 sun
.day
= sun_props
.day
182 sun
.month
= sun_props
.month
183 sun
.day_of_year
= day_of_year
184 if sun_props
.day_of_year
!= day_of_year
:
185 sun_props
.day_of_year
= day_of_year
187 sun
.year
= sun_props
.year
188 sun
.longitude
= sun_props
.longitude
189 sun
.latitude
= sun_props
.latitude
190 sun
.UTC_zone
= sun_props
.UTC_zone
194 def sun_handler(scene
):
195 update_time(bpy
.context
)
196 move_sun(bpy
.context
)
199 def format_time(time
, daylight_savings
, UTC_zone
=None):
200 if UTC_zone
is not None:
207 return format_hms(time
)
210 def format_hms(time
):
212 mm
= (time
% 1.0) * 60
215 return f
"{hh:02d}:{int(mm):02d}:{int(ss):02d}"
218 def format_lat_long(latitude
, longitude
):
221 for i
, co
in enumerate((latitude
, longitude
)):
223 mm
= abs(co
- int(co
)) * 60.0
224 ss
= abs(mm
- int(mm
)) * 60.0
228 direction
= "N" if co
> 0 else "S"
230 direction
= "E" if co
> 0 else "W"
232 coordinates
+= f
"{dd:02d}°{int(mm):02d}′{ss:05.2f}″{direction} "
234 return coordinates
.strip(" ")
237 def get_sun_coordinates(local_time
, latitude
, longitude
,
238 utc_zone
, month
, day
, year
):
240 Calculate the actual position of the sun based on input parameters.
242 The sun positioning algorithms below are based on the National Oceanic
243 and Atmospheric Administration's (NOAA) Solar Position Calculator
244 which rely on calculations of Jean Meeus' book "Astronomical Algorithms."
245 Use of NOAA data and products are in the public domain and may be used
246 freely by the public as outlined in their policies at
247 www.nws.noaa.gov/disclaimer.php
249 The calculations of this script can be verified with those of NOAA's
250 using the Azimuth and Solar Elevation displayed in the SunPos_Panel.
252 http://www.esrl.noaa.gov/gmd/grad/solcalc
254 sun_props
= bpy
.context
.scene
.sun_pos_properties
256 longitude
*= -1 # for internal calculations
257 utc_time
= local_time
+ utc_zone
# Set Greenwich Meridian Time
259 if latitude
> 89.93: # Latitude 90 and -90 gives
260 latitude
= radians(89.93) # erroneous results so nudge it
261 elif latitude
< -89.93:
262 latitude
= radians(-89.93)
264 latitude
= radians(latitude
)
266 t
= julian_time_from_y2k(utc_time
, year
, month
, day
)
268 e
= radians(obliquity_correction(t
))
269 L
= apparent_longitude_of_sun(t
)
270 solar_dec
= sun_declination(e
, L
)
271 eqtime
= calc_equation_of_time(t
)
273 time_correction
= (eqtime
- 4 * longitude
) + 60 * utc_zone
274 true_solar_time
= ((utc_time
- utc_zone
) * 60.0 + time_correction
) % 1440
276 hour_angle
= true_solar_time
/ 4.0 - 180.0
277 if hour_angle
< -180.0:
280 csz
= (sin(latitude
) * sin(solar_dec
) +
281 cos(latitude
) * cos(solar_dec
) *
282 cos(radians(hour_angle
)))
290 az_denom
= cos(latitude
) * sin(zenith
)
292 if abs(az_denom
) > 0.001:
293 az_rad
= ((sin(latitude
) *
294 cos(zenith
)) - sin(solar_dec
)) / az_denom
295 if abs(az_rad
) > 1.0:
296 az_rad
= -1.0 if (az_rad
< 0.0) else 1.0
297 azimuth
= pi
- acos(az_rad
)
301 azimuth
= pi
if (latitude
> 0.0) else 0.0
306 exoatm_elevation
= 90.0 - degrees(zenith
)
308 if sun_props
.use_refraction
:
309 if exoatm_elevation
> 85.0:
310 refraction_correction
= 0.0
312 te
= tan(radians(exoatm_elevation
))
313 if exoatm_elevation
> 5.0:
314 refraction_correction
= (
315 58.1 / te
- 0.07 / (te
** 3) + 0.000086 / (te
** 5))
316 elif exoatm_elevation
> -0.575:
317 s1
= -12.79 + exoatm_elevation
* 0.711
318 s2
= 103.4 + exoatm_elevation
* s1
319 s3
= -518.2 + exoatm_elevation
* s2
320 refraction_correction
= 1735.0 + exoatm_elevation
* (s3
)
322 refraction_correction
= -20.774 / te
324 refraction_correction
/= 3600
325 elevation
= pi
/2 - (zenith
- radians(refraction_correction
))
328 elevation
= pi
/2 - zenith
330 azimuth
+= sun_props
.north_offset
332 return azimuth
, elevation
335 def get_sun_vector(azimuth
, elevation
):
337 Convert the sun coordinates to cartesian
340 theta
= pi
/2 - elevation
342 loc_x
= sin(phi
) * sin(-theta
)
343 loc_y
= sin(theta
) * cos(phi
)
345 return Vector((loc_x
, loc_y
, loc_z
))
348 def set_sun_rotations(obj
, rotation_euler
):
349 rotation_quaternion
= rotation_euler
.to_quaternion()
350 obj
.rotation_quaternion
= rotation_quaternion
352 if obj
.rotation_mode
in {'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'}:
353 obj
.rotation_euler
= rotation_quaternion
.to_euler(obj
.rotation_mode
)
355 obj
.rotation_euler
= rotation_euler
357 rotation_axis_angle
= obj
.rotation_quaternion
.to_axis_angle()
358 obj
.rotation_axis_angle
= (rotation_axis_angle
[1],
359 *rotation_axis_angle
[0])
362 def calc_sunrise_set_UTC(rise
, jd
, latitude
, longitude
):
363 t
= calc_time_julian_cent(jd
)
364 eq_time
= calc_equation_of_time(t
)
365 solar_dec
= calc_sun_declination(t
)
366 hour_angle
= calc_hour_angle_sunrise(latitude
, solar_dec
)
368 hour_angle
= -hour_angle
369 delta
= longitude
+ degrees(hour_angle
)
370 time_UTC
= 720 - (4.0 * delta
) - eq_time
374 def calc_sun_declination(t
):
375 e
= radians(obliquity_correction(t
))
376 L
= apparent_longitude_of_sun(t
)
377 solar_dec
= sun_declination(e
, L
)
381 def calc_hour_angle_sunrise(lat
, solar_dec
):
382 lat_rad
= radians(lat
)
383 HAarg
= (cos(radians(90.833)) /
384 (cos(lat_rad
) * cos(solar_dec
))
385 - tan(lat_rad
) * tan(solar_dec
))
394 # def calc_solar_noon(jd, longitude, timezone, dst):
395 # t = calc_time_julian_cent(jd - longitude / 360.0)
396 # eq_time = calc_equation_of_time(t)
397 # noon_offset = 720.0 - (longitude * 4.0) - eq_time
398 # newt = calc_time_julian_cent(jd + noon_offset / 1440.0)
399 # eq_time = calc_equation_of_time(newt)
401 # nv = 780.0 if dst else 720.0
402 # noon_local = (nv- (longitude * 4.0) - eq_time + (timezone * 60.0)) % 1440
403 # sun.solar_noon.time = noon_local / 60.0
406 def calc_sunrise_sunset(rise
):
409 jd
= get_julian_day(sun
.year
, sun
.month
, sun
.day
)
410 time_UTC
= calc_sunrise_set_UTC(rise
, jd
, sun
.latitude
, sun
.longitude
)
411 new_time_UTC
= calc_sunrise_set_UTC(rise
, jd
+ time_UTC
/ 1440.0,
412 sun
.latitude
, sun
.longitude
)
413 time_local
= new_time_UTC
+ (-zone
* 60.0)
414 tl
= time_local
/ 60.0
415 if sun
.use_daylight_savings
:
417 tl
= time_local
/ 60.0
425 def julian_time_from_y2k(utc_time
, year
, month
, day
):
427 Get the elapsed julian time since 1/1/2000 12:00 gmt
428 Y2k epoch (1/1/2000 12:00 gmt) is Julian day 2451545.0
430 century
= 36525.0 # Days in Julian Century
431 epoch
= 2451545.0 # Julian Day for 1/1/2000 12:00 gmt
432 jd
= get_julian_day(year
, month
, day
)
433 return ((jd
+ (utc_time
/ 24)) - epoch
) / century
436 def get_julian_day(year
, month
, day
):
440 A
= floor(year
/ 100)
441 B
= 2 - A
+ floor(A
/ 4.0)
442 jd
= (floor((365.25 * (year
+ 4716.0))) +
443 floor(30.6001 * (month
+ 1)) + day
+ B
- 1524.5)
447 def calc_time_julian_cent(jd
):
448 t
= (jd
- 2451545.0) / 36525.0
452 def sun_declination(e
, L
):
453 return (asin(sin(e
) * sin(L
)))
456 def calc_equation_of_time(t
):
457 epsilon
= obliquity_correction(t
)
458 ml
= radians(mean_longitude_sun(t
))
459 e
= eccentricity_earth_orbit(t
)
460 m
= radians(mean_anomaly_sun(t
))
461 y
= tan(radians(epsilon
) / 2.0)
463 sin2ml
= sin(2.0 * ml
)
464 cos2ml
= cos(2.0 * ml
)
465 sin4ml
= sin(4.0 * ml
)
468 etime
= (y
* sin2ml
- 2.0 * e
* sinm
+ 4.0 * e
* y
*
469 sinm
* cos2ml
- 0.5 * y
** 2 * sin4ml
- 1.25 * e
** 2 * sin2m
)
470 return (degrees(etime
) * 4)
473 def obliquity_correction(t
):
474 ec
= obliquity_of_ecliptic(t
)
475 omega
= 125.04 - 1934.136 * t
476 return (ec
+ 0.00256 * cos(radians(omega
)))
479 def obliquity_of_ecliptic(t
):
480 return ((23.0 + 26.0 / 60 + (21.4480 - 46.8150) / 3600 * t
-
481 (0.00059 / 3600) * t
**2 + (0.001813 / 3600) * t
**3))
484 def true_longitude_of_sun(t
):
485 return (mean_longitude_sun(t
) + equation_of_sun_center(t
))
488 def calc_sun_apparent_long(t
):
489 o
= true_longitude_of_sun(t
)
490 omega
= 125.04 - 1934.136 * t
491 lamb
= o
- 0.00569 - 0.00478 * sin(radians(omega
))
495 def apparent_longitude_of_sun(t
):
496 return (radians(true_longitude_of_sun(t
) - 0.00569 - 0.00478 *
497 sin(radians(125.04 - 1934.136 * t
))))
500 def mean_longitude_sun(t
):
501 return (280.46646 + 36000.76983 * t
+ 0.0003032 * t
**2) % 360
504 def equation_of_sun_center(t
):
505 m
= radians(mean_anomaly_sun(t
))
506 c
= ((1.914602 - 0.004817 * t
- 0.000014 * t
**2) * sin(m
) +
507 (0.019993 - 0.000101 * t
) * sin(m
* 2) +
508 0.000289 * sin(m
* 3))
512 def mean_anomaly_sun(t
):
513 return (357.52911 + t
* (35999.05029 - 0.0001537 * t
))
516 def eccentricity_earth_orbit(t
):
517 return (0.016708634 - 0.000042037 * t
- 0.0000001267 * t
** 2)
520 def calc_surface(context
):
522 sun_props
= context
.scene
.sun_pos_properties
523 zone
= -sun_props
.UTC_zone
525 def get_surface_coordinates(time
, month
):
526 azimuth
, elevation
= get_sun_coordinates(
527 time
, sun_props
.latitude
, sun_props
.longitude
,
528 zone
, month
, 1, sun_props
.year
)
529 sun_vector
= get_sun_vector(azimuth
, elevation
) * sun_props
.sun_distance
530 sun_vector
.z
= max(0, sun_vector
.z
)
533 for month
in range(1, 7):
534 for time
in range(24):
535 coords
.append(get_surface_coordinates(time
, month
))
536 coords
.append(get_surface_coordinates(time
+ 1, month
))
537 coords
.append(get_surface_coordinates(time
, month
+ 1))
539 coords
.append(get_surface_coordinates(time
, month
+ 1))
540 coords
.append(get_surface_coordinates(time
+ 1, month
+ 1))
541 coords
.append(get_surface_coordinates(time
+ 1, month
))
545 def calc_analemma(context
, h
):
547 sun_props
= context
.scene
.sun_pos_properties
548 zone
= -sun_props
.UTC_zone
549 for day_of_year
in range(1, 367, 5):
550 day
, month
= day_of_year_to_month_day(sun_props
.year
, day_of_year
)
551 azimuth
, elevation
= get_sun_coordinates(
552 h
, sun_props
.latitude
, sun_props
.longitude
,
553 zone
, month
, day
, sun_props
.year
)
554 sun_vector
= get_sun_vector(azimuth
, elevation
) * sun_props
.sun_distance
556 vertices
.append(sun_vector
)