From ca85dbc76c57decc803fff09dcb60bb6fc21e03a Mon Sep 17 00:00:00 2001 From: meta-androcto <meta.androcto1@gmail.com> Date: Fri, 20 Sep 2019 16:23:17 +1000 Subject: [PATCH] sun position: update working version: T69936 --- sun_position/__init__.py | 88 ++- sun_position/hdr.py | 1150 ++++++++---------------------------- sun_position/map.py | 195 +++--- sun_position/north.py | 186 +++--- sun_position/operators.py | 313 ---------- sun_position/properties.py | 441 +++++++------- sun_position/sun_calc.py | 711 ++++++++++++---------- sun_position/ui_sun.py | 926 ++++++++++------------------- 8 files changed, 1403 insertions(+), 2607 deletions(-) delete mode 100644 sun_position/operators.py diff --git a/sun_position/__init__.py b/sun_position/__init__.py index c8e03f77..d276463e 100644 --- a/sun_position/__init__.py +++ b/sun_position/__init__.py @@ -28,67 +28,65 @@ # NASA's image use policy can be found at: # http://www.nasa.gov/audience/formedia/features/MP_Photo_Guidelines.html # -------------------------------------------------------------------------- +# 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 2.8", - "author": "Michael Martin, Kevan Cress", - "version": (3, 0, 1), + "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": "3D View"} # "Lighting"} ? + "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"} -import bpy -from . properties import * -from . ui_sun import * -from . map import SunPos_Help -from . hdr import SunPos_HdrHelp +if "bpy" in locals(): + import importlib + importlib.reload(properties) + importlib.reload(ui_sun) + importlib.reload(map) + importlib.reload(hdr) -############################################################################ +else: + from . import properties, ui_sun, map, hdr + +import bpy -classes = ( - SunPos_OT_Controller, - SunPos_OT_Preferences, - SunPos_OT_PreferencesDone, - SunPos_OT_DayRange, - SunPos_OT_SetObjectGroup, - SunPos_OT_ClearObjectGroup, - SunPos_OT_TimePlace, - SunPos_OT_Map, - SunPos_OT_Hdr, - SPOS_PT_Panel, - SunPos_OT_MapChoice, - SunPos_Help, - SunPos_HdrHelp, -) def register(): - bpy.utils.register_class(SunPosSettings) - bpy.types.Scene.SunPos_property = ( - bpy.props.PointerProperty(type=SunPosSettings, + 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(SunPosPreferences) - bpy.types.Scene.SunPos_pref_property = ( - bpy.props.PointerProperty(type=SunPosPreferences, - name="Sun Position Preferences", - description="SP Preferences")) + 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.utils.register_class(map.SunPos_Help) - for c in classes: - bpy.utils.register_class(c) + bpy.app.handlers.frame_change_post.append(sun_calc.sun_handler) def unregister(): - for c in reversed(classes): - bpy.utils.unregister_class(c) + # bpy.utils.unregister_class(map.SunPos_Help) + 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) - del bpy.types.Scene.SunPos_pref_property - bpy.utils.unregister_class(SunPosPreferences) - del bpy.types.Scene.SunPos_property - bpy.utils.unregister_class(SunPosSettings) + bpy.app.handlers.frame_change_post.remove(sun_calc.sun_handler) diff --git a/sun_position/hdr.py b/sun_position/hdr.py index e7f908f6..774b42df 100644 --- a/sun_position/hdr.py +++ b/sun_position/hdr.py @@ -1,936 +1,294 @@ +### 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 -import blf -import sys -import os -import math - -from . sun_calc import degToRad, radToDeg, format_hms -from . properties import Display, Sun - -# --------------------------------------------------------------------------- - - -class HdrObject: - - class Origin: - x = 0 - y = 0 - - def __init__(self, t, w, h): - self.type = t - self.width = w - self.height = h - self.heightFactor = .50 - self.opacity = 1.0 - self.focused = False - self.view3d_area = None - self.origin = self.Origin() - - def set_dimensions(self, width): - self.width = width - self.height = int(width * self.heightFactor) - - def check_focus(self, context, event): - self.focused = self.is_focused(context, event) - return self.focused - - def is_focused(self, context, event): - if context.area != self.view3d_area: - return False - - x = event.mouse_region_x - y = event.mouse_region_y - - for reg in self.view3d_area.regions: - if reg.type == 'WINDOW': - if x < 0 or x > reg.width: - return False - else: - break - - if x < self.origin.x or x > (self.origin.x + self.width) or \ - y < self.origin.y or y > (self.origin.y + self.height) or \ - y < 0 or y > reg.height: - return False - return True - - def near_border(self, context, event): - if context.area != self.view3d_area: - return False - - x = event.mouse_region_x - y = event.mouse_region_y - - for reg in self.view3d_area.regions: - if reg.type == 'WINDOW': - if x < 20 or x > (reg.width - 20) or \ - y < 20 or y > (reg.height - 20): - return True - else: - break - return False - - -# --------------------------------------------------------------------------- +from gpu_extras.batch import batch_for_shader +from mathutils import Vector +from gpu_extras.presets import draw_texture_2d +from math import sqrt, pi, atan2, asin -class HdrClass: +vertex_shader = ''' +uniform mat4 ModelViewProjectionMatrix; - class mouse: - pass +/* Keep in sync with intern/opencolorio/gpu_shader_display_transform_vertex.glsl */ +in vec2 texCoord; +in vec2 pos; +out vec2 texCoord_interp; - class grab: +void main() +{ + gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); + gl_Position.z = 1.0; + texCoord_interp = texCoord; +}''' - class spot: - pass +fragment_shader = ''' +in vec2 texCoord_interp; +out vec4 fragColor; - class offset: - pass +uniform sampler2D image; +uniform float exposure; - class zoom: - pass +void main() +{ + fragColor = texture(image, texCoord_interp) * exposure; +}''' - class image: - pass +# shader = gpu.types.GPUShader(vertex_shader, fragment_shader) - class last: - pass - def __init__(self): - self.handler1 = None - self.handler2 = None - self.view3d_area = None - self.draw_region = None - self.glImage = None - self.init_zoom_preference = True - self.reset() - self.last.filename = None - self.last.image = None - self.last.pixels = None - self.last.projection = None +def draw_callback_px(self, context): + nt = bpy.context.scene.world.node_tree.nodes + env_tex_node = nt.get(bpy.context.scene.sun_pos_properties.hdr_texture) + image = env_tex_node.image - def init(self): - self.object = [HdrObject('MAP', 0, 0), HdrObject('TEXT', 100, 160)] - self.object[0].set_dimensions(200) - self.object[1].origin.x = 10 - self.object[1].origin.y = 80 - - def zoom_preferences(self, invert_zoom_wheel, invert_mouse_zoom): - self.init_zoom_preference = False - if invert_zoom_wheel: - self.zoom.wheel_up = 'OUT' - self.zoom.wheel_down = 'IN' - else: - self.zoom.wheel_up = 'IN' - self.zoom.wheel_down = 'OUT' - if invert_mouse_zoom: - self.zoom.mouse_up = 'IN' - self.zoom.mouse_down = 'OUT' - else: - self.zoom.mouse_up = 'OUT' - self.zoom.mouse_down = 'IN' - - def reset(self): - self.init() - self.action = None - self.isActive = False - self.start = False - self.stop = False - self.lockCrosshair = True - self.elevation = 0.0 - self.azimuth = 0.0 - self.ctrlPress = False - self.altPress = False - self.mouse.x = 0 - self.mouse.y = 0 - self.grab.spot.x = 0 - self.grab.spot.y = 0 - self.grab.offset.x = 0 - self.grab.offset.y = 0 - self.zoom.width = 0 - self.zoom.x = 0 - self.zoom.y = 0 - self.image.name = None - self.image.bindcode = 0 - self.image.loaded = False - self.image.free_it = False - - def clear_callbacks(self): - if self.handler2 is not None: - bpy.types.SpaceView3D.draw_handler_remove(self.handler2, 'WINDOW') - self.handler2 = None - if self.handler1 is not None: - bpy.types.SpaceView3D.draw_handler_remove(self.handler1, 'WINDOW') - self.handler1 = None - - def set_view3d_area(self, area): - for obj in self.object: - obj.view3d_area = area - - def activate(self, context): - if context.area.type == 'PROPERTIES': - self.reset() - - def fw(self, context): - self.draw_region = context.region - areas = bpy.context.screen.areas - for area in areas: - if area.type == 'VIEW_3D': - self.view3d_area = context.area - for reg in area.regions: - if reg.type == 'WINDOW': - self.draw_region = reg - Display.refresh() - return True - return False - if not fw(self, context): - self.draw_region = context.region - - self.set_view3d_area(self.view3d_area) - self.start = True - self.handler1 = bpy.types.SpaceView3D.draw_handler_add( - Hdr_load_callback, - (self, context), 'WINDOW', 'POST_PIXEL') - self.isActive = True - return True - else: - return False - - def activateBGLcallback(self, context): - self.handler2 = bpy.types.SpaceView3D.draw_handler_add( - Draw_hdr_callback, (self, context), 'WINDOW', 'POST_PIXEL') - self.view3d_area = context.area - self.set_view3d_area(self.view3d_area) - bpy.ops.sunpos.hdr('INVOKE_DEFAULT') - - def deactivate(self): - self.clear_callbacks() - self.stop = False - self.action = None - self.image.loaded = False - if self.glImage is not None: - try: - if self.glImage.bindcode == self.image.bindcode: - self.glImage.gl_free() - bpy.data.images.remove(self.glImage) - except: - pass - self.image.free_it = False - self.glImage = None - self.image.bindcode = 0 - self.isActive = False - #if Sun.SP: # why removed? - Sun.SP.ShowHdr = False # indent? - Sun.SP.BindToSun = True # indent? - Sun.BindToSun = False - Display.refresh() - - def make_dummy_file(self, file_name): - fname = file_name.replace("\\", "/") - if os.path.exists(fname): - return False - else: - return True - - def load_blender_image(self, file_name): - fn = file_name - self.image.name = fn.replace("\\", "/") - if os.path.exists(self.image.name): - try: - self.glImage = bpy.data.images.load(self.image.name) - self.glImage.pixels = self.last.pixels - if self.glImage is not None: - self.image.loaded = True - self.glImage.user_clear() - self.object[0].heightFactor = \ - self.glImage.size[1] / self.glImage.size[0] - return True - else: - return False - except: - pass - return False - - def load_gl_image(self): - for i in range(1, 6): # Make up to 6 tries to load image - self.glImage.gl_load(bgl.GL_NEAREST, bgl.GL_NEAREST) - if self.glImage.bindcode != 0: - self.image.bindcode = self.glImage.bindcode - return True - return False - - def locked_crosshair_event(self, action, event): - self.mouse.x = event.mouse_region_x - self.mouse.y = event.mouse_region_y - self.grab.offset.x = event.mouse_region_x - self.grab.offset.y = event.mouse_region_y - self.lockCrosshair = True - Display.refresh() - return Hdr_function[action](event) - - # ----------------------------------------------------------------------- - - def event_controller(self, context, event): - - if not Sun.SP.ShowHdr or event.type == 'TIMER': - return {'PASS_THROUGH'} + if self.area != context.area: + return - hdrInFocus = self.object[0].check_focus(context, event) + if image.gl_load(): + raise Exception() + + left = 0 + 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 + + 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() - if event.type == 'MOUSEMOVE': - if self.action == None: - if hdrInFocus: - if self.object[0].near_border(context, event): - return {'PASS_THROUGH'} - else: - return {'RUNNING_MODAL'} else: - return {'PASS_THROUGH'} - if self.action in ('PAN', 'ZOOM', 'G'): - return self.locked_crosshair_event(self.action, event) - - if event.type in ('LEFT_CTRL', 'LEFT_ALT', 'RIGHT_CTRL', 'RIGHT_ALT'): - Key_function[event.type](event) - return {'RUNNING_MODAL'} - - self.object[1].check_focus(context, event) - - if event.type in ('MIDDLEMOUSE', 'LEFTMOUSE', 'G'): - val = Key_function[event.type](context, event) - if val: - return val - elif event.type in ('RIGHTMOUSE', 'ESC', 'H', 'F1'): - Display.refresh() - return Key_function[event.type](event) - - if self.action in ('PAN', 'ZOOM', 'G'): - return self.locked_crosshair_event(self.action, event) - - if not hdrInFocus: - return {'PASS_THROUGH'} + dir.y = -sqrt(max(1.0 - dir.x * dir.x - dir.z * dir.z, 0.0)) - self.mouse.x = event.mouse_region_x - self.mouse.y = event.mouse_region_y + # Reflection + i = Vector((0.0, -1.0, 0.0)) - if event.type == 'WHEELUPMOUSE': - wheel_zoom(self.zoom.wheel_up) - elif event.type == 'WHEELDOWNMOUSE': - wheel_zoom(self.zoom.wheel_down) - elif self.action == 'CROSS': - self.lockCrosshair = False - Display.refresh() - - return {'RUNNING_MODAL'} - -# --------------------------------------------------------------------------- - - -Hdr = HdrClass() - - -# --------------------------------------------------------------------------- + dir = 2.0 * dir.dot(i) * dir - i + # Convert vector to euler + el = asin(dir.z) + az = atan2(dir.x, dir.y) + sun_props.hdr_elevation = el + sun_props.hdr_azimuth = az -def key_Ctrl(event): - if event.value == 'PRESS': - Hdr.ctrlPress = True - elif event.value == 'RELEASE': - Hdr.ctrlPress = False - - -def key_Alt(event): - if event.value == 'PRESS': - Hdr.altPress = True - elif event.value == 'RELEASE': - Hdr.altPress = False - - -def key_Esc(event): - if Hdr.object[0].focused: - if Hdr.action is None: - Hdr.stop = True - return {'FINISHED'} - if Hdr.action is not None: - if event.value == 'RELEASE': - Hdr.action = None - return {'RUNNING_MODAL'} - return {'PASS_THROUGH'} - - -def key_LeftMouse(context, event): - if event.value == 'PRESS': - if Hdr.action is not None: - Hdr.action = None - Display.refresh() - return {'RUNNING_MODAL'} - elif Hdr.object[0].focused: - if Hdr.object[0].near_border(context, event): - Hdr.action = 'BORDER' - Hdr.lockCrosshair = True - return {'PASS_THROUGH'} - Hdr.action = 'CROSS' - Hdr.lockCrosshair = False - Display.refresh() - else: - return {'PASS_THROUGH'} - elif event.value == 'RELEASE': - Hdr.lockCrosshair = True - Hdr.action = None - Display.refresh() - return {'PASS_THROUGH'} - - return False - - -def key_MiddleMouse(context, event): - if event.value == 'PRESS': - if Hdr.object[0].focused: - if Hdr.ctrlPress: - hdr = Hdr.object[0] - Hdr.action = 'ZOOM' - Hdr.zoom.width = hdr.width - Hdr.zoom.x = hdr.origin.x - Hdr.zoom.y = hdr.origin.y else: - Hdr.action = 'PAN' if Hdr.action != 'PAN' else None - Hdr.grab.spot.x = event.mouse_region_x - Hdr.grab.spot.y = event.mouse_region_y - return False - else: - return {'PASS_THROUGH'} - elif event.value == 'RELEASE': - Hdr.action = None - Hdr.object[0].focused = False - Hdr.object[1].focused = False - Display.refresh() - return {'RUNNING_MODAL'} - -# --------------------------------------------------------------------------- - - -def key_G(context, event): - if event.value == 'PRESS': - if Hdr.object[0].focused: - Hdr.action = 'PAN' if Hdr.action != 'PAN' else None - Hdr.grab.spot.x = event.mouse_region_x - Hdr.grab.spot.y = event.mouse_region_y - return False - else: - return {'PASS_THROUGH'} - return {'RUNNING_MODAL'} - - -def key_H(event): - if event.value == 'PRESS': - if Hdr.object[0].focused: - Hdr.action = None - bpy.ops.object.hdrhelp_operator('INVOKE_DEFAULT') - return {'RUNNING_MODAL'} - - -# --------------------------------------------------------------------------- - -def hdr_Pan(event): - return {'RUNNING_MODAL'} - - -def hdr_Zoom(event): - mouse_zoom() - return {'RUNNING_MODAL'} - - -def hdr_G(event): - if Hdr.grab.offset.x < Hdr.grab.spot.x: - off = Hdr.grab.spot.x - Hdr.grab.offset.x - Hdr.object[1].origin.x -= off - else: - off = Hdr.grab.offset.x - Hdr.grab.spot.x - Hdr.object[1].origin.x += off - if Hdr.grab.offset.y < Hdr.grab.spot.y: - off = Hdr.grab.spot.y - Hdr.grab.offset.y - Hdr.object[1].origin.y -= off - else: - off = Hdr.grab.offset.y - Hdr.grab.spot.y - Hdr.object[1].origin.y += off - - Hdr.grab.spot.x = Hdr.mouse.x - Hdr.grab.spot.y = Hdr.mouse.y - - return {'RUNNING_MODAL'} + 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 -############################################################################ - -Key_function = dict([('LEFT_CTRL', key_Ctrl), ('LEFT_ALT', key_Alt), - ('RIGHT_CTRL', key_Ctrl), ('RIGHT_ALT', key_Alt), - ('MIDDLEMOUSE', key_MiddleMouse), - ('LEFTMOUSE', key_LeftMouse), - ('RIGHTMOUSE', key_Esc), ('ESC', key_Esc), - ('G', key_G), ('H', key_H), ('F1', key_H)]) - -# --------------------------------------------------------------------------- - -Hdr_function = dict([('PAN', hdr_Pan), ('ZOOM', hdr_Zoom), - ('G', hdr_G)]) - -############################################################################ - - -def wheel_zoom(action): - mf = 0.2 if Hdr.ctrlPress else 0.0 - af = 0.07 if Hdr.altPress else 0.0 - if action == 'IN': - scale = 1.10 + mf - af - else: - scale = .90 - mf + af - if Hdr.object[0].width * scale < 50: - return - else: - Hdr.object[0].set_dimensions(int(int(Hdr.object[0].width * scale))) - x = Hdr.mouse.x - Hdr.object[0].origin.x - y = Hdr.mouse.y - Hdr.object[0].origin.y - Hdr.object[0].origin.x += x - int(x * scale) - Hdr.object[0].origin.y += y - int(y * scale) - Hdr.lockCrosshair = True - Display.refresh() - - -def mouse_zoom(): - if Hdr.mouse.y > Hdr.grab.spot.y: - s = Hdr.mouse.y - Hdr.grab.spot.y - action = Hdr.zoom.mouse_up - elif Hdr.mouse.y < Hdr.grab.spot.y: - s = Hdr.grab.spot.y - Hdr.mouse.y - action = Hdr.zoom.mouse_down - else: - s = 0 - action = Hdr.zoom.mouse_down - - if action == 'IN': - scale = 1 + s * .01 - else: - scale = 1 - s * .006 - - w = int(Hdr.zoom.width * scale) - if w < 50: - return - Hdr.object[0].set_dimensions(w) - x = Hdr.grab.spot.x - Hdr.zoom.x - y = Hdr.grab.spot.y - Hdr.zoom.y - Hdr.object[0].origin.x = x - int(x * scale) + Hdr.zoom.x - Hdr.object[0].origin.y = y - int(y * scale) + Hdr.zoom.y - - -# -------------------------------------------------------------------------- - - -############################################################################ - - -def Hdr_load_callback(self, context): - - def remove_imagedata(name): - bd = bpy.data - for i in bd.images: - if i.name == name: - bd.images.remove(i) + 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'} - if Sun.SP.ShowHdr and not Hdr.image.loaded: - Hdr.glImage = None - fileName = None - projection = "EQUIRECTANGULAR" - try: - nt = bpy.context.scene.world.node_tree.nodes - envTex = nt.get(Sun.HDR_texture) - if envTex.type != "TEX_ENVIRONMENT": - Sun.SP.ShowHdr = False - elif envTex.image == None: - Sun.SP.ShowHdr = False + # 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: - envTex.texture_mapping.rotation.z = 0.0 - projection = envTex.projection - prefs = bpy.context.preferences - fileName = prefs.filepaths.temporary_directory + "tmpSun.png" - - st = envTex.image.copy() - if projection == "MIRROR_BALL": - st.scale(256, 256) - Hdr.last.image = st.copy() - Hdr.last.image.scale(512, 256) - ConvertToLatLong(st, Hdr.last.image) - else: - st.scale(512, 256) - Hdr.last.image = st.copy() - Hdr.last.image.scale(512, 256) - - if Hdr.make_dummy_file(fileName) == True: - Hdr.last.image.save_render(fileName) - Hdr.last.pixels = list(Hdr.last.image.pixels) - except: - pass - - if Sun.SP.ShowHdr: - if not Hdr.load_blender_image(fileName): - print("Could not load image file: ", Hdr.image.name) - Sun.SP.ShowHdr = False + 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: - try: - nt = bpy.context.scene.world.node_tree.nodes - envTex = nt.get(Sun.HDR_texture) - if projection == "MIRROR_BALL": - envTex.texture_mapping.rotation.z = degToRad(270.0) - else: - envTex.texture_mapping.rotation.z = degToRad(90.0) - except: - pass - - if Hdr.start: - def set_region_data(): - Hdr.activateBGLcallback(context) - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, - bgl.GL_LINEAR) - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, - bgl.GL_LINEAR) - Hdr.object[0].set_dimensions(0) - Hdr.toolProps = None - Hdr.toolPropsWidth = 0 - for reg in Hdr.view3d_area.regions: - if reg.type == 'TOOL_PROPS': - Hdr.toolProps = reg - Hdr.toolProps_width = reg.width - elif reg.type == 'WINDOW': - Hdr.region = reg - Hdr.saved_region_width = reg.width - Hdr.object[0].set_dimensions(int(reg.width * .5)) - Hdr.object[0].origin.x = \ - int((Hdr.region.width - Hdr.object[0].width) / 2) - Hdr.object[0].origin.y = 2 - return - - Hdr.start = False - if Hdr.image.loaded: - if not Hdr.load_gl_image(): - print("Could not load image file: ", Hdr.image.name) - elif Hdr.glImage.bindcode != 0: - Hdr.image.free_it = True - set_region_data() - return - print("Could not get texture in gl_load()") - Hdr.glImage = None - Hdr.image.bindcode = 0 - Sun.SP.ShowHdr = False - else: - Sun.SP.ShowHdr = False - return - - if Hdr.stop: - Hdr.deactivate() - return - - return - - -############################################################################ -# Thanks to Domino for the Pixel and ImageBuffer classes -############################################################################ - - -class Pixel: - - def __init__(self, r=0.0, g=0.0, b=0.0, a=None, colour=None): - self.r = r - self.g = g - self.b = b - self.a = a - if colour: - self.r = colour[0] - self.g = colour[1] - self.b = colour[2] - if len(colour) > 3: - self.a = colour[3] - if self.a is None: - self.a = 1.0 - - def as_tuple(self): - return (self.r, self.g, self.b, self.a) - + 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 -class ImageBuffer: - - def __init__(self, image, clear=False): - self.image = image - self.x, self.y = self.image.size - if clear: - self.clear() - else: - self.buffer = list(self.image.pixels) - - def update(self): - self.image.pixels = self.buffer - - def _index(self, x, y): - if x < 0 or y < 0 or x >= self.x or y >= self.y: - return None - return (x + y * self.x) * 4 - - def clear(self): - self.buffer = [0.0 for i in range(self.x * self.y * 4)] - - def set_pixel(self, x, y, colour): - index = self._index(x, y) - if index is not None: - self.buffer[index:index + 4] = colour.as_tuple() - - def get_pixel(self, x, y): - index = self._index(x, y) - if index is not None: - return Pixel(colour=self.buffer[index:index + 4]) else: - return None - - -def ConvertToLatLong(inpic, outpic): - - width = inpic.size[0] - height = inpic.size[1] - - uv_width = width * 2 - 1 - uv_height = height - 1 - - pc_width = 0.5 * width - pc_height = 0.5 * height - - p_in = ImageBuffer(inpic) - p_out = ImageBuffer(outpic) - p_out.clear() + return {'PASS_THROUGH'} - flip = width * 2 - 1 + return {'RUNNING_MODAL'} - for col in range(0, width * 2): - phi = (col / uv_width) * 2 * math.pi - for row in range(0, height): - theta = (1 - row / uv_height) * math.pi - m = math.sqrt(2 * (1 + math.sin(-theta) * math.sin(phi))) - x = int((math.sin(theta) * math.cos(phi) / m + 1) * pc_width) - y = int((math.cos(theta) / m + 1) * pc_height) - pixel = p_in.get_pixel(x, y) - p_out.set_pixel(flip - col, row, pixel) - p_out.update() + 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'} + self.area = context.area -def Draw_hdr_callback(self, context): + self.mouse_position = event.mouse_region_x, event.mouse_region_y - if context.area != Hdr.view3d_area: - return - elif context.area.type == 'PROPERTIES' and \ - context.space_data.context != 'WORLD': - return + self.initial_elevation = context.scene.sun_pos_properties.hdr_elevation + self.initial_azimuth = context.scene.sun_pos_properties.hdr_azimuth - # Check if window area has changed for sticky zoom - theHdr = Hdr.object[0] - if Hdr.region.width < Hdr.saved_region_width: - diff = Hdr.saved_region_width - Hdr.region.width - if theHdr.origin.x + theHdr.width > Hdr.saved_region_width: - if theHdr.origin.x > 0: - theHdr.origin.x -= diff - else: - theHdr.width -= diff - else: - if Hdr.toolProps is not None: - if Hdr.toolProps.width > Hdr.toolProps_width: - theHdr.origin.x -= diff - Hdr.toolProps_width = Hdr.toolProps.width - if theHdr.origin.x < 0: - theHdr.origin.x += diff - else: - diff = Hdr.region.width - Hdr.saved_region_width - if theHdr.width > Hdr.saved_region_width: - theHdr.width += diff - else: - if Hdr.toolProps is not None: - if Hdr.toolProps.width < Hdr.toolProps_width: - theHdr.origin.x += diff - Hdr.toolProps_width = Hdr.toolProps.width - theHdr.set_dimensions(theHdr.width) - Hdr.saved_region_width = Hdr.region.width - - zAzim = theHdr.width / 2 - azimFac = zAzim / 180 - zElev = theHdr.height / 2 - elevFac = zElev / 90 - crossChange = True - - if not Hdr.action == 'PAN': - x = Hdr.mouse.x - y = Hdr.mouse.y - if x < theHdr.origin.x or x > theHdr.origin.x + theHdr.width: - crossChange = False - x = 0 - else: - testBoundary = theHdr.origin.x + theHdr.width - if testBoundary < Hdr.region.width: - rightBoundary = testBoundary - else: - rightBoundary = Hdr.region.width - if x > rightBoundary: - crossChange = False - x = rightBoundary - cX = x - zAzim - theHdr.origin.x - - if azimFac: - newAzimuth = cX / azimFac - else: - newAzimuth = 0.0 - - if y < theHdr.origin.y or y < 0: - crossChange = False - y = 0 - elif y > theHdr.origin.y + theHdr.height: - crossChange = False - y = theHdr.origin.y + theHdr.height - cY = y - zElev - theHdr.origin.y - - if elevFac: - newElevation = cY / elevFac - else: - newElevation = 0.0 + context.workspace.status_text_set("Enter/LMB: confirm, Esc/RMB: cancel, MMB: pan, mouse wheel: zoom, Ctrl + mouse wheel: set exposure") - if newElevation == Hdr.elevation and newAzimuth == Hdr.azimuth: - crossChange = False - else: - Hdr.elevation = newElevation - Hdr.azimuth = newAzimuth - else: - if Hdr.grab.offset.x < Hdr.grab.spot.x: - off = Hdr.grab.spot.x - Hdr.grab.offset.x - theHdr.origin.x -= off - else: - off = Hdr.grab.offset.x - Hdr.grab.spot.x - theHdr.origin.x += off - if Hdr.grab.offset.y < Hdr.grab.spot.y: - off = Hdr.grab.spot.y - Hdr.grab.offset.y - theHdr.origin.y -= off - else: - off = Hdr.grab.offset.y - Hdr.grab.spot.y - theHdr.origin.y += off - Hdr.grab.spot.x = Hdr.mouse.x - Hdr.grab.spot.y = Hdr.mouse.y - - Lx = theHdr.origin.x - Ly = theHdr.origin.y - - # --------------------- - # Draw a textured quad - # --------------------- - - bgl.glEnable(bgl.GL_BLEND) - if Hdr.glImage.bindcode == 0: - Hdr.load_gl_image() - bgl.glBindTexture(bgl.GL_TEXTURE_2D, Hdr.glImage.bindcode) - bgl.glEnable(bgl.GL_TEXTURE_2D) - bgl.glColor4f(1.0, 1.0, 1.0, Hdr.object[0].opacity) - bgl.glBegin(bgl.GL_QUADS) - bgl.glTexCoord2f(0.0, 0.0) - bgl.glVertex2f(Lx, Ly) - bgl.glTexCoord2f(1.0, 0.0) - bgl.glVertex2f(Lx + theHdr.width, Ly) - bgl.glTexCoord2f(1.0, 1.0) - bgl.glVertex2f(Lx + theHdr.width, Ly + theHdr.height) - bgl.glTexCoord2f(0.0, 1.0) - bgl.glVertex2f(Lx, theHdr.height + Ly) - bgl.glEnd() - bgl.glDisable(bgl.GL_TEXTURE_2D) - - # --------------------- - # draw the crosshair - # --------------------- - x = theHdr.width / 2.0 - if crossChange and not Hdr.lockCrosshair: - Sun.SP.HDR_azimuth = degToRad(newAzimuth + 180) - azimuth = ((radToDeg(Sun.SP.HDR_azimuth) - 180) * x / 180.0) + x - - bgl.glEnable(bgl.GL_BLEND) - bgl.glEnable(bgl.GL_LINES) - bgl.glLineWidth(1.0) - alpha = 0.8 - color = (0.4, 0.4, 0.4, alpha) - bgl.glColor4f(color[0], color[1], color[2], color[3]) - bgl.glBegin(bgl.GL_LINES) - bgl.glVertex2f(Lx + azimuth, Ly) - bgl.glVertex2f(Lx + azimuth, Ly + theHdr.height) - bgl.glEnd() - - y = theHdr.height / 2.0 - if crossChange and not Hdr.lockCrosshair: - Sun.SP.HDR_elevation = newElevation - elevation = (Sun.SP.HDR_elevation * y / 90.0) + y - - bgl.glColor4f(color[0], color[1], color[2], color[3]) - bgl.glBegin(bgl.GL_LINES) - bgl.glVertex2f(Lx, Ly + elevation) - bgl.glVertex2f(Lx + theHdr.width, Ly + elevation) - bgl.glEnd() - - # --------------------- - # draw the border - # --------------------- - bgl.glDisable(bgl.GL_BLEND) - color = (0.6, 0.6, .6, 1.0) - bgl.glColor4f(color[0], color[1], color[2], color[3]) - bgl.glBegin(bgl.GL_LINE_LOOP) - bgl.glVertex2f(Lx, Ly) - bgl.glVertex2f(Lx + theHdr.width, Ly) - bgl.glVertex2f(Lx + theHdr.width, Ly + theHdr.height) - bgl.glVertex2f(Lx, theHdr.height + Ly) - bgl.glVertex2f(Lx, Ly) - bgl.glEnd() - - bgl.glLineWidth(1.0) - bgl.glDisable(bgl.GL_LINES) - bgl.glFlush() - -# --------------------------------------------------------------------------- - - -class SunPos_HdrHelp(bpy.types.Operator): - bl_idname = "object.hdrhelp_operator" - bl_label = "Hdr help" - - def execute(self, context): - self.report({'INFO'}, self.message) - return {'FINISHED'} + self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, + (self, context), 'WINDOW', 'POST_PIXEL') + context.window_manager.modal_handler_add(self) - def invoke(self, context, event): - wm = context.window_manager - return wm.invoke_popup(self, width=400, height=200) - - def draw(self, context): - self.layout.label(text="Available commands:") - - row = self.layout.row() - split = row.split(factor=.26) - colL = split.column() - colR = split.column() - colL.label(text="Esc or Right Mouse ") - colR.label(text="Close map or text.") - colL.label(text="Left Mouse") - colR.label(text="Move crosshair.") - colL.label(text="G or MiddleMouse") - colR.label(text="Pan mode. Grab and move map or text.") - colL.label(text="Ctrl Middlemouse") - colR.label(text="Mouse zoom to point.") - self.layout.label("--- The following are changed by moving " + - "the mouse or using the scroll wheel.") - self.layout.label(text="--- Use Ctrl for coarse increments or Alt for fine.") - row = self.layout.row() - split = row.split(factor=.25) - colL = split.column() - colR = split.column() - colL.label(text="Scroll wheel") - colR.label(text="Zoom to point.") + return {'RUNNING_MODAL'} diff --git a/sun_position/map.py b/sun_position/map.py index 9585ee4a..e90b6692 100644 --- a/sun_position/map.py +++ b/sun_position/map.py @@ -1,3 +1,21 @@ +### 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 @@ -8,8 +26,8 @@ import os import datetime import math -from . sun_calc import degToRad, format_hms -from . properties import Display, Sun +from math import radians +from . sun_calc import format_hms, sun # --------------------------------------------------------------------------- @@ -24,16 +42,16 @@ class MapObject: self.type = t self.width = w self.height = h - self.heightFactor = .50 + self.height_factor = .50 self.opacity = 1.0 self.focused = False self.view3d_area = None - self.colorStyle = 'N' + self.color_style = 'N' self.origin = self.Origin() def set_dimensions(self, width): self.width = width - self.height = int(width * self.heightFactor) + self.height = int(width * self.height_factor) def check_focus(self, context, event): self.focused = self.is_focused(context, event) @@ -132,16 +150,16 @@ class MapClass: def reset(self, location): self.init(location) self.action = None - self.isActive = False + self.is_active = False self.start = False self.stop = False - self.lockCrosshair = True - self.showInfo = False + self.lock_crosshair = True + self.show_info = False self.latitude = 0.0 self.longitude = 0.0 - self.ctrlPress = False - self.altPress = False - self.lineWidth = 1.0 + self.ctrl_press = False + self.alt_press = False + self.line_width = 1.0 self.textureless = False self.mouse.x = 0 self.mouse.y = 0 @@ -205,7 +223,7 @@ class MapClass: self.handler1 = bpy.types.SpaceView3D.draw_handler_add( Map_load_callback, (self, context), 'WINDOW', 'POST_PIXEL') - self.isActive = True + self.is_active = True return True else: return False @@ -238,14 +256,13 @@ class MapClass: self.image.free_it = False self.glImage = None self.image.bindcode = 0 - self.isActive = False - #if Sun.SP: # why removed? - Sun.SP.ShowMap = False # indent? + self.is_active = False + sun.sp.show_map = False def load_blender_image(self, file_name): if file_name == "None": self.textureless = True - self.object[0].heightFactor = .5 + self.object[0].height_factor = .5 return True else: self.textureless = False @@ -259,7 +276,7 @@ class MapClass: if self.glImage is not None: self.image.loaded = True self.glImage.user_clear() - self.object[0].heightFactor = \ + self.object[0].height_factor = \ self.glImage.size[1] / self.glImage.size[0] return True else: @@ -286,7 +303,7 @@ class MapClass: fy -= 20 return fy - if text.colorStyle == 'N': + if text.color_style == 'N': tColor = (0.8, 0.8, 0.8, 1.0) vColor = (1.0, 1.0, 1.0, 1.0) else: @@ -296,31 +313,31 @@ class MapClass: blf.size(0, 14, 72) fx = text.origin.x fy = text.origin.y + 140 - fy = text_line(fx + 10, fy, True, tColor, str(Sun.SP.Month) + - " / " + str(Sun.SP.Day) + - " / " + str(Sun.SP.Year)) + fy = text_line(fx + 10, fy, True, tColor, str(sun.sp.month) + + " / " + str(sun.sp.day) + + " / " + str(sun.sp.year)) fy = text_line(fx, fy, False, tColor, " Day: ") - fy = text_line(fx + 40, fy, True, vColor, str(Sun.SP.Day_of_year)) + fy = text_line(fx + 40, fy, True, vColor, str(sun.sp.day_of_year)) fy = text_line(fx, fy, False, tColor, "Time: ") - fy = text_line(fx + 40, fy, True, vColor, format_hms(Sun.SP.Time)) + fy = text_line(fx + 40, fy, True, vColor, format_hms(sun.sp.time)) - if Sun.ShowRiseSet: + if sun.ShowRiseSet: fy -= 10 fy = text_line(fx, fy, True, tColor, "Solar Noon:") fy = text_line(fx + 14, fy, True, vColor, - format_hms(Sun.SolarNoon.time)) + format_hms(sun.SolarNoon.time)) fy -= 10 fy = text_line(fx, fy, False, tColor, "Rise: ") - if Sun.RiseSetOK: + if sun.RiseSetOK: fy = text_line(fx + 40, fy, True, vColor, - format_hms(Sun.Sunrise.time)) + format_hms(sun.Sunrise.time)) else: fy = text_line(fx + 40, fy, True, vColor, "--------") fy = text_line(fx, fy, False, tColor, " Set: ") - if Sun.RiseSetOK: + if sun.RiseSetOK: fy = text_line(fx + 40, fy, True, vColor, - format_hms(Sun.Sunset.time)) + format_hms(sun.sunset.time)) else: fy = text_line(fx + 40, fy, True, vColor, "--------") @@ -337,7 +354,7 @@ class MapClass: def event_controller(self, context, event): - if not Sun.SP.ShowMap or event.type == 'TIMER': + if not sun.sp.show_map or event.type == 'TIMER': return {'PASS_THROUGH'} mapInFocus = self.object[0].check_focus(context, event) @@ -488,7 +505,7 @@ def key_MiddleMouse(context, event): def key_A(event): if event.value == 'PRESS': if Map.object[0].focused: - Sun.SP.ObjectGroup = 'ANALEMMA' + sun.sp.object_group = 'ANALEMMA' else: return {'PASS_THROUGH'} return {'RUNNING_MODAL'} @@ -497,10 +514,10 @@ def key_A(event): def key_C(event): if event.value == 'PRESS': if Map.object[0].focused or Map.object[1].focused: - if Map.object[1].colorStyle == 'N': - Map.object[1].colorStyle = 'R' + if Map.object[1].color_style == 'N': + Map.object[1].color_style = 'R' else: - Map.object[1].colorStyle = 'N' + Map.object[1].color_style = 'N' else: return {'PASS_THROUGH'} return {'RUNNING_MODAL'} @@ -509,7 +526,7 @@ def key_C(event): def key_E(event): if event.value == 'PRESS': if Map.object[0].focused: - Sun.SP.ObjectGroup = 'ECLIPTIC' + sun.sp.object_group = 'ECLIPTIC' else: return {'PASS_THROUGH'} return {'RUNNING_MODAL'} @@ -556,8 +573,8 @@ def key_S(event): if event.value == 'PRESS': if Map.object[0].focused: Map.showInfo = True if not Map.showInfo else False - Sun.PP.ShowRiseSet = True - Sun.ShowRiseSet = True + sun.PP.ShowRiseSet = True + sun.ShowRiseSet = True else: return {'PASS_THROUGH'} return {'RUNNING_MODAL'} @@ -749,10 +766,10 @@ def mouse_zoom(): def check_time_boundary(): - if Sun.SP.Time > 24.0: - Sun.SP.Time += -24.0 - elif Sun.SP.Time < 0.0: - Sun.SP.Time += 24.0 + if sun.sp.time > 24.0: + sun.sp.time += -24.0 + elif sun.sp.time < 0.0: + sun.sp.time += 24.0 Display.refresh() @@ -762,7 +779,7 @@ def time_change_wheel(action): else: val = -1.0 if not Map.altPress else -0.0166 mf = 1.5 if Map.ctrlPress else 1.0 - Sun.SP.Time += mf * val + sun.sp.time += mf * val check_time_boundary() @@ -774,7 +791,7 @@ def time_change(): sx = 0.0001 if Map.mouse.x > Map.grab.spot.x else -0.0001 else: sx = (Map.mouse.x - Map.grab.spot.x) / mf - Sun.SP.Time += sx + sun.sp.time += sx Map.grab.spot.x = Map.mouse.x check_time_boundary() @@ -799,18 +816,18 @@ def day_change(): def day_change_bounds(wf): - if Sun.SP.Day_of_year + wf > 366: - Sun.SP.Day_of_year = 1 - Sun.SP.Year += 1 - elif Sun.SP.Day_of_year + wf < 1: - Sun.SP.Day_of_year = 366 - Sun.SP.Year -= 1 + if sun.sp.day_of_year + wf > 366: + sun.sp.day_of_year = 1 + sun.sp.year += 1 + elif sun.sp.day_of_year + wf < 1: + sun.sp.day_of_year = 366 + sun.sp.year -= 1 else: - Sun.SP.Day_of_year += wf - dt = (datetime.date(Sun.SP.Year, 1, 1) + - datetime.timedelta(Sun.SP.Day_of_year - 1)) - Sun.SP.Day = dt.day - Sun.SP.Month = dt.month + sun.sp.day_of_year += wf + dt = (datetime.date(sun.sp.year, 1, 1) + + datetime.timedelta(sun.sp.day_of_year - 1)) + sun.sp.day = dt.day + sun.sp.month = dt.month Display.refresh() # --------------------------------------------------------------------------- @@ -861,22 +878,22 @@ def opacity_change_bounds(obj): def X_change_wheel(action): wf = wheel_factor(action) - if Sun.SP.Longitude + wf > 180.0: - Sun.SP.Longitude = -180.0 - elif Sun.SP.Longitude + wf < -180.0: - Sun.SP.Longitude = 180.0 + if sun.sp.longitude + wf > 180.0: + sun.sp.longitude = -180.0 + elif sun.sp.longitude + wf < -180.0: + sun.sp.longitude = 180.0 else: - Sun.SP.Longitude += wf + sun.sp.longitude += wf def Y_change_wheel(action): wf = wheel_factor(action) - if Sun.SP.Latitude + wf > 90.0: - Sun.SP.Latitude = -90.0 - elif Sun.SP.Latitude + wf < -90.0: - Sun.SP.Latitude = 90.0 + if sun.sp.latitude + wf > 90.0: + sun.sp.latitude = -90.0 + elif sun.sp.latitude + wf < -90.0: + sun.sp.latitude = 90.0 else: - Sun.SP.Latitude += wf + sun.sp.latitude += wf def wheel_factor(action): @@ -892,11 +909,11 @@ def wheel_factor(action): def Map_load_callback(self, context): - if Sun.SP.ShowMap and not Map.image.loaded: + if sun.sp.show_map and not Map.image.loaded: Map.glImage = None - if not Map.load_blender_image(Sun.MapName): + if not Map.load_blender_image(sun.MapName): print("Could not load image file: ", Map.image.name) - Sun.SP.ShowMap = False + sun.SP.ShowMap = False if Map.start: def set_region_data(): @@ -933,13 +950,13 @@ def Map_load_callback(self, context): print("Could not get texture in gl_load()") Map.glImage = None Map.image.bindcode = 0 - Sun.SP.ShowMap = False + sun.sp.show_map = False elif Map.textureless: set_region_data() return else: - Sun.SP.ShowMap = False + sun.sp.show_map = False return if Map.stop: @@ -992,10 +1009,10 @@ def Draw_map_callback(self, context): # cylindrical projection with lat/long 0/0 exactly # in the middle of the image. - zLong = theMap.width / 2 - longFac = zLong / 180 - zLat = theMap.height / 2 - latFac = zLat / 90 + zLong = the_map.width / 2 + longFac = z_long / 180 + zLat = the_map.height / 2 + latFac = z_lat / 90 crossChange = True if not Map.action == 'PAN': @@ -1092,8 +1109,8 @@ def Draw_map_callback(self, context): x = theMap.width / 2.0 if crossChange and not Map.lockCrosshair: if Map.action != 'Y': - Sun.SP.Longitude = newLongitude - longitude = (Sun.SP.Longitude * x / 180.0) + x + sun.sp.longitude = newLongitude + longitude = (sun.sp.longitude * x / 180.0) + x bgl.glEnable(bgl.GL_BLEND) bgl.glEnable(bgl.GL_LINES) @@ -1109,8 +1126,8 @@ def Draw_map_callback(self, context): y = theMap.height / 2.0 if crossChange and not Map.lockCrosshair: if Map.action != 'X': - Sun.SP.Latitude = newLatitude - latitude = (Sun.SP.Latitude * y / 90.0) + y + sun.sp.latitude = newLatitude + latitude = (sun.sp.latitude * y / 90.0) + y alpha = 1.0 if Map.action == 'X' else 0.5 color = (0.894, 0.741, .510, alpha) @@ -1134,7 +1151,7 @@ def Draw_map_callback(self, context): bgl.glVertex2f(Lx, Ly) bgl.glEnd() - if not Sun.ShowRiseSet or not Map.lineWidth: + if not sun.ShowRiseSet or not Map.lineWidth: bgl.glDisable(bgl.GL_LINES) bgl.glFlush() return @@ -1159,14 +1176,14 @@ def Draw_map_callback(self, context): py = Ly + latitude radius = 30 + Map.lineWidth * 10 - if Sun.RiseSetOK and Map.lineWidth: + if sun.RiseSetOK and Map.lineWidth: color = (0.2, 0.6, 1.0, 0.9) - angle = -(degToRad(Sun.Sunrise.azimuth) - math.pi / 2) + angle = -(radians(sun.sunrise.azimuth) - math.pi / 2) bgl.glLineWidth(Map.lineWidth) draw_angled_line(color, angle, px, py) color = (0.86, 0.18, 0.18, 0.9) - angle = -(degToRad(Sun.Sunset.azimuth) - math.pi / 2) + angle = -(radians(sun.sunset.azimuth) - math.pi / 2) draw_angled_line(color, angle, px, py) # ------------------------ @@ -1174,18 +1191,18 @@ def Draw_map_callback(self, context): # ------------------------ if Map.textureless: - phi = degToRad(Sun.AzNorth) * -1 + phi = radians(sun.AzNorth) * -1 else: - phi = degToRad(Sun.Azimuth) * -1 - x = math.sin(phi) * math.sin(-Sun.Theta) * (radius + 10) - y = math.sin(Sun.Theta) * math.cos(phi) * (radius + 10) + phi = radians(sun.Azimuth) * -1 + x = math.sin(phi) * math.sin(-sun.Theta) * (radius + 10) + y = math.sin(sun.Theta) * math.cos(phi) * (radius + 10) night = (0.24, 0.29, 0.94, 0.9) day = (0.85, 0.77, 0.60, 0.9) - if Sun.SolarNoon.elevation < 0.0: + if sun.SolarNoon.elevation < 0.0: color = night - elif Sun.Elevation >= Sun.Sunrise.elevation: - if Sun.Time >= Sun.Sunset.time and \ - Sun.Elevation <= Sun.Sunset.elevation: + elif sun.Elevation >= sun.Sunrise.elevation: + if sun.Time >= sun.Sunset.time and \ + sun.Elevation <= sun.Sunset.elevation: color = night else: color = day diff --git a/sun_position/north.py b/sun_position/north.py index 98269ae2..3ef5f822 100644 --- a/sun_position/north.py +++ b/sun_position/north.py @@ -1,87 +1,111 @@ +### 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 -from .shader import Dashed_Shader_3D -from . properties import Sun - -dashedLineShader = gpu.types.GPUShader(Dashed_Shader_3D.vertex_shader, Dashed_Shader_3D.fragment_shader) - -class NorthClass: - - def __init__(self): - self.handler = None - self.isActive = False - - def refresh_screen(self): - #bpy.context.scene.cursor.location.x += 0.0 - bpy.context.area.tag_redraw() - - def activate(self, context): - - if context.area.type == 'PROPERTIES': - self.handler = bpy.types.SpaceView3D.draw_handler_add( - DrawNorth_callback, - #(self, context), 'WINDOW', 'POST_PIXEL') # why changed? - (self, context), 'WINDOW', 'POST_VIEW') - self.isActive = True - self.refresh_screen() - return True - return False - - def deactivate(self): - if self.handler is not None: - bpy.types.SpaceView3D.draw_handler_remove(self.handler, 'WINDOW') - self.handler = None - self.isActive = False - self.refresh_screen() - #if Sun.SP: # why removed? - Sun.SP.ShowNorth = False # indent? - Sun.ShowNorth = False - - -North = NorthClass() - - -def DrawNorth_callback(self, context): - - if not Sun.SP.ShowNorth and North.isActive: - North.deactivate() - return - - # ------------------------------------------------------------------ - # 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. - # ------------------------------------------------------------------ - #color = (0.2, 0.6, 1.0, 0.7) - color = (0.2, 0.6, 1.0, 1) - radius = 100 - angle = -(Sun.NorthOffset - math.pi / 2) - x = math.cos(angle) * radius - y = math.sin(angle) * radius - - #p1, p2 = (0, 0, 0), (x, y, 0) # Start & end of needle - # much removed / changed from original below - bgl.glEnable(bgl.GL_MULTISAMPLE) - bgl.glEnable(bgl.GL_LINE_SMOOTH) - bgl.glEnable(bgl.GL_BLEND) - - bgl.glEnable(bgl.GL_DEPTH_TEST) - bgl.glDepthMask(False) - - bgl.glLineWidth(2) - - - p1 = (0, 0, 0) - p2 = (x/20 , y/20, 0) - coords = [p1, p2] # Start & end of needle - arclengths = [0,(Vector(p1)-Vector(p2)).length] - batch = batch_for_shader(dashedLineShader, 'LINES', {"pos": coords,"arcLength":arclengths}) - - dashedLineShader.bind() - dashedLineShader.uniform_float("finalColor", color) - dashedLineShader.uniform_float("u_Scale", 10) - batch.draw(dashedLineShader) + + +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. + # ------------------------------------------------------------------ + addon_prefs = bpy.context.preferences.addons[__package__].preferences + 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() diff --git a/sun_position/operators.py b/sun_position/operators.py deleted file mode 100644 index 08fe42b6..00000000 --- a/sun_position/operators.py +++ /dev/null @@ -1,313 +0,0 @@ -import bpy -import datetime - -from . properties import * -from . sun_calc import Move_sun -from . north import * -from . map import Map -from . hdr import Hdr - -# --------------------------------------------------------------------------- - - -class ControlClass: - - region = None - handler = None - - def callback(self, os, context): - if Sun.SP.IsActive: - if self.panel_changed(): - Move_sun() - else: - self.remove_handler() - - def activate(self, context): - if context.area.type == 'PROPERTIES': - if Display.ENABLE: - Display.setAction('PANEL') - Sun.SP.IsActive = True - self.region = context.region - self.add_handler(context) - return {'RUNNING_MODAL'} - else: - Display.setAction('ENABLE') - Sun.SP.IsActive = False - Map.deactivate() - Hdr.deactivate() - return {'FINISHED'} - else: - self.report({'WARNING'}, "Context not available") - return {'CANCELLED'} - - def add_handler(self, context): - self.handler = bpy.types.SpaceView3D.draw_handler_add(self.callback, - (self, context), 'WINDOW', 'POST_PIXEL') - - def remove_handler(self): - if self.handler: - bpy.types.SpaceView3D.draw_handler_remove(self.handler, 'WINDOW') - self.handler = None - - def panel_changed(self): - rv = False - sp = Sun.SP - - if not Sun.UseDayMonth and sp.Day_of_year != Sun.Day_of_year: - dt = (datetime.date(sp.Year, 1, 1) + - datetime.timedelta(sp.Day_of_year - 1)) - Sun.Day = dt.day - Sun.Month = dt.month - Sun.Day_of_year = sp.Day_of_year - sp.Day = dt.day - sp.Month = dt.month - rv = True - elif (sp.Day != Sun.Day or sp.Month != Sun.Month): - try: - dt = datetime.date(sp.Year, sp.Month, sp.Day) - sp.Day_of_year = dt.timetuple().tm_yday - Sun.Day = sp.Day - Sun.Month = sp.Month - Sun.Day_of_year = sp.Day_of_year - rv = True - except: - pass - - if Sun.PP.UsageMode == "HDR": - if sp.BindToSun != Sun.BindToSun: - Sun.BindToSun = sp.BindToSun - if Sun.BindToSun: - nt = bpy.context.scene.world.node_tree.nodes - envTex = nt.get(sp.HDR_texture) - if envTex: - if envTex.type == "TEX_ENVIRONMENT": - Sun.Bind.tex_location = envTex.texture_mapping.rotation - Sun.Bind.azStart = sp.HDR_azimuth - obj = bpy.context.view_layer.objects.get(Sun.SunObject) - Sun.HDR_texture = sp.HDR_texture - Sun.Elevation = sp.HDR_elevation - Sun.Azimuth = sp.HDR_azimuth - Sun.Bind.elevation = sp.HDR_elevation - Sun.Bind.azimuth = sp.HDR_azimuth - Sun.SunDistance = sp.SunDistance - return True - if (sp.HDR_elevation != Sun.Bind.elevation or - sp.HDR_azimuth != Sun.Bind.azimuth or - sp.SunDistance != Sun.SunDistance): - Sun.Elevation = sp.HDR_elevation - Sun.Azimuth = sp.HDR_azimuth - Sun.Bind.elevation = sp.HDR_elevation - Sun.Bind.azimuth = sp.HDR_azimuth - Sun.SunDistance = sp.SunDistance - return True - return False - - if (rv or sp.Time != Sun.Time or - sp.TimeSpread != Sun.TimeSpread or - sp.SunDistance != Sun.SunDistance or - sp.Latitude != Sun.Latitude or - sp.Longitude != Sun.Longitude or - sp.UTCzone != Sun.UTCzone or - sp.Year != Sun.Year or - sp.UseSkyTexture != Sun.UseSkyTexture or - sp.SkyTexture != Sun.SkyTexture or - sp.HDR_texture != Sun.HDR_texture or - sp.UseSunObject != Sun.UseSunObject or - sp.SunObject != Sun.SunObject or - sp.UseObjectGroup != Sun.UseObjectGroup or - sp.ObjectGroup != Sun.ObjectGroup or - sp.DaylightSavings != Sun.DaylightSavings or - sp.ShowRefraction != Sun.ShowRefraction or - sp.ShowNorth != Sun.ShowNorth or - sp.NorthOffset != Sun.NorthOffset): - - Sun.Time = sp.Time - Sun.TimeSpread = sp.TimeSpread - Sun.SunDistance = sp.SunDistance - Sun.Latitude = sp.Latitude - Sun.Longitude = sp.Longitude - Sun.UTCzone = sp.UTCzone - Sun.Year = sp.Year - Sun.UseSkyTexture = sp.UseSkyTexture - Sun.SkyTexture = sp.SkyTexture - Sun.HDR_texture = sp.HDR_texture - Sun.UseSunObject = sp.UseSunObject - Sun.SunObject = sp.SunObject - Sun.UseObjectGroup = sp.UseObjectGroup - Sun.ObjectGroup = sp.ObjectGroup - Sun.DaylightSavings = sp.DaylightSavings - Sun.ShowRefraction = sp.ShowRefraction - Sun.ShowNorth = sp.ShowNorth - Sun.NorthOffset = sp.NorthOffset - return True - return False - - -Controller = ControlClass() - -# --------------------------------------------------------------------------- - - -class SunPos_OT_Controller(bpy.types.Operator): - bl_idname = "world.sunpos_controller" - bl_label = "Sun panel event handler" - bl_description = "Enable sun panel" - - def __del__(self): - Stop_all_handlers() - Controller.remove_handler() - Display.setAction('ENABLE') - #try: - Sun.SP.IsActive = False # indent? - #except: - # pass - - def modal(self, context, event): - - if Display.PANEL: - - if Sun.SP.ShowMap: - if not Map.isActive: - if not Map.activate(context): - Sun.SP.ShowMap = False - elif Map.isActive: - Map.deactivate() - - if Sun.SP.ShowHdr: - if not Hdr.isActive: - Sun.SP.BindToSun = False - if not Hdr.activate(context): - Sun.SP.ShowHdr = False - elif Hdr.isActive: - Hdr.deactivate() - - if Sun.SP.ShowNorth: - if not North.isActive: - North.activate(context) - elif North.isActive: - North.deactivate() - - return {'PASS_THROUGH'} - - Display.refresh() - return {'FINISHED'} - - def invoke(self, context, event): - - Sun.verify_ObjectGroup() - Map.init(Sun.PP.MapLocation) - Hdr.init() - retval = Controller.activate(context) - if retval != {'RUNNING_MODAL'}: - return retval - - context.window_manager.modal_handler_add(self) - Sun.PreBlend_handler = SunPos_new_blendfile - bpy.app.handlers.load_pre.append(SunPos_new_blendfile) - Sun.Frame_handler = Frame_handler - bpy.app.handlers.frame_change_pre.append(Frame_handler) - - Display.setAction('PANEL') - Sun.SP.IsActive = True - - return {'RUNNING_MODAL'} - -############################################################################ - - -class SunPos_OT_Map(bpy.types.Operator): - bl_idname = "sunpos.map" - bl_label = "World map" - - def modal(self, context, event): - if Map.view3d_area != context.area or not Sun.SP.ShowMap: - Map.deactivate() - Display.refresh() - return {'FINISHED'} - elif not Display.PANEL: - Stop_all_handlers() - return {'FINISHED'} - return Map.event_controller(context, event) - - def invoke(self, context, event): - context.window_manager.modal_handler_add(self) - Display.refresh() - return {'RUNNING_MODAL'} - -############################################################################ - - -class SunPos_OT_Hdr(bpy.types.Operator): - bl_idname = "sunpos.hdr" - bl_label = "HDR map" - - def modal(self, context, event): - if Hdr.view3d_area != context.area or not Sun.SP.ShowHdr: - Hdr.deactivate() - Display.refresh() - return {'FINISHED'} - elif not Display.PANEL: - Stop_all_handlers() - return {'FINISHED'} - return Hdr.event_controller(context, event) - - def invoke(self, context, event): - context.window_manager.modal_handler_add(self) - Display.refresh() - return {'RUNNING_MODAL'} - -############################################################################ - - -def SunPos_new_blendfile(context): - Stop_all_handlers() - Cleanup_objects() - - -def Cleanup_callback(self, context): - Stop_all_handlers() - Cleanup_objects() - - -def Cleanup_objects(): - try: - #if Sun.SP: # why removed? - Sun.SP.UseObjectGroup = False # indent? - Sun.UseObjectGroup = False - except: - pass - del Sun.Selected_objects[:] - del Sun.Selected_names[:] - Display.setAction('ENABLE') - #if Sun.SP: # why removed? - Sun.SP.IsActive = False # indent? - - -def Stop_all_handlers(): - North.deactivate() - Map.deactivate() - Hdr.deactivate() - - if Sun.Frame_handler is not None: - try: - bpy.app.handlers.frame_change_pre.remove(Frame_handler) - except: - pass - Sun.Frame_handler = None - - if Sun.PreBlend_handler is not None: - try: - bpy.app.handlers.load_pre.remove(SunPos_new_blendfile) - except: - pass - Sun.PreBlend_handler = None - -############################################################################ -# The Frame_handler is called while rendering when the scene changes -# to make sure objects are updated according to any keyframes. -############################################################################ - - -def Frame_handler(context): - Controller.panel_changed() - Move_sun() diff --git a/sun_position/properties.py b/sun_position/properties.py index 14cbd428..11d2eaed 100644 --- a/sun_position/properties.py +++ b/sun_position/properties.py @@ -1,304 +1,243 @@ -import bpy -from bpy.props import ( - StringProperty, EnumProperty, - IntProperty, FloatProperty, - BoolProperty -) -from bpy.types import ( - PropertyGroup -) - -class DisplayAction: - ENABLE = True - PANEL = False - PREFERENCES = False - - def __init__(self): - self.invert_zoom_wheel = False - self.invert_mouse_zoom = False - - def setAction(self, VAL): - if VAL == 'ENABLE': - self.ENABLE = True - self.PANEL = self.PREFERENCES = False - elif VAL == 'PANEL': - self.PANEL = True - self.ENABLE = self.PREFERENCES = False - else: - self.PREFERENCES = True - self.ENABLE = self.PANEL = False - - def refresh(self): - # Touching the cursor forces a screen refresh - #bpy.context.scene.cursor.location.x += 0.0 - bpy.context.area.tag_redraw() - - -Display = DisplayAction() -Display.setAction('ENABLE') +### 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 ##### -############################################################################ -# SunClass is used for storing and comparing changes in panel values -# as well as general traffic direction. -############################################################################ +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 -class SunClass: - - class TazEl: - time = 0.0 - azimuth = 0.0 - elevation = 0.0 - - class CLAMP: - tex_location = None - elevation = 0.0 - azimuth = 0.0 - azStart = 0.0 - azDiff = 0.0 - - Sunrise = TazEl() - Sunset = TazEl() - SolarNoon = TazEl() - RiseSetOK = False - - Bind = CLAMP() - BindToSun = False - PlaceLabel = "Time & Place" - PanelLabel = "Panel Location" - MapName = "WorldMapLR.jpg" - MapLocation = 'VIEWPORT' - - Latitude = 0.0 - Longitude = 0.0 - Elevation = 0.0 - Azimuth = 0.0 - AzNorth = 0.0 - Phi = 0.0 - Theta = 0.0 - LX = 0.0 - LY = 0.0 - LZ = 0.0 - - Month = 0 - Day = 0 - Year = 0 - Day_of_year = 0 - Time = 0.0 - - UTCzone = 0 - SunDistance = 0.0 - TimeSpread = 23.50 - UseDayMonth = True - DaylightSavings = False - ShowRiseSet = True - ShowRefraction = True - NorthOffset = 0.0 - - UseSunObject = False - SunObject = "Sun" - - UseSkyTexture = False - SkyTexture = "Sky Texture" - HDR_texture = "Environment Texture" - - UseObjectGroup = False - ObjectGroup = 'ECLIPTIC' - Selected_objects = [] - Selected_names = [] - ObjectGroup_verified = False - - PreBlend_handler = None - Frame_handler = None - SP = None - PP = None - Counter = 0 - - # ---------------------------------------------------------------------- - # There are times when the object group needs to be deleted such as - # when opening a new file or when objects might be deleted that are - # part of the group. If such is the case, delete the object group. - # ---------------------------------------------------------------------- - - def verify_ObjectGroup(self): - if not self.ObjectGroup_verified: - if len(self.Selected_names) > 0: - names = [x.name for x in bpy.data.objects] - for x in self.Selected_names: - if not x in names: - del self.Selected_objects[:] - del self.Selected_names[:] - break - self.ObjectGroup_verified = True - -Sun = SunClass() ############################################################################ # Sun panel properties ############################################################################ -class SunPosSettings(PropertyGroup): - - IsActive: BoolProperty( - description="True if panel enabled.", - default=False) +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) - ShowMap: BoolProperty( + show_map: BoolProperty( description="Show world map.", - default=False) + default=False, + update=sun_update) - DaylightSavings: BoolProperty( + daylight_savings: BoolProperty( description="Daylight savings time adds 1 hour to standard time.", - default=0) + default=0, + update=sun_update) - ShowRefraction: BoolProperty( + show_refraction: BoolProperty( description="Show apparent sun position due to refraction.", - default=1) + default=1, + update=sun_update) - ShowNorth: BoolProperty( + show_north: BoolProperty( description="Draws line pointing north.", - default=0) + default=0, + update=north_update) - Latitude: FloatProperty( + latitude: FloatProperty( attr="", name="Latitude", description="Latitude: (+) Northern (-) Southern", soft_min=-90.000, soft_max=90.000, step=3.001, - default=40.000, precision=3) + default=40.000, precision=3, + update=sun_update) - Longitude: FloatProperty( + longitude: FloatProperty( attr="", name="Longitude", description="Longitude: (-) West of Greenwich (+) East of Greenwich", soft_min=-180.000, soft_max=180.000, - step=3.001, default=1.000, precision=3) + step=3.001, default=1.000, precision=3, + update=sun_update) - Month: IntProperty( + co_parser: StringProperty( + attr="", + name="Enter coordinates", + description="Enter coordinates from an online map", + update=parse_coordinates) + + month: IntProperty( attr="", name="Month", description="", - min=1, max=12, default=6) + min=1, max=12, default=6, + update=sun_update) - Day: IntProperty( + day: IntProperty( attr="", name="Day", description="", - min=1, max=31, default=21) + min=1, max=31, default=21, + update=sun_update) - Year: IntProperty( + year: IntProperty( attr="", name="Year", description="", - min=1800, max=4000, default=2012) + min=1800, max=4000, default=2012, + 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( + day_of_year: IntProperty( attr="", name="Day of year", description="", - min=1, max=366, default=1) + min=1, max=366, default=1, + update=sun_update) - UTCzone: IntProperty( + UTC_zone: IntProperty( attr="", name="UTC zone", description="Time zone: Difference from Greenwich England in hours.", - min=0, max=12, default=0) + min=-12, max=12, default=0, + update=sun_update) - Time: FloatProperty( + time: FloatProperty( attr="", name="Time", description="", precision=4, - soft_min=0.00, soft_max=23.9999, step=1.00, default=12.00) + soft_min=0.00, soft_max=23.9999, step=1.00, default=12.00, + update=sun_update) - NorthOffset: FloatProperty( + north_offset: FloatProperty( attr="", name="", description="North offset in degrees or radians " "from scene's units settings", unit="ROTATION", - soft_min=-3.14159265, soft_max=3.14159265, step=10.00, default=0.00) + soft_min=-3.14159265, soft_max=3.14159265, step=10.00, default=0.00, + update=sun_update) - SunDistance: FloatProperty( + sun_distance: FloatProperty( attr="", name="Distance", description="Distance to sun from XYZ axes intersection.", unit="LENGTH", - soft_min=1, soft_max=3000.00, step=10.00, default=50.00) + soft_min=1, soft_max=3000.00, step=10.00, default=50.00, + update=sun_update) + + use_sun_object: BoolProperty( + description="Enable sun positioning of named lamp or mesh", + default=False, + update=sun_update) + + sun_object: PointerProperty( + type=bpy.types.Object, + # default="Sun", + 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 group of objects to be positioned.", + default=False, + update=sun_update) - UseSunObject: BoolProperty( - description="Enable sun positioning of named light or mesh", - default=False) + object_collection: PointerProperty( + type=bpy.types.Collection, + description="Collection of objects used for analemma", + update=sun_update) - SunObject: StringProperty( - default="Sun", - name="theSun", - description="Name of sun object") + 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) - UseSkyTexture: BoolProperty( + use_sky_texture: BoolProperty( description="Enable use of Cycles' " "sky texture. World nodes must be enabled, " "then set color to Sky Texture.", - default=False) + default=False, + update=sun_update) - SkyTexture: StringProperty( + sky_texture: StringProperty( default="Sky Texture", name="sunSky", - description="Name of sky texture to be used") + description="Name of sky texture to be used", + update=sun_update) - ShowHdr: BoolProperty( - description="Click to set sun location on environment texture", - default=False) - - HDR_texture: StringProperty( + hdr_texture: StringProperty( default="Environment Texture", name="envSky", description="Name of texture to use. World nodes must be enabled " - "and color set to Environment Texture") + "and color set to Environment Texture", + update=sun_update) - HDR_azimuth: FloatProperty( + hdr_azimuth: FloatProperty( attr="", name="Rotation", description="Rotation angle of sun and environment texture " "in degrees or radians from scene's units settings", unit="ROTATION", - step=1.00, default=0.00, precision=3) + step=10.0, + default=0.0, precision=3, + update=sun_update) - HDR_elevation: FloatProperty( + hdr_elevation: FloatProperty( attr="", name="Elevation", description="Elevation angle of sun", - step=3.001, - default=0.000, precision=3) + unit="ROTATION", + step=10.0, + default=0.0, precision=3, + update=sun_update) - BindToSun: BoolProperty( + bind_to_sun: BoolProperty( description="If true, Environment texture moves with sun.", - default=False) - - UseObjectGroup: BoolProperty( - description="Allow a group of objects to be positioned.", - default=False) + default=False, + update=sun_update) - TimeSpread: FloatProperty( + time_spread: FloatProperty( attr="", name="Time Spread", description="Time period in which to spread object group", precision=4, - soft_min=1.00, soft_max=24.00, step=1.00, default=23.00) + soft_min=1.00, soft_max=24.00, step=1.00, default=23.00, + update=sun_update) - ObjectGroup: EnumProperty( - name="Display type", - description="Show object group on ecliptic or as analemma", - items=( - ('ECLIPTIC', "On the Ecliptic", ""), - ('ANALEMMA', "As and Analemma", ""), - ), - default='ECLIPTIC') - - Location: StringProperty( + location: StringProperty( default="view3d", name="location", - description="panel location") + description="panel location", + update=sun_update) ############################################################################ @@ -306,18 +245,10 @@ class SunPosSettings(PropertyGroup): ############################################################################ -class SunPosPreferences(PropertyGroup): +class SunPosAddonPreferences(AddonPreferences): + bl_idname = __package__ - UsageMode: EnumProperty( - name="Usage mode", - description="operate in normal mode or environment texture mode", - items=( - ('NORMAL', "Normal", ""), - ('HDR', "Sun + HDR texture", ""), - ), - default='NORMAL') - - MapLocation: EnumProperty( + map_location: EnumProperty( name="Map location", description="Display map in viewport or world panel", items=( @@ -326,43 +257,69 @@ class SunPosPreferences(PropertyGroup): ), default='VIEWPORT') - UseOneColumn: BoolProperty( - description="Set panel to use one column.", - default=False) - - UseTimePlace: BoolProperty( - description="Show time/place presets.", + use_time_place: BoolProperty( + description="Show time/place presets", default=False) - UseObjectGroup: BoolProperty( - description="Use object group option.", + use_object_collection: BoolProperty( + description="Use object collection", default=True) - ShowDMS: BoolProperty( - description="Show lat/long degrees,minutes,seconds labels.", + show_dms: BoolProperty( + description="Show lat/long degrees, minutes, seconds labels", default=True) - ShowNorth: BoolProperty( - description="Show north offset choice and slider.", - default=True) + show_north: BoolProperty( + description="Show north offset choice and slider", + default=True, + update=sun_update) - ShowRefraction: BoolProperty( - description="Show sun refraction choice.", - default=True) + show_refraction: BoolProperty( + description="Show sun refraction choice", + default=True, + update=sun_update) - ShowAzEl: BoolProperty( - description="Show azimuth and solar elevation info.", + show_az_el: BoolProperty( + description="Show azimuth and solar elevation info", default=True) - ShowDST: BoolProperty( - description="Show daylight savings time choice.", + show_dst: BoolProperty( + description="Show daylight savings time choice", default=True) - ShowRiseSet: BoolProperty( - description="Show sunrise and sunset.", + show_rise_set: BoolProperty( + description="Show sunrise and sunset", default=True) - MapName: StringProperty( - default="WorldMap.jpg", - name="WorldMap", - description="Name of world map") + # Uncomment this if you add other map images + # map_name: StringProperty( + # default="WorldMap.jpg", + # name="WorldMap", + # description="Name of world map") + + def draw(self, context): + layout = self.layout + + box = layout.box() + col = box.column() + + # Uncomment this if you add other map images + # row = col.row() + # row.label(text="World map options:") + # row.operator_menu_enum('world.wmp_operator', + # 'map_presets', text=Sun.map_name) + # col.separator() + + col.prop(self, "map_location") + col.separator() + + col.label(text="Show or use:") + flow = col.grid_flow(columns=0, even_columns=True, even_rows=False, align=False) + flow.prop(self, "use_time_place", text="Time/place presets") + flow.prop(self, "use_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_dst", text="Daylight savings time") + flow.prop(self, "show_rise_set", text="Sunrise, sunset") diff --git a/sun_position/sun_calc.py b/sun_position/sun_calc.py index d9d13794..ed6107d7 100644 --- a/sun_position/sun_calc.py +++ b/sun_position/sun_calc.py @@ -1,25 +1,257 @@ +### 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 * 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 -from . properties import * + 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 + 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 to place them in the sky. +# +############################################################################ -Degrees = "\xb0" +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 + + locX = math.sin(sun.phi) * math.sin(-sun.theta) * sun_props.sun_distance + locY = math.sin(sun.theta) * math.cos(sun.phi) * sun_props.sun_distance + locZ = math.cos(sun.theta) * sun_props.sun_distance + sun_props.sun_object.location = locX, locY, locZ + sun_props.sun_object.rotation_euler = (sun_props.hdr_elevation - pi/2, + 0, -sun_props.hdr_azimuth) + return + + local_time = sun_props.time + if sun_props.longitude > 0.0: + zone = sun_props.UTC_zone * -1 + else: + zone = sun_props.UTC_zone + if sun_props.daylight_savings: + zone -= 1 -def format_time(theTime, UTCzone, daylightSavings, longitude): - hh = str(int(theTime)) - min = (theTime - int(theTime)) * 60 + north_offset = degrees(sun_props.north_offset) + + if addon_prefs.show_rise_set: + calc_sunrise_sunset(1) + calc_sunrise_sunset(0) + + 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: + 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) + if obj.type == 'LIGHT': + obj.rotation_euler = ( + (math.radians(sun.elevation - 90), 0, + math.radians(-sun.az_north))) + + # Sun collection + if (addon_prefs.use_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): + addon_prefs = context.preferences.addons[__package__].preferences + 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 + + +def format_time(the_time, UTC_zone, daylight_savings, longitude): + 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) - zone = UTCzone + zone = UTC_zone if(longitude < 0): zone *= -1 - if daylightSavings: + if daylight_savings: zone += 1 - gt = int(theTime) - zone + gt = int(the_time) - zone if gt < 0: gt = 24 + gt @@ -31,9 +263,9 @@ def format_time(theTime, UTCzone, daylightSavings, longitude): "UTC: " + gt + ":" + mm + ":" + ss) -def format_hms(theTime): - hh = str(int(theTime)) - min = (theTime - int(theTime)) * 60 +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) @@ -41,162 +273,24 @@ def format_hms(theTime): return (hh + ":" + mm + ":" + ss) -def format_lat_long(latLong, isLatitude): - hh = str(abs(int(latLong))) - min = abs((latLong - int(latLong)) * 60) +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 latLong == 0: - coordTag = " " + if lat_long == 0: + coord_tag = " " else: - if isLatitude: - coordTag = " N" if latLong > 0 else " S" + if is_latitude: + coord_tag = " N" if lat_long > 0 else " S" else: - coordTag = " E" if latLong > 0 else " W" - - return hh + Degrees + " " + mm + "' " + ss + '"' + coordTag + coord_tag = " E" if lat_long > 0 else " W" -############################################################################ -# -# PlaceSun() will cycle through all the selected objects of type LIGHT or -# MESH and call setSunPosition to place them in the sky. -# -############################################################################ + return hh + "° " + mm + "' " + ss + '"' + coord_tag -def Move_sun(): - if Sun.PP.UsageMode == "HDR": - Sun.Theta = math.pi / 2 - degToRad(Sun.Elevation) - Sun.Phi = -Sun.Azimuth - - locX = math.sin(Sun.Phi) * math.sin(-Sun.Theta) * Sun.SunDistance - locY = math.sin(Sun.Theta) * math.cos(Sun.Phi) * Sun.SunDistance - locZ = math.cos(Sun.Theta) * Sun.SunDistance - - try: - nt = bpy.context.scene.world.node_tree.nodes - envTex = nt.get(Sun.HDR_texture) - if Sun.Bind.azDiff and envTex.texture_mapping.rotation.z == 0.0: - envTex.texture_mapping.rotation.z = Sun.Bind.azDiff - - if envTex and Sun.BindToSun: - az = Sun.Azimuth - if Sun.Bind.azStart < az: - taz = az - Sun.Bind.azStart - else: - taz = -(Sun.Bind.azStart - az) - envTex.texture_mapping.rotation.z += taz - Sun.Bind.azStart = az - - string = Sun.SunObject - newString = string[3:1000] - obj = bpy.context.view_layer.objects.get(newString) - - try: - obj.location = locX, locY, locZ - except: - pass - - if obj.type == 'LIGHT': - obj.rotation_euler = ( - (math.radians(Sun.Elevation - 90), 0, -Sun.Azimuth)) - except: - pass - return True - totalObjects = len(Sun.Selected_objects) - - localTime = Sun.Time - if Sun.Longitude > 0.0: - zone = Sun.UTCzone * -1 - else: - zone = Sun.UTCzone - if Sun.DaylightSavings: - zone -= 1 - - northOffset = radToDeg(Sun.NorthOffset) - - if Sun.ShowRiseSet: - calcSunrise_Sunset(1) - calcSunrise_Sunset(0) - - getSunPosition(None, localTime, Sun.Latitude, Sun.Longitude, - northOffset, zone, Sun.Month, Sun.Day, Sun.Year, - Sun.SunDistance) - - if Sun.UseSkyTexture: - try: - nt = bpy.context.scene.world.node_tree.nodes - sunTex = nt.get(Sun.SkyTexture) - if sunTex: - locX = math.sin(Sun.Phi) * math.sin(-Sun.Theta) - locY = math.sin(Sun.Theta) * math.cos(Sun.Phi) - locZ = math.cos(Sun.Theta) - sunTex.texture_mapping.rotation.z = 0.0 - sunTex.sun_direction = locX, locY, locZ - - except: - pass - - if Sun.UseSunObject: - try: - obj = bpy.context.view_layer.objects.get(Sun.SunObject) - setSunPosition(obj, Sun.SunDistance) - if obj.type == 'LIGHT': - obj.rotation_euler = ( - (math.radians(Sun.Elevation - 90), 0, - math.radians(-Sun.AzNorth))) - except: - pass - - if totalObjects < 1 or not Sun.UseObjectGroup: - return False - - if Sun.ObjectGroup == 'ECLIPTIC': - # Ecliptic - if totalObjects > 1: - timeIncrement = Sun.TimeSpread / (totalObjects - 1) - localTime = localTime + timeIncrement * (totalObjects - 1) - else: - timeIncrement = Sun.TimeSpread - - for obj in Sun.Selected_objects: - mesh = obj.type - if mesh == 'LIGHT' or mesh == 'MESH': - getSunPosition(obj, - localTime, - Sun.Latitude, Sun.Longitude, - northOffset, zone, - Sun.Month, Sun.Day, Sun.Year, - Sun.SunDistance) - setSunPosition(obj, Sun.SunDistance) - localTime = localTime - timeIncrement - if mesh == 'LIGHT': - obj.rotation_euler = ( - (math.radians(Sun.Elevation - 90), 0, - math.radians(-Sun.AzNorth))) - else: - # Analemma - dayIncrement = 365 / totalObjects - day = Sun.Day_of_year + dayIncrement * (totalObjects - 1) - for obj in Sun.Selected_objects: - mesh = obj.type - if mesh == 'LIGHT' or mesh == 'MESH': - dt = (datetime.date(Sun.Year, 1, 1) + - datetime.timedelta(day - 1)) - getSunPosition(obj, localTime, - Sun.Latitude, Sun.Longitude, - northOffset, zone, dt.month, dt.day, - Sun.Year, Sun.SunDistance) - setSunPosition(obj, Sun.SunDistance) - day -= dayIncrement - if mesh == 'LIGHT': - obj.rotation_euler = ( - (math.radians(Sun.Elevation - 90), 0, - math.radians(-Sun.AzNorth))) - - return True ############################################################################ # @@ -216,36 +310,39 @@ def Move_sun(): ############################################################################ -def getSunPosition(obj, localTime, latitude, longitude, northOffset, - utcZone, month, day, year, distance): +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 - utcTime = localTime + utcZone # Set Greenwich Meridian Time + utc_time = local_time + utc_zone # Set Greenwich Meridian Time if latitude > 89.93: # Latitude 90 and -90 gives - latitude = degToRad(89.93) # erroneous results so nudge it + latitude = radians(89.93) # erroneous results so nudge it elif latitude < -89.93: - latitude = degToRad(-89.93) + latitude = radians(-89.93) else: - latitude = degToRad(latitude) + latitude = radians(latitude) - t = julianTimeFromY2k(utcTime, year, month, day) + t = julian_time_from_y2k(utc_time, year, month, day) - e = degToRad(obliquityCorrection(t)) - L = apparentLongitudeOfSun(t) - solarDec = sunDeclination(e, L) - eqtime = calcEquationOfTime(t) + e = radians(obliquity_correction(t)) + L = apparent_longitude_of_sun(t) + solar_dec = sun_declination(e, L) + eqtime = calc_equation_of_time(t) - timeCorrection = (eqtime - 4 * longitude) + 60 * utcZone - trueSolarTime = ((utcTime - utcZone) * 60.0 + timeCorrection) % 1440 + time_correction = (eqtime - 4 * longitude) + 60 * utc_zone + true_solar_time = ((utc_time - utc_zone) * 60.0 + time_correction) % 1440 - hourAngle = trueSolarTime / 4.0 - 180.0 - if hourAngle < -180.0: - hourAngle += 360.0 + hour_angle = true_solar_time / 4.0 - 180.0 + if hour_angle < -180.0: + hour_angle += 360.0 - csz = (math.sin(latitude) * math.sin(solarDec) + - math.cos(latitude) * math.cos(solarDec) * - math.cos(degToRad(hourAngle))) + 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: @@ -253,15 +350,15 @@ def getSunPosition(obj, localTime, latitude, longitude, northOffset, zenith = math.acos(csz) - azDenom = math.cos(latitude) * math.sin(zenith) + az_denom = math.cos(latitude) * math.sin(zenith) - if abs(azDenom) > 0.001: - azRad = ((math.sin(latitude) * - math.cos(zenith)) - math.sin(solarDec)) / azDenom - if abs(azRad) > 1.0: - azRad = -1.0 if (azRad < 0.0) else 1.0 - azimuth = 180.0 - radToDeg(math.acos(azRad)) - if hourAngle > 0.0: + 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 @@ -269,79 +366,77 @@ def getSunPosition(obj, localTime, latitude, longitude, northOffset, if azimuth < 0.0: azimuth = azimuth + 360.0 - exoatmElevation = 90.0 - radToDeg(zenith) + exoatm_elevation = 90.0 - degrees(zenith) - if exoatmElevation > 85.0: - refractionCorrection = 0.0 - else: - te = math.tan(degToRad(exoatmElevation)) - if exoatmElevation > 5.0: - refractionCorrection = ( - 58.1 / te - 0.07 / (te ** 3) + 0.000086 / (te ** 5)) - elif (exoatmElevation > -0.575): - s1 = (-12.79 + exoatmElevation * 0.711) - s2 = (103.4 + exoatmElevation * (s1)) - s3 = (-518.2 + exoatmElevation * (s2)) - refractionCorrection = 1735.0 + exoatmElevation * (s3) + if addon_prefs.show_refraction and sun_props.show_refraction: + if exoatm_elevation > 85.0: + refraction_correction = 0.0 else: - refractionCorrection = -20.774 / te - - refractionCorrection = refractionCorrection / 3600 + 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) - if Sun.ShowRefraction: - solarElevation = 90.0 - (radToDeg(zenith) - refractionCorrection) else: - solarElevation = 90.0 - radToDeg(zenith) + solar_elevation = 90.0 - degrees(zenith) - solarAzimuth = azimuth + northOffset + solar_azimuth = azimuth + if addon_prefs.show_north: + solar_azimuth += north_offset - Sun.AzNorth = solarAzimuth + sun.az_north = solar_azimuth - Sun.Theta = math.pi / 2 - degToRad(solarElevation) - Sun.Phi = degToRad(solarAzimuth) * -1 - Sun.Azimuth = azimuth - Sun.Elevation = solarElevation + sun.theta = math.pi / 2 - radians(solar_elevation) + sun.phi = radians(solar_azimuth) * -1 + sun.azimuth = azimuth + sun.elevation = solar_elevation -def setSunPosition(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 +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 #---------------------------------------------- - try: - obj.location = locX, locY, locZ - except: - pass + obj.location = locX, locY, locZ -def calcSunriseSetUTC(rise, jd, latitude, longitude): - t = calcTimeJulianCent(jd) - eqTime = calcEquationOfTime(t) - solarDec = calcSunDeclination(t) - hourAngle = calcHourAngleSunrise(latitude, solarDec) +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: - hourAngle = -hourAngle - delta = longitude + radToDeg(hourAngle) - timeUTC = 720 - (4.0 * delta) - eqTime - return timeUTC + hour_angle = -hour_angle + delta = longitude + degrees(hour_angle) + time_UTC = 720 - (4.0 * delta) - eq_time + return time_UTC -def calcSunDeclination(t): - e = degToRad(obliquityCorrection(t)) - L = apparentLongitudeOfSun(t) - solarDec = sunDeclination(e, L) - return solarDec +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 calcHourAngleSunrise(lat, solarDec): - latRad = degToRad(lat) - HAarg = (math.cos(degToRad(90.833)) / - (math.cos(latRad) * math.cos(solarDec)) - - math.tan(latRad) * math.tan(solarDec)) +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: @@ -350,53 +445,53 @@ def calcHourAngleSunrise(lat, solarDec): return HA -def calcSolarNoon(jd, longitude, timezone, dst): - t = calcTimeJulianCent(jd - longitude / 360.0) - eqTime = calcEquationOfTime(t) - noonOffset = 720.0 - (longitude * 4.0) - eqTime - newt = calcTimeJulianCent(jd + noonOffset / 1440.0) - eqTime = calcEquationOfTime(newt) +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 - noonLocal = (nv - (longitude * 4.0) - eqTime + (timezone * 60.0)) % 1440 - Sun.SolarNoon.time = noonLocal / 60.0 + noon_local = (nv- (longitude * 4.0) - eq_time + (timezone * 60.0)) % 1440 + sun.solar_noon.time = noon_local / 60.0 -def calcSunrise_Sunset(rise): - if Sun.Longitude > 0: - zone = Sun.UTCzone * -1 +def calc_sunrise_sunset(rise): + if sun.longitude > 0: + zone = sun.UTC_zone * -1 else: - zone = Sun.UTCzone - - jd = getJulianDay(Sun.Year, Sun.Month, Sun.Day) - timeUTC = calcSunriseSetUTC(rise, jd, Sun.Latitude, Sun.Longitude) - newTimeUTC = calcSunriseSetUTC(rise, jd + timeUTC / 1440.0, - Sun.Latitude, Sun.Longitude) - timeLocal = newTimeUTC + (-zone * 60.0) - tl = timeLocal / 60.0 - getSunPosition(None, tl, Sun.Latitude, Sun.Longitude, 0.0, - zone, Sun.Month, Sun.Day, Sun.Year, - Sun.SunDistance) - if Sun.DaylightSavings: - timeLocal += 60.0 - tl = timeLocal / 60.0 + 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.daylight_savings: + time_local += 60.0 + tl = time_local / 60.0 if tl < 0.0: tl += 24.0 elif tl > 24.0: tl -= 24.0 if rise: - Sun.Sunrise.time = tl - Sun.Sunrise.azimuth = Sun.Azimuth - Sun.Sunrise.elevation = Sun.Elevation - calcSolarNoon(jd, Sun.Longitude, -zone, Sun.DaylightSavings) - getSunPosition(None, Sun.SolarNoon.time, Sun.Latitude, Sun.Longitude, - 0.0, zone, Sun.Month, Sun.Day, Sun.Year, - Sun.SunDistance) - Sun.SolarNoon.elevation = Sun.Elevation + sun.sunrise.time = tl + sun.sunrise.azimuth = sun.azimuth + sun.sunrise.elevation = sun.elevation + calc_solar_noon(jd, sun.longitude, -zone, sun.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 + 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 @@ -404,14 +499,14 @@ def calcSunrise_Sunset(rise): ########################################################################## -def julianTimeFromY2k(utcTime, year, month, day): +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 = getJulianDay(year, month, day) - return ((jd + (utcTime / 24)) - epoch) / century + jd = get_julian_day(year, month, day) + return ((jd + (utc_time / 24)) - epoch) / century -def getJulianDay(year, month, day): +def get_julian_day(year, month, day): if month <= 2: year -= 1 month += 12 @@ -422,21 +517,21 @@ def getJulianDay(year, month, day): return jd -def calcTimeJulianCent(jd): +def calc_time_julian_cent(jd): t = (jd - 2451545.0) / 36525.0 return t -def sunDeclination(e, L): +def sun_declination(e, L): return (math.asin(math.sin(e) * math.sin(L))) -def calcEquationOfTime(t): - epsilon = obliquityCorrection(t) - ml = degToRad(meanLongitudeSun(t)) - e = eccentricityEarthOrbit(t) - m = degToRad(meanAnomalySun(t)) - y = math.tan(degToRad(epsilon) / 2.0) +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) @@ -445,59 +540,51 @@ def calcEquationOfTime(t): 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 (radToDeg(etime) * 4) + return (degrees(etime) * 4) -def obliquityCorrection(t): - ec = obliquityOfEcliptic(t) +def obliquity_correction(t): + ec = obliquity_of_ecliptic(t) omega = 125.04 - 1934.136 * t - return (ec + 0.00256 * math.cos(degToRad(omega))) + return (ec + 0.00256 * math.cos(radians(omega))) -def obliquityOfEcliptic(t): +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 trueLongitudeOfSun(t): - return (meanLongitudeSun(t) + equationOfSunCenter(t)) +def true_longitude_of_sun(t): + return (mean_longitude_sun(t) + equation_of_sun_center(t)) -def calcSunApparentLong(t): - o = trueLongitudeOfSun(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(degToRad(omega)) + lamb = o - 0.00569 - 0.00478 * math.sin(radians(omega)) return lamb -def apparentLongitudeOfSun(t): - return (degToRad(trueLongitudeOfSun(t) - 0.00569 - 0.00478 * - math.sin(degToRad(125.04 - 1934.136 * t)))) +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 meanLongitudeSun(t): +def mean_longitude_sun(t): return (280.46646 + 36000.76983 * t + 0.0003032 * t ** 2) % 360 -def equationOfSunCenter(t): - m = degToRad(meanAnomalySun(t)) +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 meanAnomalySun(t): +def mean_anomaly_sun(t): return (357.52911 + t * (35999.05029 - 0.0001537 * t)) -def eccentricityEarthOrbit(t): +def eccentricity_earth_orbit(t): return (0.016708634 - 0.000042037 * t - 0.0000001267 * t ** 2) - - -def degToRad(angleDeg): - return (math.pi * angleDeg / 180.0) - - -def radToDeg(angleRad): - return (180.0 * angleRad / math.pi) diff --git a/sun_position/ui_sun.py b/sun_position/ui_sun.py index d83a3d93..a923ce9e 100644 --- a/sun_position/ui_sun.py +++ b/sun_position/ui_sun.py @@ -1,679 +1,347 @@ +### 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 import datetime +from math import radians + +from .sun_calc import (format_lat_long, format_time, format_hms, + move_sun, 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.daylight_savings", + "sun_props.latitude", + "sun_props.longitude", + ] + + # where to store the preset + preset_subdir = "operator/sun_position" -from . properties import * -from . operators import * -from . sun_calc import Degrees, format_lat_long, degToRad, \ - format_time, format_hms, Move_sun -#--------------------------------------------------------------------------- +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) + # Why these in particular? + presets = {"north_pole.py": [6, 21, 12.0, 0, 90.000, 0.0000, False], + "equator_vernal_equinox.py": [3, 20, 12.0, 0, 0.0000, 0.0000, False], + "rio_de_janeiro_may_10th.py": [5, 10, 12.0, 3, -22.9002, -43.2334, False], + "tokyo_august_20th.py": [8, 20, 12.0, 9, 35.7002, 139.7669, False], + "boston_autumnal_equinox.py": [9, 22, 12.0, 5, 42.3502, -71.0500, True], + "boston_vernal_equinox.py": [3, 20, 12.0, 5, 42.3502, -71.0500, True], + "honolulu_winter_solstice.py": [12, 21, 12.0, 10, 21.3001, -157.850, False], + "honolulu_summer_solstice.py": [6, 21, 12.0, 10, 21.3001, -157.850, 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.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 SPOS_PT_Panel(bpy.types.Panel): +# ------------------------------------------------------------------- - #bl_idname = "panel.SunPos_world" +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'} - @classmethod - def poll(cls, context): - if context.area.type == 'PROPERTIES': - return 1 - return 0 - - def enable(self, layout): - row = layout.row() - split = row.split(factor=.90) - colL = split.column() - colL.alignment = 'LEFT' - colR = split.column() - colR.alignment = 'RIGHT' - colL.operator('world.sunpos_controller', text='Enable', icon='PLAY') - colR.operator('world.sunpos_preferences', text='', icon='PREFERENCES') - Map.init_zoom_preference = True - - def disable(self, context, layout): - p = context.scene.SunPos_pref_property - if Map.init_zoom_preference: - Map.zoom_preferences(bpy.context.preferences.inputs.invert_zoom_wheel, - bpy.context.preferences.inputs.invert_mouse_zoom) - Hdr.zoom_preferences(bpy.context.preferences.inputs.invert_zoom_wheel, - bpy.context.preferences.inputs.invert_mouse_zoom) - row = self.layout.row() - if p.UseOneColumn: - col1 = row.column() - col2 = col1 - elif p.UseTimePlace: - split = row.split(factor=.3) - col1 = split.column() - col2 = split.column() - else: - col1 = row.column() - - col1.operator('world.sunpos_controller', text='Disable', icon='X') - if p.UseTimePlace: - col2.operator_menu_enum('world.pdp_operator', - 'timePlacePresets', text=Sun.PlaceLabel) - - def show_preferences(self, context, layout): - p = context.scene.SunPos_pref_property - box = self.layout.box() - row = box.row(align=True) - row.alignment = 'CENTER' - row.label(text="Preferences") - col = box.column(align=True) - col.label(text="Usage mode:") - col.props_enum(p, "UsageMode") - col.separator() - - if p.UsageMode == "NORMAL": - cb = col.box() - tr = cb.row() - rr = tr.row(align=True) - cs = rr.split() - cl = cs.column() - cr = cs.column() - - cl.label(text="World map options:") - cl.operator_menu_enum('world.wmp_operator', - 'mapPresets', text=Sun.MapName) - cr.label(text="Display map in:") - cr.props_enum(p, "MapLocation") - col.separator() - col.label(text="Show or use:") - col.alignment = 'LEFT' - col.prop(p, "UseOneColumn", text="Single column mode") - col.prop(p, "UseTimePlace", text="Time/place presets") - col.prop(p, "UseObjectGroup", text="Object group") - col.prop(p, "ShowDMS", text="D\xb0 M' S\"") - col.prop(p, "ShowNorth", text="North offset") - col.prop(p, "ShowRefraction", text="Refraction") - col.prop(p, "ShowAzEl", text="Azimuth, elevation") - col.prop(p, "ShowDST", text="Daylight savings time") - col.prop(p, "ShowRiseSet", text="Sunrise, sunset") - col.separator() - col.operator('world.sunpos_pref_done', text='Done', icon='QUIT') - Sun.ShowRiseSet = p.ShowRiseSet - def draw(self, context): - sp = context.scene.SunPos_property - p = context.scene.SunPos_pref_property + sp = context.scene.sun_pos_properties + p = context.preferences.addons[__package__].preferences layout = self.layout - if Display.PREFERENCES: - self.show_preferences(context, layout) - elif Display.ENABLE: - Sun.SP = sp - Sun.PP = p - self.enable(layout) - else: - Sun.SP = sp - Sun.PP = p - self.disable(context, layout) - if Display.PANEL: - if Sun.SP.IsActive: - self.draw_panel(context, sp, p, layout) - else: - Display.setAction('ENABLE') + self.draw_panel(context, sp, p, layout) def draw_panel(self, context, sp, p, layout): - if p.UsageMode == "HDR": - self.draw_environ_panel(context, sp, p, layout) - elif p.UseOneColumn: - self.draw_one_column(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_two_columns(context, sp, p, layout) + self.draw_normal_mode_panel(context, sp, p, layout) - def draw_environ_panel(self, context, sp, p, layout): + def draw_environ_mode_panel(self, context, sp, p, layout): box = self.layout.box() - toprow = box.row() - row = toprow.row(align=False) - row.alignment = 'CENTER' - col = row.column(align=True) + flow = box.grid_flow(row_major=True, columns=0, even_columns=True, + even_rows=False, align=False) have_texture = False - try: - col.separator() - col.label(text="Use environment texture:") - col.prop_search(sp, "HDR_texture", - context.scene.world.node_tree, "nodes", text="") - col.separator() - try: - nt = bpy.context.scene.world.node_tree.nodes - envTex = nt.get(sp.HDR_texture) - if envTex: - if envTex.type == "TEX_ENVIRONMENT": - if envTex.image != None: - have_texture = True - if Sun.Bind.azDiff == 0: - if envTex.texture_mapping.rotation.z == 0.0: - Sun.Bind.azDiff = degToRad(90.0) - except: - pass - except: - pass - try: - col.label(text="Use sun object:") - col.prop_search(sp, "SunObject", - context.view_layer, "objects", text="") - #Sun.SunObject = sp.SunObject # why removed? - except: - pass - - - col.separator() - col.prop(sp, "SunDistance") - if not sp.BindToSun: - col.prop(sp, "HDR_elevation") - col.prop(sp, "HDR_azimuth") + col = flow.column() + col.label(text="Environment texture:") + col.prop_search(sp, "hdr_texture", + context.scene.world.node_tree, "nodes", text="") col.separator() - toprow1 = box.row() - row1 = toprow1.row(align=False) - row1.alignment = 'CENTER' - if not sp.BindToSun: - row1.prop(sp, "BindToSun", toggle=True, icon="CONSTRAINT", - text="Bind Texture to Sun ") - else: - row1.prop(sp, "BindToSun", toggle=True, icon="CONSTRAINT", - text="Release binding") - - toprow2 = box.row() - row2 = toprow2.row(align=False) - row2.alignment = 'CENTER' - row2.prop(sp, "ShowHdr", text="Sync Sun to Texture", toggle=True, icon='LIGHT_SUN') - if have_texture == False: - row2.enabled = False - elif sp.BindToSun: - row2.enabled = False - else: - row2.enabled = True - if have_texture == False: - row1.enabled = False - elif sp.ShowHdr: - row1.enabled = False - else: - row1.enabled = True - - def draw_one_column(self, context, sp, p, layout): - box = self.layout.box() - toprow = box.row() - row = toprow.row(align=False) - row.alignment = 'CENTER' - - col = row.column(align=True) - col.prop(sp, "UseSkyTexture", text="Cycles sky") - if sp.UseSkyTexture: - try: - col.prop_search(sp, "SkyTexture", - context.scene.world.node_tree, "nodes", text="") - except: - pass - col.prop(sp, "UseSunObject", text="Use object") - if(sp.UseSunObject): - try: - col.prop_search(sp, "SunObject", + col = flow.column() + col.label(text="Sun object:") + col.prop_search(sp, "sun_object", context.view_layer, "objects", text="") - except: - pass - - if p.UseObjectGroup: - col.prop(sp, "UseObjectGroup", text="Object group") - if sp.UseObjectGroup: - Sun.verify_ObjectGroup() - if len(Sun.Selected_objects) > 0: - col.operator('world.sunpos_clear_objects', - 'Release Group') - col.separator() - if(sp.ObjectGroup == 'ECLIPTIC'): - col.prop(sp, "TimeSpread") - col.props_enum(sp, "ObjectGroup") - else: - col.operator('world.sunpos_set_objects', - text='Set Object Group') - else: - Sun.ObjectGroup_verified = False - - row = layout.row() - row.prop(sp, "ShowMap", text="Show Map", toggle=True, icon='WORLD') - - box = self.layout.box() - toprow = box.row() - row = toprow.row(align=True) - row.alignment = 'CENTER' - col = row.column(align=True) - col.alignment = 'CENTER' - col.prop(sp, "Latitude") - if p.ShowDMS: - col.label(text=format_lat_long(sp.Latitude, True)) - col.prop(sp, "Longitude") - if p.ShowDMS: - col.label(text=format_lat_long(sp.Longitude, False)) - cb = col.box() - tr = cb.row() - rr = tr.row(align=True) - if p.ShowNorth: - cs = rr.split() - cl = cs.column() - cr = cs.column() - cl.prop(sp, "ShowNorth", text="Show North", toggle=True) - cr.prop(sp, "NorthOffset") - col.prop(sp, "SunDistance") - else: - rr.prop(sp, "SunDistance") - - if p.ShowRefraction: - col.prop(sp, "ShowRefraction", text="Show refraction") - if p.ShowAzEl: - col.label(text="Azimuth: " + - str(round(Sun.Azimuth, 3)) + Degrees) - col.label(text="Elevation: " + - str(round(Sun.Elevation, 3)) + Degrees) - box = self.layout.box() - toprow = box.row() - row = toprow.row(align=False) - row.alignment = 'CENTER' - col = row.column(align=False) - col.alignment = 'CENTER' - tr = col.row() - rr = tr.row(align=True) - - if Sun.UseDayMonth: - cs = rr.split(factor=.82) - cl = cs.column() - cl.alignment = 'LEFT' - cr = cs.column() - cr.alignment = 'RIGHT' - cl.prop(sp, "Month") - cr.operator('world.sunpos_day_range',text= '', - icon='SORTTIME') - col.prop(sp, "Day") - else: - cs = rr.split(factor=.90) - cl = cs.column() - cr = cs.column() - cl.alignment = 'LEFT' - cr.alignment = 'RIGHT' - cl.prop(sp, "Day_of_year") - cr.operator('world.sunpos_day_range', '', - icon='SORTTIME') - - col.prop(sp, "Year") - col.prop(sp, "UTCzone", slider=True) - col.prop(sp, "Time") - lt, ut = format_time(sp.Time, sp.UTCzone, - sp.DaylightSavings, sp.Longitude) - col.label(text=lt, icon='TIME') - col.label(text=" " + ut, icon='PREVIEW_RANGE') - - if p.ShowRiseSet: - if Sun.Sunrise.time == Sun.Sunset.time or \ - Sun.Sunrise.elevation > -0.4 or Sun.Sunset.elevation > -0.4: - Sun.RiseSetOK = False - tsr = "Sunrise: --------" - tss = " Sunset: --------" - else: - Sun.RiseSetOK = True - 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') - - if p.ShowDST: - col.prop(sp, "DaylightSavings", text="Daylight Savings") + col.separator() - def draw_two_columns(self, context, sp, p, layout): - box = self.layout.box() - toprow = box.row() - row = toprow.row(align=True) - row.alignment = 'CENTER' - col = row.column(align=True) - split = col.split(factor=.5) - cL = split.column() - cR = split.column() - cL.alignment = 'LEFT' - cR.alignment = 'RIGHT' - - cLi = cRi = 1 - cL.prop(sp, "UseSkyTexture", text="Cycles sky") - if sp.UseSkyTexture: - try: - cL.prop_search(sp, "SkyTexture", - context.scene.world.node_tree, "nodes", text="") - cLi += 1 - except: - pass - cR.prop(sp, "UseSunObject", text="Use object") - if(sp.UseSunObject): - try: - cR.prop_search(sp, "SunObject", - context.view_layer, "objects", text="") - cRi += 1 - except: - pass - - if p.UseObjectGroup: - cLi += 1 - cL.prop(sp, "UseObjectGroup", text="Object group") - if sp.UseObjectGroup: - Sun.verify_ObjectGroup() - if len(Sun.Selected_objects) > 0: - while cRi < cLi: - cR.label(text=" ") - cRi += 1 - cL.operator('world.sunpos_clear_objects', - text='Release Group') - cL.label(text=" ") - if(sp.ObjectGroup == 'ECLIPTIC'): - cR.prop(sp, "TimeSpread") - cR.props_enum(sp, "ObjectGroup") - else: - cL.operator('world.sunpos_set_objects', - text='Set Object Group') - else: - Sun.ObjectGroup_verified = False + 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() - box = self.layout.box() - toprow = box.row() - row = toprow.row(align=False) - row.alignment = 'CENTER' - col = row.column(align=False) - #col.prop(sp, "ShowMap", text="Show Map", toggle=True, icon='WORLD') # why commented out? - distanceSet = False - - if p.ShowDMS: - split = col.split(factor=.5) - cL = split.column() - cR = split.column() - cL.alignment = 'LEFT' - cR.alignment = 'RIGHT' - - cL.prop(sp, "Latitude") - cR.label(text=format_lat_long(sp.Latitude, True)) - cL.prop(sp, "Longitude") - cR.label(text=format_lat_long(sp.Longitude, False)) - if p.ShowNorth: - cL.prop(sp, "ShowNorth", text="Show North", toggle=True) - cR.prop(sp, "NorthOffset") - if p.ShowAzEl: - cL.label(text="Azimuth: " + - str(round(Sun.Azimuth, 3)) + Degrees) - cR.label(text="Elevation: " + - str(round(Sun.Elevation, 3)) + Degrees) - if p.ShowRefraction: - cL.prop(sp, "ShowRefraction", text="Show refraction") - cR.prop(sp, "SunDistance") - distanceSet = True + col = flow.column(align=True) + row1 = col.row() + if sp.bind_to_sun: + prop_text="Release binding" else: - cb = col.box() - tr = cb.row() - rr = tr.row(align=True) - cs = rr.split() - cL = cs.column(align=True) - cR = cs.column(align=True) - cL.prop(sp, "Latitude") - cR.prop(sp, "Longitude") - if p.ShowNorth: - col.separator() - cL.prop(sp, "ShowNorth", text="Show North", toggle=True) - cR.prop(sp, "NorthOffset") - if p.ShowAzEl: - cL.label(text="Azimuth: " + - str(round(Sun.Azimuth, 3)) + Degrees) - cR.label(text="Elevation: " + - str(round(Sun.Elevation, 3)) + Degrees) - if p.ShowRefraction: - cL.prop(sp, "ShowRefraction", text="Show refraction") - cR.prop(sp, "SunDistance") - distanceSet = True - if not distanceSet: - col.prop(sp, "SunDistance") + 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.use_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() - toprow = box.row() - row = toprow.row(align=False) - row.alignment = 'CENTER' - if Sun.UseDayMonth: - split = row.split(factor=.5) - colL = split.column() - colMid = split.column() - colMsplit = colMid.split(factor=.82) - colM = colMsplit.column() - colR = colMsplit.column() - colL.prop(sp, "Month") - colM.prop(sp, "Day") - colR.operator('world.sunpos_day_range', text= '', - icon='SORTTIME') - else: - split = row.split(factor=.50) - colL = split.column() - colL.alignment = 'LEFT' - colMid = split.column() - colMsplit = colMid.split(factor=.90) - colM = colMsplit.column() - colR = colM.column() - colR.alignment = 'RIGHT' - colL.prop(sp, "Day_of_year") - colR.operator('world.sunpos_day_range',text= '', - icon='SORTTIME') - - colL.prop(sp, "Year") - colM.prop(sp, "UTCzone", slider=True) - lt, ut = format_time(sp.Time, - sp.UTCzone, - sp.DaylightSavings, - sp.Longitude) - colL.prop(sp, "Time") - colM.label(text=lt, icon='TIME') - if p.ShowDST: - colL.prop(sp, "DaylightSavings", text="Daylight Savings") - colM.label(text=" " + ut, icon='PREVIEW_RANGE') - if p.ShowRiseSet: - if Sun.Sunrise.time == Sun.Sunset.time or \ - Sun.Sunrise.elevation > -0.4 or Sun.Sunset.elevation > -0.4: - Sun.RiseSetOK = False - tsr = "Sunrise: --------" - tss = " Sunset: --------" - else: - Sun.RiseSetOK = True - sr = format_hms(Sun.Sunrise.time) - ss = format_hms(Sun.Sunset.time) - tsr = "Sunrise: " + sr - tss = " Sunset: " + ss - colL.label(text=tsr, icon='LIGHT_SUN') - if p.ShowDST: - colM.label(text=tss, icon='SOLO_ON') - else: - colL.label(text=tss, icon='SOLO_ON') + 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() -class SunPos_OT_Preferences(bpy.types.Operator): - bl_idname = "world.sunpos_preferences" - bl_label = "Set preferences" - bl_description = "Press to set your preferences" - - def execute(self, context): - Display.setAction('PREFERENCES') - return {'FINISHED'} + col = flow.column() + if p.use_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() + box.prop(sp, "show_map", text="Show Map", toggle=True, icon='WORLD') -class SunPos_OT_PreferencesDone(bpy.types.Operator): - bl_idname = "world.sunpos_pref_done" - bl_label = "Preferences done" - bl_description = "Press to complete your preferences" + 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() - def execute(self, context): - Display.setAction('ENABLE') - p = context.scene.SunPos_pref_property - Sun.UsageMode = p.UsageMode - Sun.MapLocation = p.MapLocation - if not p.UseObjectGroup: - sp = context.scene.SunPos_property - sp.UseObjectGroup = False - Sun.UseObjectGroup = False - return {'FINISHED'} + 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() -class SunPos_OT_DayRange(bpy.types.Operator): - bl_idname = "world.sunpos_day_range" - bl_label = "toggleDayRange" - bl_description = "Toggle day or (month / day) range" + if p.show_refraction: + col = flow.column() + col.prop(sp, "show_refraction", text="Show refraction") + col.separator() - def execute(self, context): - sp = context.scene.SunPos_property - - if Sun.UseDayMonth: - try: - dt = datetime.date(sp.Year, sp.Month, sp.Day) - sp.Day_of_year = dt.timetuple().tm_yday - except: - pass - Sun.UseDayMonth = False - else: - Sun.UseDayMonth = True - dt = (datetime.date(sp.Year, 1, 1) + - datetime.timedelta(sp.Day_of_year - 1)) - sp.Day = dt.day - sp.Month = dt.month - return {'FINISHED'} + col = flow.column() + col.prop(sp, "sun_distance") -class SunPos_OT_SetObjectGroup(bpy.types.Operator): - bl_idname = "world.sunpos_set_objects" - bl_label = "Set object group" - bl_description = "Set currently selected objects as object group" + box = self.layout.box() + flow = box.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False) - def execute(self, context): - del Sun.Selected_objects[:] - del Sun.Selected_names[:] - if (len(bpy.context.selected_objects) > 0): - Sun.Selected_names = [x.name for x in bpy.context.selected_objects] - Sun.Selected_objects = bpy.context.selected_objects - bpy.ops.object.select_all(action='DESELECT') - Move_sun() + 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: - self.report({'WARNING'}, "No objects selected") - return {'FINISHED'} - - -class SunPos_OT_ClearObjectGroup(bpy.types.Operator): - bl_idname = "world.sunpos_clear_objects" - bl_label = "Release object group" - bl_description = "Release object group" + col.prop(sp, "month") + col.prop(sp, "day") + col.prop(sp, "year") + col.separator() - def execute(self, context): - bpy.ops.object.select_all(action='DESELECT') - Sun.ObjectGroup_verified = False - Sun.verify_ObjectGroup() - try: - for x in Sun.Selected_objects: - x.select_set(True) - except: - pass - del Sun.Selected_objects[:] - del Sun.Selected_names[:] - return {'FINISHED'} + col = flow.column(align=True) + lt, ut = format_time(sp.time, + sp.UTC_zone, + sp.daylight_savings, + sp.longitude) + col.prop(sp, "time") + col.prop(sp, "UTC_zone") + if p.show_dst: + col.prop(sp, "daylight_savings", text="Daylight Savings") + col.separator() -# --------------------------------------------------------------------------- -# Choice List of places, month and day at 12:00 noon -# --------------------------------------------------------------------------- - - -class SunPos_OT_TimePlace(bpy.types.Operator): - bl_idname = "world.pdp_operator" - bl_label = "Place & Day Presets" - - #----------- Description --------- M D UTC Lat Long DaySav - pdp = [["North Pole, Summer Solstice", 6, 21, 0, 90.000, 0.0000, False], - ["Equator, Vernal Equinox", 3, 20, 0, 0.0000, 0.0000, False], - ["Rio de Janeiro, May 10th", 5, 10, 3, -22.9002, -43.2334, False], - ["Tokyo, August 20th", 8, 20, 9, 35.7002, 139.7669, False], - ["Boston, Autumnal Equinox", 9, 22, 5, 42.3502, -71.0500, True], - ["Boston, Vernal Equinox", 3, 20, 5, 42.3502, -71.0500, True], - ["Honolulu, Winter Solstice", 12, 21, 10, 21.3001, -157.850, False], - ["Honolulu, Summer Solstice", 6, 21, 10, 21.3001, -157.850, False]] - - from bpy.props import EnumProperty - - timePlacePresets: EnumProperty( - name="Time & place presets", - description="Preset Place & Day", - items=( - ("7", pdp[7][0], ""), - ("6", pdp[6][0], ""), - ("5", pdp[5][0], ""), - ("4", pdp[4][0], ""), - ("3", pdp[3][0], ""), - ("2", pdp[2][0], ""), - ("1", pdp[1][0], ""), - ("0", pdp[0][0], ""), - ), - default="4") + col = flow.column(align=True) + col.alignment = 'CENTER' + col.label(text=lt, icon='TIME') + col.label(text=" " + ut, icon='PREVIEW_RANGE') + col.separator() - def execute(self, context): - sp = context.scene.SunPos_property - pdp = self.pdp - i = int(self.properties.timePlacePresets) - it = pdp[i] - Sun.PlaceLabel = it[0] - sp.Month = it[1] - sp.Day = it[2] - sp.Time = 12.00 - sp.UTCzone = it[3] - sp.Latitude = it[4] - sp.Longitude = it[5] - sp.DaylightSavings = it[6] - dt = datetime.date(sp.Year, sp.Month, sp.Day) - sp.Day_of_year = dt.timetuple().tm_yday - # Force screen update - Display.refresh() + col = flow.column(align=True) + col.alignment = 'CENTER' + if p.show_rise_set: + if (sun.sunrise.time == sun.sunset.time + or sun.sunrise.elevation > -0.4 + or sun.sunset.elevation > -0.4): + sun.rise_set_ok = False + tsr = "sunrise: --------" + tss = " sunset: --------" + else: + sun.rise_set_ok = True + 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') - return {'FINISHED'} -# --------------------------------------------------------------------------- +# ------------------------------------------------------------------- # Choice List of world maps -# --------------------------------------------------------------------------- - - -class SunPos_OT_MapChoice(bpy.types.Operator): - bl_idname = "world.wmp_operator" - bl_label = "World map files" - """ - wmp = [["1536 x 768", "WorldMap.jpg"], - ["768 x 384", "WorldMapLR.jpg"], - ["512 x 256", "WorldMapLLR.jpg"], - ["Textureless", "None"]] - """ - # S.L. provide one single optimized map < 100k - wmp = [["1536 x 768", "World.jpg"], - ["Textureless", "None"]] - from bpy.props import EnumProperty - - mapPresets: EnumProperty( - name="World map presets", - description="world map files", - items=( - # ("3", wmp[3][0], ""), - #("2", wmp[2][0], ""), - ("1", wmp[1][0], ""), - ("0", wmp[0][0], ""), - ), - default="1") - - def execute(self, context): - sp = context.scene.SunPos_property - wmp = self.wmp - i = int(self.properties.mapPresets) - sp.MapName = wmp[i] - Sun.MapName = wmp[i][1] - - return {'FINISHED'} +# +# Uncomment this if you add other map images +# ------------------------------------------------------------------- + + +# class SUNPOS_OT_MapChoice(bpy.types.Operator): +# bl_idname = "world.wmp_operator" +# bl_label = "World map files" +# """ +# wmp = [["1536 x 768", "WorldMap.jpg"], +# ["768 x 384", "WorldMapLR.jpg"], +# ["512 x 256", "WorldMapLLR.jpg"], +# ["Textureless", "None"]] +# """ +# # S.L. provide one single optimized map < 100k +# wmp = [["1536 x 768", "World.jpg"], +# ["Textureless", "None"]] +# from bpy.props import EnumProperty + +# mapPresets: EnumProperty( +# name="World map presets", +# description="world map files", +# items=( +# # ("3", wmp[3][0], ""), +# #("2", wmp[2][0], ""), +# ("1", wmp[1][0], ""), +# ("0", wmp[0][0], ""), +# ), +# default="1") + +# def execute(self, context): +# sp = context.scene.sun_pos_properties +# wmp = self.wmp +# i = int(self.properties.mapPresets) +# sp.map_name = wmp[i] +# Sun.MapName = wmp[i][1] + +# return {'FINISHED'} -- GitLab