From 607f79f2f51ae04364e0114a0ab62a10f32625b4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 17 Jul 2024 17:36:50 +1000 Subject: [PATCH] New feature to set view from sun for solar analysis --- .../blenderbim/bim/module/light/__init__.py | 3 +- .../blenderbim/bim/module/light/operator.py | 38 +++++++---- .../blenderbim/bim/module/light/prop.py | 65 ++++++++++++++++--- .../blenderbim/bim/module/light/ui.py | 25 +++++-- 4 files changed, 102 insertions(+), 29 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/light/__init__.py b/src/blenderbim/blenderbim/bim/module/light/__init__.py index c24cc4d89a..cb2009806b 100644 --- a/src/blenderbim/blenderbim/bim/module/light/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/light/__init__.py @@ -24,8 +24,9 @@ classes = ( operator.ExportOBJ, operator.ImportLatLong, operator.ImportTrueNorth, + operator.MoveSunPathTo3DCursor, operator.RadianceRender, - operator.VisualiseShadows, + operator.ViewFromSun, prop.BIMSolarProperties, prop.RadianceExporterProperties, ui.BIM_PT_radiance_exporter, diff --git a/src/blenderbim/blenderbim/bim/module/light/operator.py b/src/blenderbim/blenderbim/bim/module/light/operator.py index 6613f02da5..c333564263 100644 --- a/src/blenderbim/blenderbim/bim/module/light/operator.py +++ b/src/blenderbim/blenderbim/bim/module/light/operator.py @@ -28,7 +28,6 @@ import blenderbim.tool as tool from pathlib import Path from typing import Union from blenderbim.bim.module.light.data import SolarData -from blenderbim.bim.module.light.decorator import SolarDecorator class ExportOBJ(bpy.types.Operator): @@ -275,19 +274,32 @@ class ImportLatLong(bpy.types.Operator): return {"FINISHED"} -class VisualiseShadows(bpy.types.Operator): - bl_idname = "bim.visualise_shadows" - bl_label = "Visualise Shadows" - bl_description = "Enables a visual style to display shadows easily" +class MoveSunPathTo3DCursor(bpy.types.Operator): + bl_idname = "bim.move_sun_path_to_3d_cursor" + bl_label = "Move Sun Path To 3D Cursor" + bl_description = "Shifts the visualisation of the Sun Path to the 3D cursor" bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.render.engine = "BLENDER_WORKBENCH" - context.scene.display.shading.light = "FLAT" - context.scene.display.shading.show_shadows = True - context.scene.display.shadow_focus = 1.0 - context.scene.view_settings.view_transform = "Standard" # Preserve shading colours - space = tool.Blender.get_view3d_space() - space.shading.type = "RENDERED" - SolarDecorator.install(bpy.context) + props = context.scene.BIMSolarProperties + props.sun_path_origin = bpy.context.scene.cursor.location + tool.Blender.update_viewport() + return {"FINISHED"} + + +class ViewFromSun(bpy.types.Operator): + bl_idname = "bim.view_from_sun" + bl_label = "View From Sun" + bl_description = "Views your model as if you were looking from the perspective of the sun" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + if not (camera := bpy.data.objects.get("SunPathCamera")): + camera = bpy.data.objects.new("SunPathCamera", bpy.data.cameras.new("SunPathCamera")) + camera.data.type = "ORTHO" + camera.data.ortho_scale = 100 # The default of 6m is too small + bpy.context.scene.collection.objects.link(camera) + tool.Blender.activate_camera(camera) + props = context.scene.BIMSolarProperties + props.hour = props.hour # Just to refresh camera position return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/light/prop.py b/src/blenderbim/blenderbim/bim/module/light/prop.py index f70dfa87fc..89ce115c10 100644 --- a/src/blenderbim/blenderbim/bim/module/light/prop.py +++ b/src/blenderbim/blenderbim/bim/module/light/prop.py @@ -21,11 +21,13 @@ import pytz import tzfpy import datetime import sun_position.sun_calc +import blenderbim.tool as tool from math import radians, pi -from mathutils import Euler, Vector, Matrix -from bpy.props import IntProperty, StringProperty, EnumProperty, FloatProperty, FloatVectorProperty +from mathutils import Euler, Vector, Matrix, Quaternion +from bpy.props import IntProperty, StringProperty, EnumProperty, FloatProperty, FloatVectorProperty, BoolProperty from bpy.types import PropertyGroup from blenderbim.bim.module.light.data import SolarData +from blenderbim.bim.module.light.decorator import SolarDecorator def get_sites(self, context): @@ -67,10 +69,35 @@ def update_sun_path_size(self, context): update_sun_path() +def update_display_shadows(self, context): + if self.display_shadows: + update_sun_path() + context.scene.render.engine = "BLENDER_WORKBENCH" + context.scene.display.shading.light = "FLAT" + context.scene.display.shading.show_shadows = True + context.scene.display.shading.show_object_outline = True + context.scene.display.shadow_focus = 1.0 + context.scene.view_settings.view_transform = "Standard" # Preserve shading colours + space = tool.Blender.get_view3d_space() + space.shading.type = "RENDERED" + else: + space = tool.Blender.get_view3d_space() + space.shading.type = "SOLID" + + +def update_display_sun_path(self, context): + if self.display_sun_path: + update_sun_path() + SolarDecorator.install(bpy.context) + else: + SolarDecorator.uninstall() + + def update_sun_path(): props = bpy.context.scene.BIMSolarProperties sun_props = bpy.context.scene.sun_pos_properties - timezone = pytz.timezone(tzfpy.get_tz(props.longitude, props.latitude)) + props.timezone = tzfpy.get_tz(props.longitude, props.latitude) + timezone = pytz.timezone(props.timezone) dt = datetime.datetime(sun_props.year, sun_props.month, sun_props.day, props.hour, props.minute) local_time = timezone.localize(dt, is_dst=None) sun_props.use_daylight_savings = bool(local_time.dst()) @@ -92,15 +119,22 @@ def update_sun_path(): # sun_vector.z = max(0, sun_vector.z) # Light direction is a bit weird? mat = Matrix(((-1.0, 0.0, 0.0, 0.0), (0.0, 0, 1.0, 0.0), (-0.0, -1.0, 0, 0.0), (0.0, 0.0, 0.0, 1.0))).inverted() + rotation_euler = Euler((elevation - pi / 2, 0, -azimuth)) + rotation_quaternion = rotation_euler.to_quaternion() + if sun_vector.z < 0: bpy.context.scene.display.light_direction = mat @ Vector((0, 0, 1)) else: - rotation_euler = Euler((elevation - pi / 2, 0, -azimuth)) - bpy.context.scene.display.light_direction = mat @ ( - rotation_euler.to_quaternion() @ Vector((0, 0, -1)) - ) + bpy.context.scene.display.light_direction = mat @ (rotation_quaternion @ Vector((0, 0, -1))) SolarData.data["sun"] = sun_vector + if obj := bpy.data.objects.get("SunPathCamera"): + obj.matrix_world.translation = sun_vector + z180_quaternion = Quaternion((0, 0, 1), radians(180)) + obj.rotation_mode = "QUATERNION" + obj.rotation_quaternion = rotation_quaternion @ z180_quaternion + obj.data.ortho_scale = props.sun_path_size * 2 + class RadianceExporterProperties(PropertyGroup): @@ -154,11 +188,24 @@ class BIMSolarProperties(PropertyGroup): sites: EnumProperty(items=get_sites, name="Sites") latitude: FloatProperty(name="Latitude", min=-90, max=90, update=update_latlong) longitude: FloatProperty(name="Longitude", min=-180, max=180, update=update_latlong) + timezone: StringProperty(name="Timezone", default="Etc/GMT") true_north: FloatProperty(name="True North", min=-180, max=180, update=update_true_north) month: IntProperty(name="Month", min=1, max=12, default=1, update=update_date) day: IntProperty(name="Date", min=1, max=31, default=1, update=update_date) - hour: IntProperty(name="Hour", min=0, max=23, update=update_hourminute) + hour: IntProperty(name="Hour", min=0, max=23, default=12, update=update_hourminute) minute: IntProperty(name="Minute", min=0, max=59, update=update_hourminute) sun_position: FloatVectorProperty(name="Sun Position", subtype="XYZ", default=(0, 0, 0)) - sun_path_origin: FloatVectorProperty(name="Sun Path Origin", subtype="XYZ", default=(10, 0, 0)) + sun_path_origin: FloatVectorProperty(name="Sun Path Origin", subtype="XYZ", default=(0, 0, 0)) sun_path_size: FloatProperty(name="Sun Path Size", min=0.1, default=50, update=update_sun_path_size) + display_shadows: BoolProperty( + name="Display Shadows", + default=False, + description="Enables a visual style to display shadows easily", + update=update_display_shadows, + ) + display_sun_path: BoolProperty( + name="Display Sun Path", + default=False, + description="Displays analemmas and sun position", + update=update_display_sun_path, + ) diff --git a/src/blenderbim/blenderbim/bim/module/light/ui.py b/src/blenderbim/blenderbim/bim/module/light/ui.py index 169ebfa3d7..a4bc9d6afa 100644 --- a/src/blenderbim/blenderbim/bim/module/light/ui.py +++ b/src/blenderbim/blenderbim/bim/module/light/ui.py @@ -75,6 +75,7 @@ class BIM_PT_solar(bpy.types.Panel): bl_region_type = 'WINDOW' bl_context = "scene" bl_parent_id = "BIM_PT_tab_solar_analysis" + bl_options = {"HIDE_HEADER"} def draw(self, context): if not SolarData.is_loaded: @@ -100,7 +101,6 @@ class BIM_PT_solar(bpy.types.Panel): row = self.layout.row(align=True) row.prop(props, "true_north") - row.prop(sun_props, "show_north", icon="HIDE_ON" if sun_props.show_north else "HIDE_OFF", text="") if SolarData.data["true_north"] is not None: row.operator("bim.import_true_north", icon="IMPORT", text="") @@ -125,7 +125,13 @@ class BIM_PT_solar(bpy.types.Panel): row.prop(props, "hour") row.prop(props, "minute") - row = self.layout.row(align=True) + col = self.layout.column(align=True) + box = col.box() + row = box.row() + row.alignment = "CENTER" + row.label(text=props.timezone) + + row = col.row(align=True) box = row.box() local_time = format_time(sun_props.time, sun_props.use_daylight_savings) @@ -146,11 +152,18 @@ class BIM_PT_solar(bpy.types.Panel): row2 = box.row() row2.label(text=f"Sunset: {sunset}") - row = self.layout.row(align=True) + col = self.layout.column(align=True) + row = col.row(align=True) + row.prop(props, "display_sun_path", icon="LIGHT_SUN") row.prop(props, "sun_path_size") - row = self.layout.row() + if props.display_sun_path: + row = col.row() + row.operator("bim.move_sun_path_to_3d_cursor") + + row = self.layout.row(align=True) + row.prop(props, "display_shadows", icon="SHADING_RENDERED") row.prop(context.scene.display.shading, "shadow_intensity", text="Shadow Intensity") - row = self.layout.row() - row.operator("bim.visualise_shadows", text="Visualise Shadows") + row = self.layout.row(align=True) + row.operator("bim.view_from_sun", icon="LIGHT_HEMI")