mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +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
|
||||
ui.BIM_PT_tab_services,
|
||||
ui.BIM_PT_tab_zones,
|
||||
ui.BIM_PT_tab_solar_analysis,
|
||||
# Structural analysis
|
||||
ui.BIM_PT_tab_structural,
|
||||
# Construction scheduling
|
||||
|
||||
@@ -119,16 +119,11 @@ class GeoreferenceData:
|
||||
def true_north(cls):
|
||||
ifc = tool.Ifc.get()
|
||||
true_north = {}
|
||||
|
||||
if ifc.schema == "IFC2X3":
|
||||
return
|
||||
|
||||
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if not context.TrueNorth:
|
||||
continue
|
||||
true_north = context.TrueNorth.DirectionRatios
|
||||
break
|
||||
|
||||
return true_north
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -22,13 +22,19 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ExportOBJ,
|
||||
operator.ImportLatLong,
|
||||
operator.ImportTrueNorth,
|
||||
operator.RadianceRender,
|
||||
prop.BIMSolarProperties,
|
||||
prop.RadianceExporterProperties,
|
||||
ui.BIM_PT_radiance_exporter,
|
||||
ui.BIM_PT_solar,
|
||||
)
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.radiance_exporter_properties = bpy.props.PointerProperty(type=prop.RadianceExporterProperties)
|
||||
bpy.types.Scene.BIMSolarProperties = bpy.props.PointerProperty(type=prop.BIMSolarProperties)
|
||||
|
||||
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/>.
|
||||
|
||||
import os
|
||||
|
||||
import pyradiance as pr
|
||||
import bpy
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional, Sequence
|
||||
import json
|
||||
import shutil
|
||||
import multiprocessing
|
||||
import pyradiance as pr
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import multiprocessing
|
||||
import shutil
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
from blenderbim.bim.module.light.data import SolarData
|
||||
|
||||
|
||||
|
||||
@@ -229,4 +229,43 @@ def save_obj2mesh_output(inp: Union[bytes, str, Path], output_file: str, **kwarg
|
||||
|
||||
with open(output_file, 'wb') as f:
|
||||
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/>.
|
||||
|
||||
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 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):
|
||||
|
||||
@@ -26,25 +71,14 @@ class RadianceExporterProperties(PropertyGroup):
|
||||
if self.json_file_path:
|
||||
self.json_file_path = bpy.path.abspath(self.json_file_path)
|
||||
|
||||
|
||||
|
||||
|
||||
radiance_resolution_x: IntProperty(
|
||||
name="X",
|
||||
description="Horizontal resolution of the output image",
|
||||
default=1920,
|
||||
min=1
|
||||
name="X", description="Horizontal resolution of the output image", default=1920, min=1
|
||||
)
|
||||
radiance_resolution_y: IntProperty(
|
||||
name="Y",
|
||||
description="Vertical resolution of the output image",
|
||||
default=1080,
|
||||
min=1
|
||||
name="Y", description="Vertical resolution of the output image", default=1080, min=1
|
||||
)
|
||||
ifc_file_name: StringProperty(
|
||||
name="IFC File Name",
|
||||
description="Name of the IFC file to use (without .ifc extension)",
|
||||
default=""
|
||||
name="IFC File Name", description="Name of the IFC file to use (without .ifc extension)", default=""
|
||||
)
|
||||
|
||||
json_file_path: StringProperty(
|
||||
@@ -52,36 +86,61 @@ class RadianceExporterProperties(PropertyGroup):
|
||||
description="Path to the JSON file",
|
||||
default="",
|
||||
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(
|
||||
name="Quality",
|
||||
description="Radiance rendering quality",
|
||||
items=[
|
||||
('LOW', "Low", "Low quality"),
|
||||
('MEDIUM', "Medium", "Medium quality"),
|
||||
('HIGH', "High", "High quality")
|
||||
],
|
||||
default='MEDIUM'
|
||||
items=[("LOW", "Low", "Low quality"), ("MEDIUM", "Medium", "Medium quality"), ("HIGH", "High", "High quality")],
|
||||
default="MEDIUM",
|
||||
)
|
||||
radiance_detail: EnumProperty(
|
||||
name="Detail",
|
||||
description="Radiance rendering detail",
|
||||
items=[
|
||||
('LOW', "Low", "Low detail"),
|
||||
('MEDIUM', "Medium", "Medium detail"),
|
||||
('HIGH', "High", "High detail")
|
||||
],
|
||||
default='MEDIUM'
|
||||
items=[("LOW", "Low", "Low detail"), ("MEDIUM", "Medium", "Medium detail"), ("HIGH", "High", "High detail")],
|
||||
default="MEDIUM",
|
||||
)
|
||||
radiance_variability: EnumProperty(
|
||||
name="Variability",
|
||||
description="Radiance rendering variability",
|
||||
items=[
|
||||
('LOW', "Low", "Low variability"),
|
||||
('MEDIUM', "Medium", "Medium variability"),
|
||||
('HIGH', "High", "High variability")
|
||||
("LOW", "Low", "Low variability"),
|
||||
("MEDIUM", "Medium", "Medium 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/>.
|
||||
|
||||
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):
|
||||
"""Creates a Panel in the render properties window"""
|
||||
@@ -61,4 +64,75 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
row.operator("export_scene.radiance", text="Export to OBJ")
|
||||
|
||||
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="")
|
||||
|
||||
@@ -373,7 +373,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row.prop(context.scene.DocProperties, "magic_font_scale")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "imperial_precision")
|
||||
|
||||
|
||||
|
||||
# Scene panel groups
|
||||
@@ -709,6 +708,20 @@ class BIM_PT_tab_zones(Panel):
|
||||
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):
|
||||
bl_label = "Quality Control"
|
||||
bl_space_type = "PROPERTIES"
|
||||
|
||||
Reference in New Issue
Block a user