mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-31 17:06:29 +00:00
New panel to manage solar analysis setup (location, date, and time)
This commit is contained in:
@@ -169,6 +169,7 @@ classes = [
|
|||||||
# Services and systems
|
# Services and systems
|
||||||
ui.BIM_PT_tab_services,
|
ui.BIM_PT_tab_services,
|
||||||
ui.BIM_PT_tab_zones,
|
ui.BIM_PT_tab_zones,
|
||||||
|
ui.BIM_PT_tab_solar_analysis,
|
||||||
# Structural analysis
|
# Structural analysis
|
||||||
ui.BIM_PT_tab_structural,
|
ui.BIM_PT_tab_structural,
|
||||||
# Construction scheduling
|
# Construction scheduling
|
||||||
|
|||||||
@@ -119,16 +119,11 @@ class GeoreferenceData:
|
|||||||
def true_north(cls):
|
def true_north(cls):
|
||||||
ifc = tool.Ifc.get()
|
ifc = tool.Ifc.get()
|
||||||
true_north = {}
|
true_north = {}
|
||||||
|
|
||||||
if ifc.schema == "IFC2X3":
|
|
||||||
return
|
|
||||||
|
|
||||||
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||||
if not context.TrueNorth:
|
if not context.TrueNorth:
|
||||||
continue
|
continue
|
||||||
true_north = context.TrueNorth.DirectionRatios
|
true_north = context.TrueNorth.DirectionRatios
|
||||||
break
|
break
|
||||||
|
|
||||||
return true_north
|
return true_north
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -22,13 +22,19 @@ from . import ui, prop, operator
|
|||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
operator.ExportOBJ,
|
operator.ExportOBJ,
|
||||||
|
operator.ImportLatLong,
|
||||||
|
operator.ImportTrueNorth,
|
||||||
operator.RadianceRender,
|
operator.RadianceRender,
|
||||||
|
prop.BIMSolarProperties,
|
||||||
prop.RadianceExporterProperties,
|
prop.RadianceExporterProperties,
|
||||||
ui.BIM_PT_radiance_exporter,
|
ui.BIM_PT_radiance_exporter,
|
||||||
|
ui.BIM_PT_solar,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.types.Scene.radiance_exporter_properties = bpy.props.PointerProperty(type=prop.RadianceExporterProperties)
|
bpy.types.Scene.radiance_exporter_properties = bpy.props.PointerProperty(type=prop.RadianceExporterProperties)
|
||||||
|
bpy.types.Scene.BIMSolarProperties = bpy.props.PointerProperty(type=prop.BIMSolarProperties)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
del bpy.types.Scene.radiance_exporter_properties
|
del bpy.types.Scene.radiance_exporter_properties
|
||||||
|
del bpy.types.Scene.BIMSolarProperties
|
||||||
|
|||||||
@@ -17,17 +17,17 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pyradiance as pr
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Union, Optional, Sequence
|
|
||||||
import json
|
import json
|
||||||
|
import shutil
|
||||||
|
import multiprocessing
|
||||||
|
import pyradiance as pr
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import multiprocessing
|
import blenderbim.tool as tool
|
||||||
import shutil
|
from pathlib import Path
|
||||||
|
from typing import Union
|
||||||
|
from blenderbim.bim.module.light.data import SolarData
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -230,3 +230,42 @@ def save_obj2mesh_output(inp: Union[bytes, str, Path], output_file: str, **kwarg
|
|||||||
with open(output_file, 'wb') as f:
|
with open(output_file, 'wb') as f:
|
||||||
f.write(output_bytes)
|
f.write(output_bytes)
|
||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
|
class ImportTrueNorth(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.import_true_north"
|
||||||
|
bl_label = "Import True North"
|
||||||
|
bl_description = "Imports the True North from your IFC geometric context"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
if not tool.Ifc.get():
|
||||||
|
return False
|
||||||
|
if not SolarData.is_loaded:
|
||||||
|
SolarData.load()
|
||||||
|
return SolarData.data["true_north"] is not None
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
props = context.scene.BIMSolarProperties
|
||||||
|
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||||
|
if not context.TrueNorth:
|
||||||
|
continue
|
||||||
|
value = context.TrueNorth.DirectionRatios
|
||||||
|
props.true_north = ifcopenshell.util.geolocation.yaxis2angle(*value[:2])
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class ImportLatLong(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.import_lat_long"
|
||||||
|
bl_label = "Import Latitude / Longitude"
|
||||||
|
bl_description = "Imports the latitude / longitude from an IfcSite"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
props = context.scene.BIMSolarProperties
|
||||||
|
site = tool.Ifc.get().by_id(int(props.sites))
|
||||||
|
if site.RefLatitude and site.RefLongitude:
|
||||||
|
props.latitude = ifcopenshell.util.geolocation.dms2dd(*site.RefLatitude)
|
||||||
|
props.longitude = ifcopenshell.util.geolocation.dms2dd(*site.RefLongitude)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -17,8 +17,53 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import IntProperty, StringProperty, EnumProperty
|
import pytz
|
||||||
|
import tzfpy
|
||||||
|
import datetime
|
||||||
|
from math import radians
|
||||||
|
from bpy.props import IntProperty, StringProperty, EnumProperty, FloatProperty
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
|
from blenderbim.bim.module.light.data import SolarData
|
||||||
|
|
||||||
|
|
||||||
|
def get_sites(self, context):
|
||||||
|
if not SolarData.is_loaded:
|
||||||
|
SolarData.load()
|
||||||
|
return SolarData.data["sites"]
|
||||||
|
|
||||||
|
|
||||||
|
def update_latlong(self, context):
|
||||||
|
sun_props = context.scene.sun_pos_properties
|
||||||
|
sun_props.latitude = self.latitude
|
||||||
|
sun_props.longitude = self.longitude
|
||||||
|
update_sun_path()
|
||||||
|
|
||||||
|
|
||||||
|
def update_hourminute(self, context):
|
||||||
|
sun_props = context.scene.sun_pos_properties
|
||||||
|
sun_props.time = self.hour + (self.minute / 60)
|
||||||
|
update_sun_path()
|
||||||
|
|
||||||
|
|
||||||
|
def update_date(self, context):
|
||||||
|
update_sun_path()
|
||||||
|
|
||||||
|
|
||||||
|
def update_true_north(self, context):
|
||||||
|
sun_props = context.scene.sun_pos_properties
|
||||||
|
# Preserve IFC sign convention
|
||||||
|
sun_props.north_offset = radians(self.true_north * -1)
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
dt = datetime.datetime(datetime.datetime.now(datetime.UTC).year, int(props.month), props.date, props.hour, props.minute)
|
||||||
|
local_time = timezone.localize(dt, is_dst=None)
|
||||||
|
sun_props.use_daylight_savings = bool(local_time.dst())
|
||||||
|
sun_props.UTC_zone = local_time.utcoffset().total_seconds() / 3600
|
||||||
|
|
||||||
|
|
||||||
class RadianceExporterProperties(PropertyGroup):
|
class RadianceExporterProperties(PropertyGroup):
|
||||||
|
|
||||||
@@ -26,25 +71,14 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
if self.json_file_path:
|
if self.json_file_path:
|
||||||
self.json_file_path = bpy.path.abspath(self.json_file_path)
|
self.json_file_path = bpy.path.abspath(self.json_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
radiance_resolution_x: IntProperty(
|
radiance_resolution_x: IntProperty(
|
||||||
name="X",
|
name="X", description="Horizontal resolution of the output image", default=1920, min=1
|
||||||
description="Horizontal resolution of the output image",
|
|
||||||
default=1920,
|
|
||||||
min=1
|
|
||||||
)
|
)
|
||||||
radiance_resolution_y: IntProperty(
|
radiance_resolution_y: IntProperty(
|
||||||
name="Y",
|
name="Y", description="Vertical resolution of the output image", default=1080, min=1
|
||||||
description="Vertical resolution of the output image",
|
|
||||||
default=1080,
|
|
||||||
min=1
|
|
||||||
)
|
)
|
||||||
ifc_file_name: StringProperty(
|
ifc_file_name: StringProperty(
|
||||||
name="IFC File Name",
|
name="IFC File Name", description="Name of the IFC file to use (without .ifc extension)", default=""
|
||||||
description="Name of the IFC file to use (without .ifc extension)",
|
|
||||||
default=""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
json_file_path: StringProperty(
|
json_file_path: StringProperty(
|
||||||
@@ -52,36 +86,61 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
description="Path to the JSON file",
|
description="Path to the JSON file",
|
||||||
default="",
|
default="",
|
||||||
subtype="FILE_PATH",
|
subtype="FILE_PATH",
|
||||||
update=lambda self, context: self.update_json_file_path(context)
|
update=lambda self, context: self.update_json_file_path(context),
|
||||||
)
|
)
|
||||||
|
|
||||||
radiance_quality: EnumProperty(
|
radiance_quality: EnumProperty(
|
||||||
name="Quality",
|
name="Quality",
|
||||||
description="Radiance rendering quality",
|
description="Radiance rendering quality",
|
||||||
items=[
|
items=[("LOW", "Low", "Low quality"), ("MEDIUM", "Medium", "Medium quality"), ("HIGH", "High", "High quality")],
|
||||||
('LOW', "Low", "Low quality"),
|
default="MEDIUM",
|
||||||
('MEDIUM', "Medium", "Medium quality"),
|
|
||||||
('HIGH', "High", "High quality")
|
|
||||||
],
|
|
||||||
default='MEDIUM'
|
|
||||||
)
|
)
|
||||||
radiance_detail: EnumProperty(
|
radiance_detail: EnumProperty(
|
||||||
name="Detail",
|
name="Detail",
|
||||||
description="Radiance rendering detail",
|
description="Radiance rendering detail",
|
||||||
items=[
|
items=[("LOW", "Low", "Low detail"), ("MEDIUM", "Medium", "Medium detail"), ("HIGH", "High", "High detail")],
|
||||||
('LOW', "Low", "Low detail"),
|
default="MEDIUM",
|
||||||
('MEDIUM', "Medium", "Medium detail"),
|
|
||||||
('HIGH', "High", "High detail")
|
|
||||||
],
|
|
||||||
default='MEDIUM'
|
|
||||||
)
|
)
|
||||||
radiance_variability: EnumProperty(
|
radiance_variability: EnumProperty(
|
||||||
name="Variability",
|
name="Variability",
|
||||||
description="Radiance rendering variability",
|
description="Radiance rendering variability",
|
||||||
items=[
|
items=[
|
||||||
('LOW', "Low", "Low variability"),
|
("LOW", "Low", "Low variability"),
|
||||||
('MEDIUM', "Medium", "Medium variability"),
|
("MEDIUM", "Medium", "Medium variability"),
|
||||||
('HIGH', "High", "High variability")
|
("HIGH", "High", "High variability"),
|
||||||
],
|
],
|
||||||
default='MEDIUM'
|
default="MEDIUM",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
true_north: FloatProperty(name="True North", min=-180, max=180, update=update_true_north)
|
||||||
|
month: EnumProperty(
|
||||||
|
name="Month",
|
||||||
|
items=[
|
||||||
|
(str(i + 1), m, "")
|
||||||
|
for i, m in enumerate(
|
||||||
|
(
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
update=update_date
|
||||||
|
)
|
||||||
|
date: IntProperty(name="Date", min=1, max=31, default=1, update=update_date)
|
||||||
|
hour: IntProperty(name="Hour", min=0, max=23, update=update_hourminute)
|
||||||
|
minute: IntProperty(name="Minute", min=0, max=59, update=update_hourminute)
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
from blenderbim.bim.module.light.data import SolarData
|
||||||
|
from sun_position.sun_calc import format_time, format_hms, sun
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_radiance_exporter(bpy.types.Panel):
|
class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||||
"""Creates a Panel in the render properties window"""
|
"""Creates a Panel in the render properties window"""
|
||||||
@@ -62,3 +65,74 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
|||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.operator("render_scene.radiance", text="Radiance Render")
|
row.operator("render_scene.radiance", text="Radiance Render")
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_solar(bpy.types.Panel):
|
||||||
|
"""Creates a Panel in the render properties window"""
|
||||||
|
bl_label = "Solar Access / Shadow"
|
||||||
|
bl_idname = "BIM_PT_solar"
|
||||||
|
bl_space_type = 'PROPERTIES'
|
||||||
|
bl_region_type = 'WINDOW'
|
||||||
|
bl_context = "scene"
|
||||||
|
bl_parent_id = "BIM_PT_tab_solar_analysis"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
if not SolarData.is_loaded:
|
||||||
|
SolarData.load()
|
||||||
|
|
||||||
|
props = context.scene.BIMSolarProperties
|
||||||
|
|
||||||
|
if SolarData.data["sites"]:
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(props, "sites")
|
||||||
|
row.operator("bim.import_lat_long", icon="IMPORT", text="")
|
||||||
|
else:
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text="No Sites With Lat/Longs Found", icon="ERROR")
|
||||||
|
|
||||||
|
sun_props = context.scene.sun_pos_properties
|
||||||
|
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(sun_props, "coordinates", icon='URL')
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(props, "latitude")
|
||||||
|
row.prop(props, "longitude")
|
||||||
|
|
||||||
|
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="")
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(props, "month", text="")
|
||||||
|
row.prop(props, "date")
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(props, "hour")
|
||||||
|
row.prop(props, "minute")
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
box = row.box()
|
||||||
|
|
||||||
|
local_time = format_time(sun_props.time, sun_props.use_daylight_savings)
|
||||||
|
utc_time = format_time(sun_props.time, sun_props.use_daylight_savings, sun_props.UTC_zone)
|
||||||
|
|
||||||
|
row2 = box.row()
|
||||||
|
row2.label(text=f"Local Time: {local_time}")
|
||||||
|
row2 = box.row()
|
||||||
|
row2.label(text=f"UTC Time: {utc_time}")
|
||||||
|
|
||||||
|
box = row.box()
|
||||||
|
|
||||||
|
sunrise = format_hms(sun.sunrise)
|
||||||
|
sunset = format_hms(sun.sunset)
|
||||||
|
|
||||||
|
row2 = box.row()
|
||||||
|
row2.label(text=f"Sunrise: {sunrise}")
|
||||||
|
row2 = box.row()
|
||||||
|
row2.label(text=f"Sunset: {sunset}")
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(sun_props, "sun_distance", text="Sun Path Size")
|
||||||
|
row.prop(sun_props, "show_analemmas", icon="HIDE_ON" if sun_props.show_analemmas else "HIDE_OFF", text="")
|
||||||
|
|||||||
@@ -375,7 +375,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
|||||||
row.prop(context.scene.DocProperties, "imperial_precision")
|
row.prop(context.scene.DocProperties, "imperial_precision")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Scene panel groups
|
# Scene panel groups
|
||||||
class BIM_PT_tabs(Panel):
|
class BIM_PT_tabs(Panel):
|
||||||
bl_label = "BlenderBIM Add-on"
|
bl_label = "BlenderBIM Add-on"
|
||||||
@@ -709,6 +708,20 @@ class BIM_PT_tab_zones(Panel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_tab_solar_analysis(Panel):
|
||||||
|
bl_label = "Solar Analysis"
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "scene"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return tool.Blender.is_tab(context, "SERVICES") and tool.Ifc.get()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_tab_quality_control(Panel):
|
class BIM_PT_tab_quality_control(Panel):
|
||||||
bl_label = "Quality Control"
|
bl_label = "Quality Control"
|
||||||
bl_space_type = "PROPERTIES"
|
bl_space_type = "PROPERTIES"
|
||||||
|
|||||||
Reference in New Issue
Block a user