New feature to set view from sun for solar analysis

This commit is contained in:
Dion Moult
2024-07-17 17:36:50 +10:00
parent 70f252988a
commit 607f79f2f5
4 changed files with 102 additions and 29 deletions
@@ -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,
@@ -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"}
@@ -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,
)
@@ -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")