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.ExportOBJ,
operator.ImportLatLong, operator.ImportLatLong,
operator.ImportTrueNorth, operator.ImportTrueNorth,
operator.MoveSunPathTo3DCursor,
operator.RadianceRender, operator.RadianceRender,
operator.VisualiseShadows, operator.ViewFromSun,
prop.BIMSolarProperties, prop.BIMSolarProperties,
prop.RadianceExporterProperties, prop.RadianceExporterProperties,
ui.BIM_PT_radiance_exporter, ui.BIM_PT_radiance_exporter,
@@ -28,7 +28,6 @@ import blenderbim.tool as tool
from pathlib import Path from pathlib import Path
from typing import Union from typing import Union
from blenderbim.bim.module.light.data import SolarData from blenderbim.bim.module.light.data import SolarData
from blenderbim.bim.module.light.decorator import SolarDecorator
class ExportOBJ(bpy.types.Operator): class ExportOBJ(bpy.types.Operator):
@@ -275,19 +274,32 @@ class ImportLatLong(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class VisualiseShadows(bpy.types.Operator): class MoveSunPathTo3DCursor(bpy.types.Operator):
bl_idname = "bim.visualise_shadows" bl_idname = "bim.move_sun_path_to_3d_cursor"
bl_label = "Visualise Shadows" bl_label = "Move Sun Path To 3D Cursor"
bl_description = "Enables a visual style to display shadows easily" bl_description = "Shifts the visualisation of the Sun Path to the 3D cursor"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
context.scene.render.engine = "BLENDER_WORKBENCH" props = context.scene.BIMSolarProperties
context.scene.display.shading.light = "FLAT" props.sun_path_origin = bpy.context.scene.cursor.location
context.scene.display.shading.show_shadows = True tool.Blender.update_viewport()
context.scene.display.shadow_focus = 1.0 return {"FINISHED"}
context.scene.view_settings.view_transform = "Standard" # Preserve shading colours
space = tool.Blender.get_view3d_space()
space.shading.type = "RENDERED" class ViewFromSun(bpy.types.Operator):
SolarDecorator.install(bpy.context) 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"} return {"FINISHED"}
@@ -21,11 +21,13 @@ import pytz
import tzfpy import tzfpy
import datetime import datetime
import sun_position.sun_calc import sun_position.sun_calc
import blenderbim.tool as tool
from math import radians, pi from math import radians, pi
from mathutils import Euler, Vector, Matrix from mathutils import Euler, Vector, Matrix, Quaternion
from bpy.props import IntProperty, StringProperty, EnumProperty, FloatProperty, FloatVectorProperty from bpy.props import IntProperty, StringProperty, EnumProperty, FloatProperty, FloatVectorProperty, BoolProperty
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from blenderbim.bim.module.light.data import SolarData from blenderbim.bim.module.light.data import SolarData
from blenderbim.bim.module.light.decorator import SolarDecorator
def get_sites(self, context): def get_sites(self, context):
@@ -67,10 +69,35 @@ def update_sun_path_size(self, context):
update_sun_path() 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(): def update_sun_path():
props = bpy.context.scene.BIMSolarProperties props = bpy.context.scene.BIMSolarProperties
sun_props = bpy.context.scene.sun_pos_properties 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) dt = datetime.datetime(sun_props.year, sun_props.month, sun_props.day, props.hour, props.minute)
local_time = timezone.localize(dt, is_dst=None) local_time = timezone.localize(dt, is_dst=None)
sun_props.use_daylight_savings = bool(local_time.dst()) 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) # sun_vector.z = max(0, sun_vector.z)
# Light direction is a bit weird? # 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() 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: if sun_vector.z < 0:
bpy.context.scene.display.light_direction = mat @ Vector((0, 0, 1)) bpy.context.scene.display.light_direction = mat @ Vector((0, 0, 1))
else: else:
rotation_euler = Euler((elevation - pi / 2, 0, -azimuth)) bpy.context.scene.display.light_direction = mat @ (rotation_quaternion @ Vector((0, 0, -1)))
bpy.context.scene.display.light_direction = mat @ (
rotation_euler.to_quaternion() @ Vector((0, 0, -1))
)
SolarData.data["sun"] = sun_vector 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): class RadianceExporterProperties(PropertyGroup):
@@ -154,11 +188,24 @@ class BIMSolarProperties(PropertyGroup):
sites: EnumProperty(items=get_sites, name="Sites") sites: EnumProperty(items=get_sites, name="Sites")
latitude: FloatProperty(name="Latitude", min=-90, max=90, update=update_latlong) latitude: FloatProperty(name="Latitude", min=-90, max=90, update=update_latlong)
longitude: FloatProperty(name="Longitude", min=-180, max=180, 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) 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) 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) 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) minute: IntProperty(name="Minute", min=0, max=59, update=update_hourminute)
sun_position: FloatVectorProperty(name="Sun Position", subtype="XYZ", default=(0, 0, 0)) 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) 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_region_type = 'WINDOW'
bl_context = "scene" bl_context = "scene"
bl_parent_id = "BIM_PT_tab_solar_analysis" bl_parent_id = "BIM_PT_tab_solar_analysis"
bl_options = {"HIDE_HEADER"}
def draw(self, context): def draw(self, context):
if not SolarData.is_loaded: if not SolarData.is_loaded:
@@ -100,7 +101,6 @@ class BIM_PT_solar(bpy.types.Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "true_north") 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: if SolarData.data["true_north"] is not None:
row.operator("bim.import_true_north", icon="IMPORT", text="") 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, "hour")
row.prop(props, "minute") 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() box = row.box()
local_time = format_time(sun_props.time, sun_props.use_daylight_savings) 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 = box.row()
row2.label(text=f"Sunset: {sunset}") 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.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.prop(context.scene.display.shading, "shadow_intensity", text="Shadow Intensity")
row = self.layout.row() row = self.layout.row(align=True)
row.operator("bim.visualise_shadows", text="Visualise Shadows") row.operator("bim.view_from_sun", icon="LIGHT_HEMI")