sun_position: move to release: T69936
This commit is contained in:
parent
c5f0bbde29
commit
efbc5e5db7
|
@ -0,0 +1,85 @@
|
|||
### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# The sun positioning algorithms are based on the National Oceanic
|
||||
# and Atmospheric Administration's (NOAA) Solar Position Calculator
|
||||
# which rely on calculations of Jean Meeus' book "Astronomical Algorithms."
|
||||
# Use of NOAA data and products are in the public domain and may be used
|
||||
# freely by the public as outlined in their policies at
|
||||
# www.nws.noaa.gov/disclaimer.php
|
||||
# --------------------------------------------------------------------------
|
||||
# The geo parser script is by Maximilian Högner, released
|
||||
# under the GNU GPL license:
|
||||
# http://hoegners.de/Maxi/geo/
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
bl_info = {
|
||||
"name": "Sun Position",
|
||||
"author": "Michael Martin",
|
||||
"version": (3, 1, 0),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "World > Sun Position",
|
||||
"description": "Show sun position with objects and/or sky texture",
|
||||
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
||||
"Scripts/3D_interaction/Sun_Position",
|
||||
"tracker_url": "https://projects.blender.org/tracker/"
|
||||
"index.php?func=detail&aid=29714",
|
||||
"category": "Lighting"}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
importlib.reload(properties)
|
||||
importlib.reload(ui_sun)
|
||||
importlib.reload(hdr)
|
||||
|
||||
else:
|
||||
from . import properties, ui_sun, hdr
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(properties.SunPosProperties)
|
||||
bpy.types.Scene.sun_pos_properties = (
|
||||
bpy.props.PointerProperty(type=properties.SunPosProperties,
|
||||
name="Sun Position",
|
||||
description="Sun Position Settings"))
|
||||
bpy.utils.register_class(properties.SunPosAddonPreferences)
|
||||
bpy.utils.register_class(ui_sun.SUNPOS_OT_AddPreset)
|
||||
bpy.utils.register_class(ui_sun.SUNPOS_OT_DefaultPresets)
|
||||
bpy.utils.register_class(ui_sun.SUNPOS_MT_Presets)
|
||||
bpy.utils.register_class(ui_sun.SUNPOS_PT_Panel)
|
||||
bpy.utils.register_class(hdr.SUNPOS_OT_ShowHdr)
|
||||
|
||||
bpy.app.handlers.frame_change_post.append(sun_calc.sun_handler)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(hdr.SUNPOS_OT_ShowHdr)
|
||||
bpy.utils.unregister_class(ui_sun.SUNPOS_PT_Panel)
|
||||
bpy.utils.unregister_class(ui_sun.SUNPOS_MT_Presets)
|
||||
bpy.utils.unregister_class(ui_sun.SUNPOS_OT_DefaultPresets)
|
||||
bpy.utils.unregister_class(ui_sun.SUNPOS_OT_AddPreset)
|
||||
bpy.utils.unregister_class(properties.SunPosAddonPreferences)
|
||||
del bpy.types.Scene.sun_pos_properties
|
||||
bpy.utils.unregister_class(properties.SunPosProperties)
|
||||
|
||||
bpy.app.handlers.frame_change_post.remove(sun_calc.sun_handler)
|
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# geo.py is a python module with no dependencies on extra packages,
|
||||
# providing some convenience functions for working with geographic
|
||||
# coordinates
|
||||
#
|
||||
# Copyright (C) 2010 Maximilian Hoegner <hp.maxi@hoegners.de>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
### Part one - Functions for dealing with points on a sphere ###
|
||||
|
||||
### Part two - A tolerant parser for position strings ###
|
||||
import re
|
||||
|
||||
|
||||
class Parser:
|
||||
""" A parser class using regular expressions. """
|
||||
|
||||
def __init__(self):
|
||||
self.patterns = {}
|
||||
self.raw_patterns = {}
|
||||
self.virtual = {}
|
||||
|
||||
def add(self, name, pattern, virtual=False):
|
||||
""" Adds a new named pattern (regular expression) that can reference previously added patterns by %(pattern_name)s.
|
||||
Virtual patterns can be used to make expressions more compact but don't show up in the parse tree. """
|
||||
self.raw_patterns[name] = "(?:" + pattern + ")"
|
||||
self.virtual[name] = virtual
|
||||
|
||||
try:
|
||||
self.patterns[name] = ("(?:" + pattern + ")") % self.patterns
|
||||
except KeyError as e:
|
||||
raise (Exception, "Unknown pattern name: %s" % str(e))
|
||||
|
||||
def parse(self, pattern_name, text):
|
||||
""" Parses 'text' with pattern 'pattern_name' and returns parse tree """
|
||||
|
||||
# build pattern with subgroups
|
||||
sub_dict = {}
|
||||
subpattern_names = []
|
||||
for s in re.finditer("%\(.*?\)s", self.raw_patterns[pattern_name]):
|
||||
subpattern_name = s.group()[2:-2]
|
||||
if not self.virtual[subpattern_name]:
|
||||
sub_dict[subpattern_name] = "(" + self.patterns[
|
||||
subpattern_name] + ")"
|
||||
subpattern_names.append(subpattern_name)
|
||||
else:
|
||||
sub_dict[subpattern_name] = self.patterns[subpattern_name]
|
||||
|
||||
pattern = "^" + (self.raw_patterns[pattern_name] % sub_dict) + "$"
|
||||
|
||||
# do matching
|
||||
m = re.match(pattern, text)
|
||||
|
||||
if m == None:
|
||||
return None
|
||||
|
||||
# build tree recursively by parsing subgroups
|
||||
tree = {"TEXT": text}
|
||||
|
||||
for i in range(len(subpattern_names)):
|
||||
text_part = m.group(i + 1)
|
||||
if not text_part == None:
|
||||
subpattern = subpattern_names[i]
|
||||
tree[subpattern] = self.parse(subpattern, text_part)
|
||||
|
||||
return tree
|
||||
|
||||
|
||||
position_parser = Parser()
|
||||
position_parser.add("direction_ns", r"[NSns]")
|
||||
position_parser.add("direction_ew", r"[EOWeow]")
|
||||
position_parser.add("decimal_separator", r"[\.,]", True)
|
||||
position_parser.add("sign", r"[+-]")
|
||||
|
||||
position_parser.add("nmea_style_degrees", r"[0-9]{2,}")
|
||||
position_parser.add("nmea_style_minutes",
|
||||
r"[0-9]{2}(?:%(decimal_separator)s[0-9]*)?")
|
||||
position_parser.add(
|
||||
"nmea_style", r"%(sign)s?\s*%(nmea_style_degrees)s%(nmea_style_minutes)s")
|
||||
|
||||
position_parser.add(
|
||||
"number",
|
||||
r"[0-9]+(?:%(decimal_separator)s[0-9]*)?|%(decimal_separator)s[0-9]+")
|
||||
|
||||
position_parser.add("plain_degrees", r"(?:%(sign)s\s*)?%(number)s")
|
||||
|
||||
position_parser.add("degree_symbol", r"°", True)
|
||||
position_parser.add("minutes_symbol", r"'|′|`|´", True)
|
||||
position_parser.add("seconds_symbol",
|
||||
r"%(minutes_symbol)s%(minutes_symbol)s|″|\"",
|
||||
True)
|
||||
position_parser.add("degrees", r"%(number)s\s*%(degree_symbol)s")
|
||||
position_parser.add("minutes", r"%(number)s\s*%(minutes_symbol)s")
|
||||
position_parser.add("seconds", r"%(number)s\s*%(seconds_symbol)s")
|
||||
position_parser.add(
|
||||
"degree_coordinates",
|
||||
"(?:%(sign)s\s*)?%(degrees)s(?:[+\s]*%(minutes)s)?(?:[+\s]*%(seconds)s)?|(?:%(sign)s\s*)%(minutes)s(?:[+\s]*%(seconds)s)?|(?:%(sign)s\s*)%(seconds)s"
|
||||
)
|
||||
|
||||
position_parser.add(
|
||||
"coordinates_ns",
|
||||
r"%(nmea_style)s|%(plain_degrees)s|%(degree_coordinates)s")
|
||||
position_parser.add(
|
||||
"coordinates_ew",
|
||||
r"%(nmea_style)s|%(plain_degrees)s|%(degree_coordinates)s")
|
||||
|
||||
position_parser.add(
|
||||
"position", """\
|
||||
\s*%(direction_ns)s\s*%(coordinates_ns)s[,;\s]*%(direction_ew)s\s*%(coordinates_ew)s\s*|\
|
||||
\s*%(direction_ew)s\s*%(coordinates_ew)s[,;\s]*%(direction_ns)s\s*%(coordinates_ns)s\s*|\
|
||||
\s*%(coordinates_ns)s\s*%(direction_ns)s[,;\s]*%(coordinates_ew)s\s*%(direction_ew)s\s*|\
|
||||
\s*%(coordinates_ew)s\s*%(direction_ew)s[,;\s]*%(coordinates_ns)s\s*%(direction_ns)s\s*|\
|
||||
\s*%(coordinates_ns)s[,;\s]+%(coordinates_ew)s\s*\
|
||||
""")
|
||||
|
||||
|
||||
def get_number(b):
|
||||
""" Takes appropriate branch of parse tree and returns float. """
|
||||
s = b["TEXT"].replace(",", ".")
|
||||
return float(s)
|
||||
|
||||
|
||||
def get_coordinate(b):
|
||||
""" Takes appropriate branch of the parse tree and returns degrees as a float. """
|
||||
|
||||
r = 0.
|
||||
|
||||
if b.get("nmea_style"):
|
||||
if b["nmea_style"].get("nmea_style_degrees"):
|
||||
r += get_number(b["nmea_style"]["nmea_style_degrees"])
|
||||
if b["nmea_style"].get("nmea_style_minutes"):
|
||||
r += get_number(b["nmea_style"]["nmea_style_minutes"]) / 60.
|
||||
if b["nmea_style"].get(
|
||||
"sign") and b["nmea_style"]["sign"]["TEXT"] == "-":
|
||||
r *= -1.
|
||||
elif b.get("plain_degrees"):
|
||||
r += get_number(b["plain_degrees"]["number"])
|
||||
if b["plain_degrees"].get(
|
||||
"sign") and b["plain_degrees"]["sign"]["TEXT"] == "-":
|
||||
r *= -1.
|
||||
elif b.get("degree_coordinates"):
|
||||
if b["degree_coordinates"].get("degrees"):
|
||||
r += get_number(b["degree_coordinates"]["degrees"]["number"])
|
||||
if b["degree_coordinates"].get("minutes"):
|
||||
r += get_number(b["degree_coordinates"]["minutes"]["number"]) / 60.
|
||||
if b["degree_coordinates"].get("seconds"):
|
||||
r += get_number(
|
||||
b["degree_coordinates"]["seconds"]["number"]) / 3600.
|
||||
if b["degree_coordinates"].get(
|
||||
"sign") and b["degree_coordinates"]["sign"]["TEXT"] == "-":
|
||||
r *= -1.
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def parse_position(s):
|
||||
""" Takes a (utf8-encoded) string describing a position and returns a tuple of floats for latitude and longitude in degrees.
|
||||
Tries to be as tolerant as possible with input. Returns None if parsing doesn't succeed. """
|
||||
|
||||
parse_tree = position_parser.parse("position", s)
|
||||
if parse_tree == None: return None
|
||||
|
||||
lat_sign = +1.
|
||||
if parse_tree.get(
|
||||
"direction_ns") and parse_tree["direction_ns"]["TEXT"] in ("S",
|
||||
"s"):
|
||||
lat_sign = -1.
|
||||
|
||||
lon_sign = +1.
|
||||
if parse_tree.get(
|
||||
"direction_ew") and parse_tree["direction_ew"]["TEXT"] in ("W",
|
||||
"w"):
|
||||
lon_sign = -1.
|
||||
|
||||
lat = lat_sign * get_coordinate(parse_tree["coordinates_ns"])
|
||||
lon = lon_sign * get_coordinate(parse_tree["coordinates_ew"])
|
||||
|
||||
return lat, lon
|
|
@ -0,0 +1,303 @@
|
|||
### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
import bgl
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from mathutils import Vector
|
||||
from math import sqrt, pi, atan2, asin
|
||||
|
||||
|
||||
vertex_shader = '''
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
|
||||
/* Keep in sync with intern/opencolorio/gpu_shader_display_transform_vertex.glsl */
|
||||
in vec2 texCoord;
|
||||
in vec2 pos;
|
||||
out vec2 texCoord_interp;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f);
|
||||
gl_Position.z = 1.0;
|
||||
texCoord_interp = texCoord;
|
||||
}'''
|
||||
|
||||
fragment_shader = '''
|
||||
in vec2 texCoord_interp;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D image;
|
||||
uniform float exposure;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = texture(image, texCoord_interp) * exposure;
|
||||
}'''
|
||||
|
||||
# shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
|
||||
|
||||
|
||||
def draw_callback_px(self, context):
|
||||
nt = context.scene.world.node_tree.nodes
|
||||
env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
|
||||
image = env_tex_node.image
|
||||
|
||||
if self.area != context.area:
|
||||
return
|
||||
|
||||
if image.gl_load():
|
||||
raise Exception()
|
||||
|
||||
bottom = 0
|
||||
top = context.area.height
|
||||
right = context.area.width
|
||||
|
||||
position = Vector((right, top)) / 2 + self.offset
|
||||
scale = Vector((context.area.width, context.area.width / 2)) * self.scale
|
||||
|
||||
shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
|
||||
|
||||
coords = ((-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5))
|
||||
uv_coords = ((0, 0), (1, 0), (1, 1), (0, 1))
|
||||
batch = batch_for_shader(shader, 'TRI_FAN',
|
||||
{"pos" : coords,
|
||||
"texCoord" : uv_coords})
|
||||
|
||||
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode)
|
||||
|
||||
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.translate(position)
|
||||
gpu.matrix.scale(scale)
|
||||
|
||||
shader.bind()
|
||||
shader.uniform_int("image", 0)
|
||||
shader.uniform_float("exposure", self.exposure)
|
||||
batch.draw(shader)
|
||||
|
||||
# Crosshair
|
||||
# vertical
|
||||
coords = ((self.mouse_position[0], bottom), (self.mouse_position[0], top))
|
||||
colors = ((1,)*4,)*2
|
||||
shader = gpu.shader.from_builtin('2D_FLAT_COLOR')
|
||||
batch = batch_for_shader(shader, 'LINES',
|
||||
{"pos": coords, "color": colors})
|
||||
shader.bind()
|
||||
batch.draw(shader)
|
||||
|
||||
# horizontal
|
||||
if bottom <= self.mouse_position[1] <= top:
|
||||
coords = ((0, self.mouse_position[1]), (context.area.width, self.mouse_position[1]))
|
||||
batch = batch_for_shader(shader, 'LINES',
|
||||
{"pos": coords, "color": colors})
|
||||
shader.bind()
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
class SUNPOS_OT_ShowHdr(bpy.types.Operator):
|
||||
"""Tooltip"""
|
||||
bl_idname = "world.sunpos_show_hdr"
|
||||
bl_label = "Sync Sun to Texture"
|
||||
|
||||
exposure = 1.0
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
sun_props = context.scene.sun_pos_properties
|
||||
return sun_props.hdr_texture and sun_props.sun_object is not None
|
||||
|
||||
def update(self, context, event):
|
||||
sun_props = context.scene.sun_pos_properties
|
||||
mouse_position_abs = Vector((event.mouse_x, event.mouse_y))
|
||||
|
||||
# Get current area
|
||||
for area in context.screen.areas:
|
||||
# Compare absolute mouse position to area bounds
|
||||
if (area.x < mouse_position_abs.x < area.x + area.width
|
||||
and area.y < mouse_position_abs.y < area.y + area.height):
|
||||
self.area = area
|
||||
if area.type == 'VIEW_3D':
|
||||
# Redraw all areas
|
||||
area.tag_redraw()
|
||||
|
||||
if self.area.type == 'VIEW_3D':
|
||||
self.top = self.area.height
|
||||
self.right = self.area.width
|
||||
|
||||
nt = context.scene.world.node_tree.nodes
|
||||
env_tex = nt.get(sun_props.hdr_texture)
|
||||
|
||||
# Mouse position relative to window
|
||||
self.mouse_position = Vector((mouse_position_abs.x - self.area.x,
|
||||
mouse_position_abs.y - self.area.y))
|
||||
|
||||
self.selected_point = (self.mouse_position - self.offset - Vector((self.right, self.top))/2) / self.scale
|
||||
u = self.selected_point.x / self.area.width + 0.5
|
||||
v = (self.selected_point.y) / (self.area.width / 2) + 0.5
|
||||
|
||||
# Set elevation and azimuth from selected point
|
||||
if env_tex.projection == 'EQUIRECTANGULAR':
|
||||
el = v * pi - pi/2
|
||||
az = u * pi*2 - pi/2 + env_tex.texture_mapping.rotation.z
|
||||
|
||||
# Clamp elevation
|
||||
el = max(el, -pi/2)
|
||||
el = min(el, pi/2)
|
||||
|
||||
sun_props.hdr_elevation = el
|
||||
sun_props.hdr_azimuth = az
|
||||
elif env_tex.projection == 'MIRROR_BALL':
|
||||
# Formula from intern/cycles/kernel/kernel_projection.h
|
||||
# Point on sphere
|
||||
dir = Vector()
|
||||
|
||||
# Normalize to -1, 1
|
||||
dir.x = 2.0 * u - 1.0
|
||||
dir.z = 2.0 * v - 1.0
|
||||
|
||||
# Outside bounds
|
||||
if (dir.x * dir.x + dir.z * dir.z > 1.0):
|
||||
dir = Vector()
|
||||
|
||||
else:
|
||||
dir.y = -sqrt(max(1.0 - dir.x * dir.x - dir.z * dir.z, 0.0))
|
||||
|
||||
# Reflection
|
||||
i = Vector((0.0, -1.0, 0.0))
|
||||
|
||||
dir = 2.0 * dir.dot(i) * dir - i
|
||||
|
||||
# Convert vector to euler
|
||||
el = asin(dir.z)
|
||||
az = atan2(dir.x, dir.y) + env_tex.texture_mapping.rotation.z
|
||||
sun_props.hdr_elevation = el
|
||||
sun_props.hdr_azimuth = az
|
||||
|
||||
else:
|
||||
self.report({'ERROR'}, 'Unknown projection')
|
||||
return {'CANCELLED'}
|
||||
|
||||
def pan(self, context, event):
|
||||
self.offset += Vector((event.mouse_region_x - self.mouse_prev_x,
|
||||
event.mouse_region_y - self.mouse_prev_y))
|
||||
self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
|
||||
|
||||
def modal(self, context, event):
|
||||
self.area.tag_redraw()
|
||||
if event.type == 'MOUSEMOVE':
|
||||
if self.is_panning:
|
||||
self.pan(context, event)
|
||||
self.update(context, event)
|
||||
|
||||
# Confirm
|
||||
elif event.type in {'LEFTMOUSE', 'RET'}:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
# Bind the environment texture to the sun
|
||||
context.scene.sun_pos_properties.bind_to_sun = True
|
||||
context.workspace.status_text_set(None)
|
||||
return {'FINISHED'}
|
||||
|
||||
# Cancel
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
# Reset previous values
|
||||
context.scene.sun_pos_properties.hdr_elevation = self.initial_elevation
|
||||
context.scene.sun_pos_properties.hdr_azimuth = self.initial_azimuth
|
||||
context.workspace.status_text_set(None)
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Set exposure or zoom
|
||||
elif event.type == 'WHEELUPMOUSE':
|
||||
# Exposure
|
||||
if event.ctrl:
|
||||
self.exposure *= 1.1
|
||||
# Zoom
|
||||
else:
|
||||
self.scale *= 1.1
|
||||
self.offset -= (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 10.0
|
||||
self.update(context, event)
|
||||
elif event.type == 'WHEELDOWNMOUSE':
|
||||
# Exposure
|
||||
if event.ctrl:
|
||||
self.exposure /= 1.1
|
||||
# Zoom
|
||||
else:
|
||||
self.scale /= 1.1
|
||||
self.offset += (self.mouse_position - (Vector((self.right, self.top)) / 2 + self.offset)) / 11.0
|
||||
self.update(context, event)
|
||||
|
||||
# Toggle pan
|
||||
elif event.type == 'MIDDLEMOUSE':
|
||||
if event.value == 'PRESS':
|
||||
self.mouse_prev_x, self.mouse_prev_y = event.mouse_region_x, event.mouse_region_y
|
||||
self.is_panning = True
|
||||
elif event.value == 'RELEASE':
|
||||
self.is_panning = False
|
||||
|
||||
else:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.is_panning = False
|
||||
self.mouse_prev_x = 0.0
|
||||
self.mouse_prev_y = 0.0
|
||||
self.offset = Vector((0.0, 0.0))
|
||||
self.scale = 1.0
|
||||
|
||||
# Get at least one 3D View
|
||||
area_3d = None
|
||||
for a in context.screen.areas:
|
||||
if a.type == 'VIEW_3D':
|
||||
area_3d = a
|
||||
break
|
||||
|
||||
if area_3d is None:
|
||||
self.report({'ERROR'}, 'Could not find 3D View')
|
||||
return {'CANCELLED'}
|
||||
|
||||
nt = context.scene.world.node_tree.nodes
|
||||
env_tex_node = nt.get(context.scene.sun_pos_properties.hdr_texture)
|
||||
if env_tex_node.type != "TEX_ENVIRONMENT":
|
||||
self.report({'ERROR'}, 'Please select an Environment Texture node')
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.area = context.area
|
||||
|
||||
self.mouse_position = event.mouse_region_x, event.mouse_region_y
|
||||
|
||||
self.initial_elevation = context.scene.sun_pos_properties.hdr_elevation
|
||||
self.initial_azimuth = context.scene.sun_pos_properties.hdr_azimuth
|
||||
|
||||
context.workspace.status_text_set("Enter/LMB: confirm, Esc/RMB: cancel, MMB: pan, mouse wheel: zoom, Ctrl + mouse wheel: set exposure")
|
||||
|
||||
self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px,
|
||||
(self, context), 'WINDOW', 'POST_PIXEL')
|
||||
context.window_manager.modal_handler_add(self)
|
||||
|
||||
return {'RUNNING_MODAL'}
|
|
@ -0,0 +1,110 @@
|
|||
### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
import bgl
|
||||
import math
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
if bpy.app.background: # ignore north line in background mode
|
||||
def north_update(self, context):
|
||||
pass
|
||||
else:
|
||||
vertex_shader = '''
|
||||
uniform mat4 u_ViewProjectionMatrix;
|
||||
|
||||
in vec3 position;
|
||||
|
||||
flat out vec2 v_StartPos;
|
||||
out vec4 v_VertPos;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 pos = u_ViewProjectionMatrix * vec4(position, 1.0f);
|
||||
gl_Position = pos;
|
||||
v_StartPos = (pos / pos.w).xy;
|
||||
v_VertPos = pos;
|
||||
}
|
||||
'''
|
||||
|
||||
fragment_shader = '''
|
||||
uniform vec4 u_Color;
|
||||
|
||||
flat in vec2 v_StartPos;
|
||||
in vec4 v_VertPos;
|
||||
|
||||
uniform vec2 u_Resolution;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 vertPos_2d = v_VertPos / v_VertPos.w;
|
||||
vec2 dir = (vertPos_2d.xy - v_StartPos.xy) * u_Resolution;
|
||||
float dist = length(dir);
|
||||
|
||||
if (step(sin(dist / 5.0f), 0.0) == 1) discard;
|
||||
|
||||
gl_FragColor = u_Color;
|
||||
}
|
||||
'''
|
||||
|
||||
shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
|
||||
|
||||
def draw_north_callback():
|
||||
# ------------------------------------------------------------------
|
||||
# Set up the compass needle using the current north offset angle
|
||||
# less 90 degrees. This forces the unit circle to begin at the
|
||||
# 12 O'clock instead of 3 O'clock position.
|
||||
# ------------------------------------------------------------------
|
||||
sun_props = bpy.context.scene.sun_pos_properties
|
||||
|
||||
color = (0.2, 0.6, 1.0, 0.7)
|
||||
radius = 100
|
||||
angle = -(sun_props.north_offset - math.pi / 2)
|
||||
x = math.cos(angle) * radius
|
||||
y = math.sin(angle) * radius
|
||||
|
||||
coords = Vector((x, y, 0)), Vector((0, 0, 0)) # Start & end of needle
|
||||
|
||||
batch = batch_for_shader(
|
||||
shader, 'LINE_STRIP',
|
||||
{"position": coords},
|
||||
)
|
||||
shader.bind()
|
||||
|
||||
matrix = bpy.context.region_data.perspective_matrix
|
||||
shader.uniform_float("u_ViewProjectionMatrix", matrix)
|
||||
shader.uniform_float("u_Resolution", (bpy.context.region.width, bpy.context.region.height))
|
||||
shader.uniform_float("u_Color", color)
|
||||
bgl.glLineWidth(2.0)
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
_handle = None
|
||||
|
||||
|
||||
def north_update(self, context):
|
||||
global _handle
|
||||
if self.show_north and _handle is None:
|
||||
_handle = bpy.types.SpaceView3D.draw_handler_add(draw_north_callback, (), 'WINDOW', 'POST_VIEW')
|
||||
elif _handle is not None:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(_handle, 'WINDOW')
|
||||
_handle = None
|
||||
context.area.tag_redraw()
|
|
@ -0,0 +1,276 @@
|
|||
### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
from bpy.types import AddonPreferences, PropertyGroup
|
||||
from bpy.props import (StringProperty, EnumProperty, IntProperty,
|
||||
FloatProperty, BoolProperty, PointerProperty)
|
||||
|
||||
from .sun_calc import sun_update, parse_coordinates
|
||||
from .north import north_update
|
||||
|
||||
from math import pi
|
||||
from datetime import datetime
|
||||
TODAY = datetime.today()
|
||||
|
||||
############################################################################
|
||||
# Sun panel properties
|
||||
############################################################################
|
||||
|
||||
|
||||
class SunPosProperties(PropertyGroup):
|
||||
usage_mode: EnumProperty(
|
||||
name="Usage mode",
|
||||
description="Operate in normal mode or environment texture mode",
|
||||
items=(
|
||||
('NORMAL', "Normal", ""),
|
||||
('HDR', "Sun + HDR texture", ""),
|
||||
),
|
||||
default='NORMAL',
|
||||
update=sun_update)
|
||||
|
||||
use_daylight_savings: BoolProperty(
|
||||
description="Daylight savings time adds 1 hour to standard time",
|
||||
default=False,
|
||||
update=sun_update)
|
||||
|
||||
use_refraction: BoolProperty(
|
||||
description="Show apparent sun position due to refraction",
|
||||
default=True,
|
||||
update=sun_update)
|
||||
|
||||
show_north: BoolProperty(
|
||||
description="Draw line pointing north",
|
||||
default=False,
|
||||
update=north_update)
|
||||
|
||||
north_offset: FloatProperty(
|
||||
name="North Offset",
|
||||
description="Rotate the scene to choose North direction",
|
||||
unit="ROTATION",
|
||||
soft_min=-pi, soft_max=pi, step=10.0, default=0.0,
|
||||
update=sun_update)
|
||||
|
||||
latitude: FloatProperty(
|
||||
name="Latitude",
|
||||
description="Latitude: (+) Northern (-) Southern",
|
||||
soft_min=-90.0, soft_max=90.0,
|
||||
step=5, precision=3,
|
||||
default=0.0,
|
||||
update=sun_update)
|
||||
|
||||
longitude: FloatProperty(
|
||||
name="Longitude",
|
||||
description="Longitude: (-) West of Greenwich (+) East of Greenwich",
|
||||
soft_min=-180.0, soft_max=180.0,
|
||||
step=5, precision=3,
|
||||
default=0.0,
|
||||
update=sun_update)
|
||||
|
||||
co_parser: StringProperty(
|
||||
name="Enter coordinates",
|
||||
description="Enter coordinates from an online map",
|
||||
update=parse_coordinates)
|
||||
|
||||
month: IntProperty(
|
||||
name="Month",
|
||||
min=1, max=12, default=TODAY.month,
|
||||
update=sun_update)
|
||||
|
||||
day: IntProperty(
|
||||
name="Day",
|
||||
min=1, max=31, default=TODAY.day,
|
||||
update=sun_update)
|
||||
|
||||
year: IntProperty(
|
||||
name="Year",
|
||||
min=1800, max=4000, default=TODAY.year,
|
||||
update=sun_update)
|
||||
|
||||
use_day_of_year: BoolProperty(
|
||||
description="Use a single value for day of year",
|
||||
name="Use day of year",
|
||||
default=False,
|
||||
update=sun_update)
|
||||
|
||||
day_of_year: IntProperty(
|
||||
name="Day of year",
|
||||
min=1, max=366, default=1,
|
||||
update=sun_update)
|
||||
|
||||
UTC_zone: FloatProperty(
|
||||
name="UTC zone",
|
||||
description="Time zone: Difference from Greenwich, England in hours",
|
||||
precision=1,
|
||||
min=-14.0, max=13, step=50, default=0.0,
|
||||
update=sun_update)
|
||||
|
||||
time: FloatProperty(
|
||||
name="Time",
|
||||
description="Time of the day",
|
||||
precision=4,
|
||||
soft_min=0.0, soft_max=23.9999, step=1.0, default=12.0,
|
||||
update=sun_update)
|
||||
|
||||
sun_distance: FloatProperty(
|
||||
name="Distance",
|
||||
description="Distance to sun from origin",
|
||||
unit="LENGTH",
|
||||
min=0.0, soft_max=3000.0, step=10.0, default=50.0,
|
||||
update=sun_update)
|
||||
|
||||
use_sun_object: BoolProperty(
|
||||
description="Enable sun positioning of light object",
|
||||
default=False,
|
||||
update=sun_update)
|
||||
|
||||
sun_object: PointerProperty(
|
||||
type=bpy.types.Object,
|
||||
description="Sun object to set in the scene",
|
||||
poll=lambda self, obj: obj.type == 'LIGHT',
|
||||
update=sun_update)
|
||||
|
||||
use_object_collection: BoolProperty(
|
||||
description="Allow a collection of objects to be positioned",
|
||||
default=False,
|
||||
update=sun_update)
|
||||
|
||||
object_collection: PointerProperty(
|
||||
type=bpy.types.Collection,
|
||||
description="Collection of objects used for analemma",
|
||||
update=sun_update)
|
||||
|
||||
object_collection_type: EnumProperty(
|
||||
name="Display type",
|
||||
description="Show object group on ecliptic or as analemma",
|
||||
items=(
|
||||
('ECLIPTIC', "On the Ecliptic", ""),
|
||||
('ANALEMMA', "As Analemma", ""),
|
||||
),
|
||||
default='ECLIPTIC',
|
||||
update=sun_update)
|
||||
|
||||
use_sky_texture: BoolProperty(
|
||||
description="Enable use of Cycles' "
|
||||
"sky texture. World nodes must be enabled, "
|
||||
"then set color to Sky Texture",
|
||||
default=False,
|
||||
update=sun_update)
|
||||
|
||||
sky_texture: StringProperty(
|
||||
default="Sky Texture",
|
||||
name="Sky Texture",
|
||||
description="Name of sky texture to be used",
|
||||
update=sun_update)
|
||||
|
||||
hdr_texture: StringProperty(
|
||||
default="Environment Texture",
|
||||
name="Environment Texture",
|
||||
description="Name of texture to use. World nodes must be enabled "
|
||||
"and color set to Environment Texture",
|
||||
update=sun_update)
|
||||
|
||||
hdr_azimuth: FloatProperty(
|
||||
name="Rotation",
|
||||
description="Rotation angle of sun and environment texture",
|
||||
unit="ROTATION",
|
||||
step=10.0,
|
||||
default=0.0, precision=3,
|
||||
update=sun_update)
|
||||
|
||||
hdr_elevation: FloatProperty(
|
||||
name="Elevation",
|
||||
description="Elevation angle of sun",
|
||||
unit="ROTATION",
|
||||
step=10.0,
|
||||
default=0.0, precision=3,
|
||||
update=sun_update)
|
||||
|
||||
bind_to_sun: BoolProperty(
|
||||
description="If true, Environment texture moves with sun",
|
||||
default=False,
|
||||
update=sun_update)
|
||||
|
||||
time_spread: FloatProperty(
|
||||
name="Time Spread",
|
||||
description="Time period in which to spread object group",
|
||||
precision=4,
|
||||
soft_min=1.0, soft_max=24.0, step=1.0, default=23.0,
|
||||
update=sun_update)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Preference panel properties
|
||||
############################################################################
|
||||
|
||||
|
||||
class SunPosAddonPreferences(AddonPreferences):
|
||||
bl_idname = __package__
|
||||
|
||||
show_time_place: BoolProperty(
|
||||
description="Show time/place presets",
|
||||
default=False)
|
||||
|
||||
show_object_collection: BoolProperty(
|
||||
description="Use object collection",
|
||||
default=True,
|
||||
update=sun_update)
|
||||
|
||||
show_dms: BoolProperty(
|
||||
description="Show lat/long degrees, minutes, seconds labels",
|
||||
default=True)
|
||||
|
||||
show_north: BoolProperty(
|
||||
description="Show north offset choice and slider",
|
||||
default=True,
|
||||
update=sun_update)
|
||||
|
||||
show_refraction: BoolProperty(
|
||||
description="Show sun refraction choice",
|
||||
default=True,
|
||||
update=sun_update)
|
||||
|
||||
show_az_el: BoolProperty(
|
||||
description="Show azimuth and solar elevation info",
|
||||
default=True)
|
||||
|
||||
show_daylight_savings: BoolProperty(
|
||||
description="Show daylight savings time choice",
|
||||
default=True,
|
||||
update=sun_update)
|
||||
|
||||
show_rise_set: BoolProperty(
|
||||
description="Show sunrise and sunset",
|
||||
default=True)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
box = layout.box()
|
||||
col = box.column()
|
||||
|
||||
col.label(text="Show or use:")
|
||||
flow = col.grid_flow(columns=0, even_columns=True, even_rows=False, align=False)
|
||||
flow.prop(self, "show_time_place", text="Time/place presets")
|
||||
flow.prop(self, "show_object_collection", text="Use collection")
|
||||
flow.prop(self, "show_dms", text="D° M' S\"")
|
||||
flow.prop(self, "show_north", text="North offset")
|
||||
flow.prop(self, "show_refraction", text="Refraction")
|
||||
flow.prop(self, "show_az_el", text="Azimuth, elevation")
|
||||
flow.prop(self, "show_daylight_savings", text="Daylight savings time")
|
||||
flow.prop(self, "show_rise_set", text="Sunrise, sunset")
|
|
@ -0,0 +1,589 @@
|
|||
### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
from mathutils import Euler
|
||||
import math
|
||||
from math import degrees, radians, pi
|
||||
import datetime
|
||||
from .geo import parse_position
|
||||
|
||||
|
||||
############################################################################
|
||||
#
|
||||
# SunClass is used for storing intermediate sun calculations.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
class SunClass:
|
||||
|
||||
class TazEl:
|
||||
time = 0.0
|
||||
azimuth = 0.0
|
||||
elevation = 0.0
|
||||
|
||||
class CLAMP:
|
||||
elevation = 0.0
|
||||
azimuth = 0.0
|
||||
az_start_sun = 0.0
|
||||
az_start_env = 0.0
|
||||
|
||||
sunrise = TazEl()
|
||||
sunset = TazEl()
|
||||
solar_noon = TazEl()
|
||||
rise_set_ok = False
|
||||
|
||||
bind = CLAMP()
|
||||
bind_to_sun = False
|
||||
|
||||
latitude = 0.0
|
||||
longitude = 0.0
|
||||
elevation = 0.0
|
||||
azimuth = 0.0
|
||||
|
||||
month = 0
|
||||
day = 0
|
||||
year = 0
|
||||
day_of_year = 0
|
||||
time = 0.0
|
||||
|
||||
UTC_zone = 0
|
||||
sun_distance = 0.0
|
||||
use_daylight_savings = False
|
||||
|
||||
|
||||
sun = SunClass()
|
||||
|
||||
|
||||
def sun_update(self, context):
|
||||
update_time(context)
|
||||
move_sun(context)
|
||||
|
||||
def parse_coordinates(self, context):
|
||||
error_message = "ERROR: Could not parse coordinates"
|
||||
sun_props = context.scene.sun_pos_properties
|
||||
|
||||
if sun_props.co_parser:
|
||||
parsed_co = parse_position(sun_props.co_parser)
|
||||
|
||||
if parsed_co is not None and len(parsed_co) == 2:
|
||||
sun_props.latitude, sun_props.longitude = parsed_co
|
||||
elif sun_props.co_parser != error_message:
|
||||
sun_props.co_parser = error_message
|
||||
|
||||
# Clear prop
|
||||
if sun_props.co_parser not in {'', error_message}:
|
||||
sun_props.co_parser = ''
|
||||
|
||||
@persistent
|
||||
def sun_handler(scene):
|
||||
update_time(bpy.context)
|
||||
move_sun(bpy.context)
|
||||
|
||||
|
||||
############################################################################
|
||||
#
|
||||
# move_sun() will cycle through all the selected objects
|
||||
# and call set_sun_position and set_sun_rotations
|
||||
# to place them in the sky.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
|
||||
def move_sun(context):
|
||||
addon_prefs = context.preferences.addons[__package__].preferences
|
||||
sun_props = context.scene.sun_pos_properties
|
||||
|
||||
if sun_props.usage_mode == "HDR":
|
||||
nt = context.scene.world.node_tree.nodes
|
||||
env_tex = nt.get(sun_props.hdr_texture)
|
||||
|
||||
if sun.bind_to_sun != sun_props.bind_to_sun:
|
||||
# bind_to_sun was just toggled
|
||||
sun.bind_to_sun = sun_props.bind_to_sun
|
||||
sun.bind.az_start_sun = sun_props.hdr_azimuth
|
||||
if env_tex:
|
||||
sun.bind.az_start_env = env_tex.texture_mapping.rotation.z
|
||||
|
||||
if env_tex and sun_props.bind_to_sun:
|
||||
az = sun_props.hdr_azimuth - sun.bind.az_start_sun + sun.bind.az_start_env
|
||||
env_tex.texture_mapping.rotation.z = az
|
||||
|
||||
if sun_props.sun_object:
|
||||
sun.theta = math.pi / 2 - sun_props.hdr_elevation
|
||||
sun.phi = -sun_props.hdr_azimuth
|
||||
|
||||
obj = sun_props.sun_object
|
||||
set_sun_position(obj, sun_props.sun_distance)
|
||||
rotation_euler = Euler((sun_props.hdr_elevation - pi/2,
|
||||
0, -sun_props.hdr_azimuth))
|
||||
|
||||
set_sun_rotations(obj, rotation_euler)
|
||||
return
|
||||
|
||||
local_time = sun_props.time
|
||||
zone = -sun_props.UTC_zone
|
||||
sun.use_daylight_savings = sun_props.use_daylight_savings
|
||||
if sun.use_daylight_savings:
|
||||
zone -= 1
|
||||
|
||||
north_offset = degrees(sun_props.north_offset)
|
||||
|
||||
if addon_prefs.show_rise_set:
|
||||
calc_sunrise_sunset(rise=True)
|
||||
calc_sunrise_sunset(rise=False)
|
||||
|
||||
get_sun_position(local_time, sun_props.latitude, sun_props.longitude,
|
||||
north_offset, zone, sun_props.month, sun_props.day, sun_props.year,
|
||||
sun_props.sun_distance)
|
||||
|
||||
if sun_props.use_sky_texture and sun_props.sky_texture:
|
||||
sky_node = bpy.context.scene.world.node_tree.nodes.get(sun_props.sky_texture)
|
||||
if sky_node is not None and sky_node.type == "TEX_SKY":
|
||||
locX = math.sin(sun.phi) * math.sin(-sun.theta)
|
||||
locY = math.sin(sun.theta) * math.cos(sun.phi)
|
||||
locZ = math.cos(sun.theta)
|
||||
sky_node.texture_mapping.rotation.z = 0.0
|
||||
sky_node.sun_direction = locX, locY, locZ
|
||||
|
||||
# Sun object
|
||||
if ((sun_props.use_sun_object or sun_props.usage_mode == 'HDR')
|
||||
and sun_props.sun_object
|
||||
and sun_props.sun_object.name in context.view_layer.objects):
|
||||
obj = sun_props.sun_object
|
||||
set_sun_position(obj, sun_props.sun_distance)
|
||||
rotation_euler = Euler((math.radians(sun.elevation - 90), 0,
|
||||
math.radians(-sun.az_north)))
|
||||
set_sun_rotations(obj, rotation_euler)
|
||||
|
||||
# Sun collection
|
||||
if (addon_prefs.show_object_collection
|
||||
and sun_props.use_object_collection
|
||||
and sun_props.object_collection):
|
||||
sun_objects = sun_props.object_collection.objects
|
||||
object_count = len(sun_objects)
|
||||
if sun_props.object_collection_type == 'ECLIPTIC':
|
||||
# Ecliptic
|
||||
if object_count > 1:
|
||||
time_increment = sun_props.time_spread / (object_count - 1)
|
||||
local_time = local_time + time_increment * (object_count - 1)
|
||||
else:
|
||||
time_increment = sun_props.time_spread
|
||||
|
||||
for obj in sun_objects:
|
||||
get_sun_position(local_time, sun_props.latitude,
|
||||
sun_props.longitude, north_offset, zone,
|
||||
sun_props.month, sun_props.day,
|
||||
sun_props.year, sun_props.sun_distance)
|
||||
set_sun_position(obj, sun_props.sun_distance)
|
||||
local_time -= time_increment
|
||||
obj.rotation_euler = (
|
||||
(math.radians(sun.elevation - 90), 0,
|
||||
math.radians(-sun.az_north)))
|
||||
else:
|
||||
# Analemma
|
||||
day_increment = 365 / object_count
|
||||
day = sun_props.day_of_year + day_increment * (object_count - 1)
|
||||
for obj in sun_objects:
|
||||
dt = (datetime.date(sun_props.year, 1, 1) +
|
||||
datetime.timedelta(day - 1))
|
||||
get_sun_position(local_time, sun_props.latitude,
|
||||
sun_props.longitude, north_offset, zone,
|
||||
dt.month, dt.day, sun_props.year,
|
||||
sun_props.sun_distance)
|
||||
set_sun_position(obj, sun_props.sun_distance)
|
||||
day -= day_increment
|
||||
obj.rotation_euler = (
|
||||
(math.radians(sun.elevation - 90), 0,
|
||||
math.radians(-sun.az_north)))
|
||||
|
||||
def update_time(context):
|
||||
sun_props = context.scene.sun_pos_properties
|
||||
|
||||
if not sun_props.use_day_of_year:
|
||||
dt = datetime.date(sun_props.year, sun_props.month, sun_props.day)
|
||||
day_of_year = dt.timetuple().tm_yday
|
||||
if sun_props.day_of_year != day_of_year:
|
||||
sun_props.day_of_year = day_of_year
|
||||
sun.day = sun_props.day
|
||||
sun.month = sun_props.month
|
||||
sun.day_of_year = day_of_year
|
||||
else:
|
||||
dt = (datetime.date(sun_props.year, 1, 1) +
|
||||
datetime.timedelta(sun_props.day_of_year - 1))
|
||||
sun.day = dt.day
|
||||
sun.month = dt.month
|
||||
sun.day_of_year = sun_props.day_of_year
|
||||
if sun_props.day != dt.day:
|
||||
sun_props.day = dt.day
|
||||
if sun_props.month != dt.month:
|
||||
sun_props.month = dt.month
|
||||
sun.year = sun_props.year
|
||||
sun.longitude = sun_props.longitude
|
||||
sun.latitude = sun_props.latitude
|
||||
sun.UTC_zone = sun_props.UTC_zone
|
||||
|
||||
|
||||
def format_time(the_time, daylight_savings, longitude, UTC_zone=None):
|
||||
if UTC_zone is not None:
|
||||
if daylight_savings:
|
||||
UTC_zone += 1
|
||||
the_time -= UTC_zone
|
||||
|
||||
the_time %= 24
|
||||
|
||||
hh = int(the_time)
|
||||
mm = (the_time - int(the_time)) * 60
|
||||
ss = int((mm - int(mm)) * 60)
|
||||
|
||||
return ("%02i:%02i:%02i" % (hh, mm, ss))
|
||||
|
||||
|
||||
def format_hms(the_time):
|
||||
hh = str(int(the_time))
|
||||
min = (the_time - int(the_time)) * 60
|
||||
sec = int((min - int(min)) * 60)
|
||||
mm = "0" + str(int(min)) if min < 10 else str(int(min))
|
||||
ss = "0" + str(sec) if sec < 10 else str(sec)
|
||||
|
||||
return (hh + ":" + mm + ":" + ss)
|
||||
|
||||
|
||||
def format_lat_long(lat_long, is_latitude):
|
||||
hh = str(abs(int(lat_long)))
|
||||
min = abs((lat_long - int(lat_long)) * 60)
|
||||
sec = abs(int((min - int(min)) * 60))
|
||||
mm = "0" + str(int(min)) if min < 10 else str(int(min))
|
||||
ss = "0" + str(sec) if sec < 10 else str(sec)
|
||||
if lat_long == 0:
|
||||
coord_tag = " "
|
||||
else:
|
||||
if is_latitude:
|
||||
coord_tag = " N" if lat_long > 0 else " S"
|
||||
else:
|
||||
coord_tag = " E" if lat_long > 0 else " W"
|
||||
|
||||
return hh + "° " + mm + "' " + ss + '"' + coord_tag
|
||||
|
||||
|
||||
|
||||
|
||||
############################################################################
|
||||
#
|
||||
# Calculate the actual position of the sun based on input parameters.
|
||||
#
|
||||
# The sun positioning algorithms below are based on the National Oceanic
|
||||
# and Atmospheric Administration's (NOAA) Solar Position Calculator
|
||||
# which rely on calculations of Jean Meeus' book "Astronomical Algorithms."
|
||||
# Use of NOAA data and products are in the public domain and may be used
|
||||
# freely by the public as outlined in their policies at
|
||||
# www.nws.noaa.gov/disclaimer.php
|
||||
#
|
||||
# The calculations of this script can be verified with those of NOAA's
|
||||
# using the Azimuth and Solar Elevation displayed in the SunPos_Panel.
|
||||
# NOAA's web site is:
|
||||
# http://www.esrl.noaa.gov/gmd/grad/solcalc
|
||||
############################################################################
|
||||
|
||||
|
||||
def get_sun_position(local_time, latitude, longitude, north_offset,
|
||||
utc_zone, month, day, year, distance):
|
||||
|
||||
addon_prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
sun_props = bpy.context.scene.sun_pos_properties
|
||||
|
||||
longitude *= -1 # for internal calculations
|
||||
utc_time = local_time + utc_zone # Set Greenwich Meridian Time
|
||||
|
||||
if latitude > 89.93: # Latitude 90 and -90 gives
|
||||
latitude = radians(89.93) # erroneous results so nudge it
|
||||
elif latitude < -89.93:
|
||||
latitude = radians(-89.93)
|
||||
else:
|
||||
latitude = radians(latitude)
|
||||
|
||||
t = julian_time_from_y2k(utc_time, year, month, day)
|
||||
|
||||
e = radians(obliquity_correction(t))
|
||||
L = apparent_longitude_of_sun(t)
|
||||
solar_dec = sun_declination(e, L)
|
||||
eqtime = calc_equation_of_time(t)
|
||||
|
||||
time_correction = (eqtime - 4 * longitude) + 60 * utc_zone
|
||||
true_solar_time = ((utc_time - utc_zone) * 60.0 + time_correction) % 1440
|
||||
|
||||
hour_angle = true_solar_time / 4.0 - 180.0
|
||||
if hour_angle < -180.0:
|
||||
hour_angle += 360.0
|
||||
|
||||
csz = (math.sin(latitude) * math.sin(solar_dec) +
|
||||
math.cos(latitude) * math.cos(solar_dec) *
|
||||
math.cos(radians(hour_angle)))
|
||||
if csz > 1.0:
|
||||
csz = 1.0
|
||||
elif csz < -1.0:
|
||||
csz = -1.0
|
||||
|
||||
zenith = math.acos(csz)
|
||||
|
||||
az_denom = math.cos(latitude) * math.sin(zenith)
|
||||
|
||||
if abs(az_denom) > 0.001:
|
||||
az_rad = ((math.sin(latitude) *
|
||||
math.cos(zenith)) - math.sin(solar_dec)) / az_denom
|
||||
if abs(az_rad) > 1.0:
|
||||
az_rad = -1.0 if (az_rad < 0.0) else 1.0
|
||||
azimuth = 180.0 - degrees(math.acos(az_rad))
|
||||
if hour_angle > 0.0:
|
||||
azimuth = -azimuth
|
||||
else:
|
||||
azimuth = 180.0 if (latitude > 0.0) else 0.0
|
||||
|
||||
if azimuth < 0.0:
|
||||
azimuth = azimuth + 360.0
|
||||
|
||||
exoatm_elevation = 90.0 - degrees(zenith)
|
||||
|
||||
if sun_props.use_refraction:
|
||||
if exoatm_elevation > 85.0:
|
||||
refraction_correction = 0.0
|
||||
else:
|
||||
te = math.tan(radians(exoatm_elevation))
|
||||
if exoatm_elevation > 5.0:
|
||||
refraction_correction = (
|
||||
58.1 / te - 0.07 / (te ** 3) + 0.000086 / (te ** 5))
|
||||
elif (exoatm_elevation > -0.575):
|
||||
s1 = (-12.79 + exoatm_elevation * 0.711)
|
||||
s2 = (103.4 + exoatm_elevation * (s1))
|
||||
s3 = (-518.2 + exoatm_elevation * (s2))
|
||||
refraction_correction = 1735.0 + exoatm_elevation * (s3)
|
||||
else:
|
||||
refraction_correction = -20.774 / te
|
||||
|
||||
refraction_correction = refraction_correction / 3600
|
||||
solar_elevation = 90.0 - (degrees(zenith) - refraction_correction)
|
||||
|
||||
else:
|
||||
solar_elevation = 90.0 - degrees(zenith)
|
||||
|
||||
solar_azimuth = azimuth
|
||||
solar_azimuth += north_offset
|
||||
|
||||
sun.az_north = solar_azimuth
|
||||
|
||||
sun.theta = math.pi / 2 - radians(solar_elevation)
|
||||
sun.phi = radians(solar_azimuth) * -1
|
||||
sun.azimuth = azimuth
|
||||
sun.elevation = solar_elevation
|
||||
|
||||
|
||||
def set_sun_position(obj, distance):
|
||||
locX = math.sin(sun.phi) * math.sin(-sun.theta) * distance
|
||||
locY = math.sin(sun.theta) * math.cos(sun.phi) * distance
|
||||
locZ = math.cos(sun.theta) * distance
|
||||
|
||||
#----------------------------------------------
|
||||
# Update selected object in viewport
|
||||
#----------------------------------------------
|
||||
obj.location = locX, locY, locZ
|
||||
|
||||
|
||||
def set_sun_rotations(obj, rotation_euler):
|
||||
rotation_quaternion = rotation_euler.to_quaternion()
|
||||
obj.rotation_quaternion = rotation_quaternion
|
||||
|
||||
if obj.rotation_mode in {'XZY', 'YXZ', 'YZX', 'ZXY','ZYX'}:
|
||||
obj.rotation_euler = rotation_quaternion.to_euler(obj.rotation_mode)
|
||||
else:
|
||||
obj.rotation_euler = rotation_euler
|
||||
|
||||
rotation_axis_angle = obj.rotation_quaternion.to_axis_angle()
|
||||
obj.rotation_axis_angle = (rotation_axis_angle[1],
|
||||
*rotation_axis_angle[0])
|
||||
|
||||
|
||||
def calc_sunrise_set_UTC(rise, jd, latitude, longitude):
|
||||
t = calc_time_julian_cent(jd)
|
||||
eq_time = calc_equation_of_time(t)
|
||||
solar_dec = calc_sun_declination(t)
|
||||
hour_angle = calc_hour_angle_sunrise(latitude, solar_dec)
|
||||
if not rise:
|
||||
hour_angle = -hour_angle
|
||||
delta = longitude + degrees(hour_angle)
|
||||
time_UTC = 720 - (4.0 * delta) - eq_time
|
||||
return time_UTC
|
||||
|
||||
|
||||
def calc_sun_declination(t):
|
||||
e = radians(obliquity_correction(t))
|
||||
L = apparent_longitude_of_sun(t)
|
||||
solar_dec = sun_declination(e, L)
|
||||
return solar_dec
|
||||
|
||||
|
||||
def calc_hour_angle_sunrise(lat, solar_dec):
|
||||
lat_rad = radians(lat)
|
||||
HAarg = (math.cos(radians(90.833)) /
|
||||
(math.cos(lat_rad) * math.cos(solar_dec))
|
||||
- math.tan(lat_rad) * math.tan(solar_dec))
|
||||
if HAarg < -1.0:
|
||||
HAarg = -1.0
|
||||
elif HAarg > 1.0:
|
||||
HAarg = 1.0
|
||||
HA = math.acos(HAarg)
|
||||
return HA
|
||||
|
||||
|
||||
def calc_solar_noon(jd, longitude, timezone, dst):
|
||||
t = calc_time_julian_cent(jd - longitude / 360.0)
|
||||
eq_time = calc_equation_of_time(t)
|
||||
noon_offset = 720.0 - (longitude * 4.0) - eq_time
|
||||
newt = calc_time_julian_cent(jd + noon_offset / 1440.0)
|
||||
eq_time = calc_equation_of_time(newt)
|
||||
|
||||
nv = 780.0 if dst else 720.0
|
||||
noon_local = (nv- (longitude * 4.0) - eq_time + (timezone * 60.0)) % 1440
|
||||
sun.solar_noon.time = noon_local / 60.0
|
||||
|
||||
|
||||
def calc_sunrise_sunset(rise):
|
||||
zone = -sun.UTC_zone
|
||||
|
||||
jd = get_julian_day(sun.year, sun.month, sun.day)
|
||||
time_UTC = calc_sunrise_set_UTC(rise, jd, sun.latitude, sun.longitude)
|
||||
new_time_UTC = calc_sunrise_set_UTC(rise, jd + time_UTC / 1440.0,
|
||||
sun.latitude, sun.longitude)
|
||||
time_local = new_time_UTC + (-zone * 60.0)
|
||||
tl = time_local / 60.0
|
||||
get_sun_position(tl, sun.latitude, sun.longitude, 0.0,
|
||||
zone, sun.month, sun.day, sun.year,
|
||||
sun.sun_distance)
|
||||
if sun.use_daylight_savings:
|
||||
time_local += 60.0
|
||||
tl = time_local / 60.0
|
||||
tl %= 24.0
|
||||
if rise:
|
||||
sun.sunrise.time = tl
|
||||
sun.sunrise.azimuth = sun.azimuth
|
||||
sun.sunrise.elevation = sun.elevation
|
||||
calc_solar_noon(jd, sun.longitude, -zone, sun.use_daylight_savings)
|
||||
get_sun_position(sun.solar_noon.time, sun.latitude, sun.longitude,
|
||||
0.0, zone, sun.month, sun.day, sun.year,
|
||||
sun.sun_distance)
|
||||
sun.solar_noon.elevation = sun.elevation
|
||||
else:
|
||||
sun.sunset.time = tl
|
||||
sun.sunset.azimuth = sun.azimuth
|
||||
sun.sunset.elevation = sun.elevation
|
||||
|
||||
##########################################################################
|
||||
## Get the elapsed julian time since 1/1/2000 12:00 gmt
|
||||
## Y2k epoch (1/1/2000 12:00 gmt) is Julian day 2451545.0
|
||||
##########################################################################
|
||||
|
||||
|
||||
def julian_time_from_y2k(utc_time, year, month, day):
|
||||
century = 36525.0 # Days in Julian Century
|
||||
epoch = 2451545.0 # Julian Day for 1/1/2000 12:00 gmt
|
||||
jd = get_julian_day(year, month, day)
|
||||
return ((jd + (utc_time / 24)) - epoch) / century
|
||||
|
||||
|
||||
def get_julian_day(year, month, day):
|
||||
if month <= 2:
|
||||
year -= 1
|
||||
month += 12
|
||||
A = math.floor(year / 100)
|
||||
B = 2 - A + math.floor(A / 4.0)
|
||||
jd = (math.floor((365.25 * (year + 4716.0))) +
|
||||
math.floor(30.6001 * (month + 1)) + day + B - 1524.5)
|
||||
return jd
|
||||
|
||||
|
||||
def calc_time_julian_cent(jd):
|
||||
t = (jd - 2451545.0) / 36525.0
|
||||
return t
|
||||
|
||||
|
||||
def sun_declination(e, L):
|
||||
return (math.asin(math.sin(e) * math.sin(L)))
|
||||
|
||||
|
||||
def calc_equation_of_time(t):
|
||||
epsilon = obliquity_correction(t)
|
||||
ml = radians(mean_longitude_sun(t))
|
||||
e = eccentricity_earth_orbit(t)
|
||||
m = radians(mean_anomaly_sun(t))
|
||||
y = math.tan(radians(epsilon) / 2.0)
|
||||
y = y * y
|
||||
sin2ml = math.sin(2.0 * ml)
|
||||
cos2ml = math.cos(2.0 * ml)
|
||||
sin4ml = math.sin(4.0 * ml)
|
||||
sinm = math.sin(m)
|
||||
sin2m = math.sin(2.0 * m)
|
||||
etime = (y * sin2ml - 2.0 * e * sinm + 4.0 * e * y *
|
||||
sinm * cos2ml - 0.5 * y ** 2 * sin4ml - 1.25 * e ** 2 * sin2m)
|
||||
return (degrees(etime) * 4)
|
||||
|
||||
|
||||
def obliquity_correction(t):
|
||||
ec = obliquity_of_ecliptic(t)
|
||||
omega = 125.04 - 1934.136 * t
|
||||
return (ec + 0.00256 * math.cos(radians(omega)))
|
||||
|
||||
|
||||
def obliquity_of_ecliptic(t):
|
||||
return ((23.0 + 26.0 / 60 + (21.4480 - 46.8150) / 3600 * t -
|
||||
(0.00059 / 3600) * t ** 2 + (0.001813 / 3600) * t ** 3))
|
||||
|
||||
|
||||
def true_longitude_of_sun(t):
|
||||
return (mean_longitude_sun(t) + equation_of_sun_center(t))
|
||||
|
||||
|
||||
def calc_sun_apparent_long(t):
|
||||
o = true_longitude_of_sun(t)
|
||||
omega = 125.04 - 1934.136 * t
|
||||
lamb = o - 0.00569 - 0.00478 * math.sin(radians(omega))
|
||||
return lamb
|
||||
|
||||
|
||||
def apparent_longitude_of_sun(t):
|
||||
return (radians(true_longitude_of_sun(t) - 0.00569 - 0.00478 *
|
||||
math.sin(radians(125.04 - 1934.136 * t))))
|
||||
|
||||
|
||||
def mean_longitude_sun(t):
|
||||
return (280.46646 + 36000.76983 * t + 0.0003032 * t ** 2) % 360
|
||||
|
||||
|
||||
def equation_of_sun_center(t):
|
||||
m = radians(mean_anomaly_sun(t))
|
||||
c = ((1.914602 - 0.004817 * t - 0.000014 * t ** 2) * math.sin(m) +
|
||||
(0.019993 - 0.000101 * t) * math.sin(m * 2) +
|
||||
0.000289 * math.sin(m * 3))
|
||||
return c
|
||||
|
||||
|
||||
def mean_anomaly_sun(t):
|
||||
return (357.52911 + t * (35999.05029 - 0.0001537 * t))
|
||||
|
||||
|
||||
def eccentricity_earth_orbit(t):
|
||||
return (0.016708634 - 0.000042037 * t - 0.0000001267 * t ** 2)
|
|
@ -0,0 +1,293 @@
|
|||
### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator, Menu
|
||||
from bl_operators.presets import AddPresetBase
|
||||
import os
|
||||
|
||||
from .sun_calc import (format_lat_long, format_time, format_hms, sun)
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Choice list of places, month and day at 12:00 noon
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
|
||||
class SUNPOS_MT_Presets(Menu):
|
||||
bl_label = "Sun Position Presets"
|
||||
preset_subdir = "operator/sun_position"
|
||||
preset_operator = "script.execute_preset"
|
||||
draw = Menu.draw_preset
|
||||
|
||||
|
||||
class SUNPOS_OT_AddPreset(AddPresetBase, Operator):
|
||||
'''Add Sun Position preset'''
|
||||
bl_idname = "world.sunpos_add_preset"
|
||||
bl_label = "Add Sun Position preset"
|
||||
preset_menu = "SUNPOS_MT_Presets"
|
||||
|
||||
# variable used for all preset values
|
||||
preset_defines = [
|
||||
"sun_props = bpy.context.scene.sun_pos_properties"
|
||||
]
|
||||
|
||||
# properties to store in the preset
|
||||
preset_values = [
|
||||
"sun_props.day",
|
||||
"sun_props.month",
|
||||
"sun_props.time",
|
||||
"sun_props.year",
|
||||
"sun_props.UTC_zone",
|
||||
"sun_props.use_daylight_savings",
|
||||
"sun_props.latitude",
|
||||
"sun_props.longitude",
|
||||
]
|
||||
|
||||
# where to store the preset
|
||||
preset_subdir = "operator/sun_position"
|
||||
|
||||
|
||||
class SUNPOS_OT_DefaultPresets(Operator):
|
||||
'''Copy Sun Position default presets'''
|
||||
bl_idname = "world.sunpos_default_presets"
|
||||
bl_label = "Copy Sun Position default presets"
|
||||
|
||||
def execute(self, context):
|
||||
preset_dirpath = bpy.utils.user_resource('SCRIPTS', path="presets/operator/sun_position", create=True)
|
||||
# [month, day, time, UTC, lat, lon, dst]
|
||||
presets = {"chongqing.py": [10, 1, 7.18, 8, 29.5583, 106.567, False],
|
||||
"sao_paulo.py": [9, 7, 12.0, -3, -23.55, -46.6333, False],
|
||||
"kinshasa.py": [6, 30, 12.0, 1, -4.325, 15.3222, False],
|
||||
"london.py": [6, 11, 12.0, 0, 51.5072, -0.1275, True],
|
||||
"new_york.py": [7, 4, 12.0, -5, 40.6611, -73.9439, True],
|
||||
"sydney.py": [1, 26, 17.6, 10, -33.865, 151.209, False]}
|
||||
|
||||
script = '''import bpy
|
||||
sun_props = bpy.context.scene.sun_pos_properties
|
||||
|
||||
sun_props.month = {:d}
|
||||
sun_props.day = {:d}
|
||||
sun_props.time = {:f}
|
||||
sun_props.UTC_zone = {:d}
|
||||
sun_props.latitude = {:f}
|
||||
sun_props.longitude = {:f}
|
||||
sun_props.use_daylight_savings = {}
|
||||
'''
|
||||
|
||||
for path, p in presets.items():
|
||||
print(p)
|
||||
with open(os.path.join(preset_dirpath, path), 'w') as f:
|
||||
f.write(script.format(*p))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
#
|
||||
# Draw the Sun Panel, sliders, et. al.
|
||||
#
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
class SUNPOS_PT_Panel(bpy.types.Panel):
|
||||
bl_idname = "SUNPOS_PT_world"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "world"
|
||||
bl_label = "Sun Position"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
sp = context.scene.sun_pos_properties
|
||||
p = context.preferences.addons[__package__].preferences
|
||||
layout = self.layout
|
||||
self.draw_panel(context, sp, p, layout)
|
||||
|
||||
def draw_panel(self, context, sp, p, layout):
|
||||
self.layout.label(text="Usage mode:")
|
||||
self.layout.prop(sp, "usage_mode", expand=True)
|
||||
if sp.usage_mode == "HDR":
|
||||
self.draw_environ_mode_panel(context, sp, p, layout)
|
||||
else:
|
||||
self.draw_normal_mode_panel(context, sp, p, layout)
|
||||
|
||||
def draw_environ_mode_panel(self, context, sp, p, layout):
|
||||
box = self.layout.box()
|
||||
flow = box.grid_flow(row_major=True, columns=0, even_columns=True,
|
||||
even_rows=False, align=False)
|
||||
|
||||
col = flow.column()
|
||||
col.label(text="Environment texture:")
|
||||
col.prop_search(sp, "hdr_texture",
|
||||
context.scene.world.node_tree, "nodes", text="")
|
||||
col.separator()
|
||||
|
||||
col = flow.column()
|
||||
col.label(text="Sun object:")
|
||||
col.prop_search(sp, "sun_object",
|
||||
context.view_layer, "objects", text="")
|
||||
col.separator()
|
||||
|
||||
col = flow.column(align=True)
|
||||
col.prop(sp, "sun_distance")
|
||||
if not sp.bind_to_sun:
|
||||
col.prop(sp, "hdr_elevation")
|
||||
col.prop(sp, "hdr_azimuth")
|
||||
col.separator()
|
||||
|
||||
col = flow.column(align=True)
|
||||
row1 = col.row()
|
||||
if sp.bind_to_sun:
|
||||
prop_text="Release binding"
|
||||
else:
|
||||
prop_text="Bind Texture to Sun "
|
||||
row1.prop(sp, "bind_to_sun", toggle=True, icon="CONSTRAINT",
|
||||
text=prop_text)
|
||||
|
||||
row = col.row()
|
||||
row.enabled = not sp.bind_to_sun
|
||||
row.operator("world.sunpos_show_hdr", icon='LIGHT_SUN')
|
||||
|
||||
def draw_normal_mode_panel(self, context, sp, p, layout):
|
||||
if p.show_time_place:
|
||||
row = layout.row(align=True)
|
||||
row.menu(SUNPOS_MT_Presets.__name__, text=SUNPOS_MT_Presets.bl_label)
|
||||
row.operator(SUNPOS_OT_AddPreset.bl_idname, text="", icon='ADD')
|
||||
row.operator(SUNPOS_OT_AddPreset.bl_idname, text="", icon='REMOVE').remove_active = True
|
||||
row.operator(SUNPOS_OT_DefaultPresets.bl_idname, text="", icon='FILE_REFRESH')
|
||||
|
||||
box = self.layout.box()
|
||||
flow = box.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
||||
|
||||
col = flow.column()
|
||||
col.prop(sp, "use_sky_texture", text="Cycles sky")
|
||||
if sp.use_sky_texture:
|
||||
col.prop_search(sp, "sky_texture", context.scene.world.node_tree,
|
||||
"nodes", text="")
|
||||
col.separator()
|
||||
|
||||
col = flow.column()
|
||||
col.prop(sp, "use_sun_object", text="Use object")
|
||||
if sp.use_sun_object:
|
||||
col.prop(sp, "sun_object", text="")
|
||||
col.separator()
|
||||
|
||||
col = flow.column()
|
||||
if p.show_object_collection:
|
||||
col.prop(sp, "use_object_collection", text="Use collection")
|
||||
if sp.use_object_collection:
|
||||
col.prop(sp, "object_collection", text="")
|
||||
if sp.object_collection:
|
||||
col.prop(sp, "object_collection_type")
|
||||
if sp.object_collection_type == 'ECLIPTIC':
|
||||
col.prop(sp, "time_spread")
|
||||
|
||||
box = self.layout.box()
|
||||
|
||||
col = box.column(align=True)
|
||||
col.label(text="Enter coordinates:")
|
||||
col.prop(sp, "co_parser", text='', icon='URL')
|
||||
|
||||
box.separator()
|
||||
|
||||
flow = box.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
||||
|
||||
col = flow.column(align=True)
|
||||
col.prop(sp, "latitude")
|
||||
if p.show_dms:
|
||||
row = col.row()
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text=format_lat_long(sp.latitude, True))
|
||||
|
||||
col = flow.column(align=True)
|
||||
col.prop(sp, "longitude")
|
||||
if p.show_dms:
|
||||
row = col.row()
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text=format_lat_long(sp.longitude, False))
|
||||
col.separator()
|
||||
|
||||
if p.show_north:
|
||||
col = flow.column(align=True)
|
||||
col.prop(sp, "show_north", text="Show North", toggle=True)
|
||||
col.prop(sp, "north_offset")
|
||||
col.separator()
|
||||
|
||||
if p.show_az_el:
|
||||
col = flow.column(align=True)
|
||||
row = col.row()
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text="Azimuth: " +
|
||||
str(round(sun.azimuth, 3)) + "°")
|
||||
row = col.row()
|
||||
row.alignment = 'RIGHT'
|
||||
row.label(text="Elevation: " +
|
||||
str(round(sun.elevation, 3)) + "°")
|
||||
col.separator()
|
||||
|
||||
if p.show_refraction:
|
||||
col = flow.column()
|
||||
col.prop(sp, "use_refraction", text="Show refraction")
|
||||
col.separator()
|
||||
|
||||
col = flow.column()
|
||||
col.prop(sp, "sun_distance")
|
||||
|
||||
|
||||
box = self.layout.box()
|
||||
flow = box.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
||||
|
||||
col = flow.column(align=True)
|
||||
col.prop(sp, "use_day_of_year",
|
||||
icon='SORTTIME')
|
||||
if sp.use_day_of_year:
|
||||
col.prop(sp, "day_of_year")
|
||||
else:
|
||||
col.prop(sp, "month")
|
||||
col.prop(sp, "day")
|
||||
col.prop(sp, "year")
|
||||
col.separator()
|
||||
|
||||
col = flow.column(align=True)
|
||||
col.prop(sp, "time")
|
||||
col.prop(sp, "UTC_zone")
|
||||
if p.show_daylight_savings:
|
||||
col.prop(sp, "use_daylight_savings", text="Daylight Savings")
|
||||
col.separator()
|
||||
|
||||
lt = format_time(sp.time,
|
||||
p.show_daylight_savings and sp.use_daylight_savings,
|
||||
sp.longitude)
|
||||
ut = format_time(sp.time,
|
||||
p.show_daylight_savings and sp.use_daylight_savings,
|
||||
sp.longitude,
|
||||
sp.UTC_zone)
|
||||
col = flow.column(align=True)
|
||||
col.alignment = 'CENTER'
|
||||
col.label(text="Local: " + lt, icon='TIME')
|
||||
col.label(text=" UTC: " + ut, icon='PREVIEW_RANGE')
|
||||
col.separator()
|
||||
|
||||
col = flow.column(align=True)
|
||||
col.alignment = 'CENTER'
|
||||
if p.show_rise_set:
|
||||
sr = format_hms(sun.sunrise.time)
|
||||
ss = format_hms(sun.sunset.time)
|
||||
tsr = "Sunrise: " + sr
|
||||
tss = " Sunset: " + ss
|
||||
col.label(text=tsr, icon='LIGHT_SUN')
|
||||
col.label(text=tss, icon='SOLO_ON')
|
Loading…
Reference in New Issue