mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Light/radiance (#5252)
* Cam fix, Sun Fix, import-export as json * Ran black formatting
This commit is contained in:
@@ -39,6 +39,10 @@ classes = (
|
|||||||
operator.ViewFromSun,
|
operator.ViewFromSun,
|
||||||
operator.RefreshIFCMaterials,
|
operator.RefreshIFCMaterials,
|
||||||
operator.UnmapMaterial,
|
operator.UnmapMaterial,
|
||||||
|
operator.RADIANCE_OT_select_camera,
|
||||||
|
operator.RADIANCE_OT_export_material_mappings,
|
||||||
|
operator.RADIANCE_OT_import_material_mappings,
|
||||||
|
operator.RADIANCE_OT_open_spectraldb,
|
||||||
prop.RadianceMaterial,
|
prop.RadianceMaterial,
|
||||||
prop.BIMSolarProperties,
|
prop.BIMSolarProperties,
|
||||||
prop.RadianceExporterProperties,
|
prop.RadianceExporterProperties,
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
# Bonsai - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# This file is part of Bonsai.
|
||||||
|
#
|
||||||
|
# Bonsai is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Bonsai is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -10,6 +29,15 @@ class MATERIAL_UL_radiance_materials(bpy.types.UIList):
|
|||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
if self.layout_type in {"DEFAULT", "COMPACT"}:
|
if self.layout_type in {"DEFAULT", "COMPACT"}:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
|
|
||||||
|
# Adding color preview or glass icon
|
||||||
|
if item.category == "Glass":
|
||||||
|
row.label(icon="SHADING_TEXTURE")
|
||||||
|
else:
|
||||||
|
color_rect = row.row()
|
||||||
|
color_rect.prop(item, "color", text="")
|
||||||
|
color_rect.scale_x = 0.3
|
||||||
|
|
||||||
row.prop(item, "name", text="", emboss=False, icon_value=icon)
|
row.prop(item, "name", text="", emboss=False, icon_value=icon)
|
||||||
if item.is_mapped:
|
if item.is_mapped:
|
||||||
row.label(text=f"{item.category} - {item.subcategory}")
|
row.label(text=f"{item.category} - {item.subcategory}")
|
||||||
|
|||||||
@@ -30,11 +30,17 @@ import bonsai.tool as tool
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union, Optional, Sequence
|
from typing import Union, Optional, Sequence
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
import sun_position
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import webbrowser
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from bonsai.bim.module.light.data import SolarData
|
from bonsai.bim.module.light.data import SolarData
|
||||||
|
from bpy_extras.io_utils import ExportHelper
|
||||||
|
from bpy_extras.io_utils import ImportHelper
|
||||||
|
|
||||||
ifc_materials = []
|
ifc_materials = []
|
||||||
|
|
||||||
@@ -95,14 +101,10 @@ class ExportOBJ(bpy.types.Operator):
|
|||||||
while True:
|
while True:
|
||||||
shape = iterator.get()
|
shape = iterator.get()
|
||||||
materials = shape.geometry.materials
|
materials = shape.geometry.materials
|
||||||
# print(shape.geometry)
|
|
||||||
material_ids = shape.geometry.material_ids
|
material_ids = shape.geometry.material_ids
|
||||||
# material_names = shape.geometry.material_names
|
# material_names = shape.geometry.material_names
|
||||||
# print(material_ids)
|
|
||||||
|
|
||||||
for material in materials:
|
for material in materials:
|
||||||
# print(material, dir(material))
|
|
||||||
# print(material.name)
|
|
||||||
ifc_materials.append(material.name)
|
ifc_materials.append(material.name)
|
||||||
|
|
||||||
serialiser.write(shape)
|
serialiser.write(shape)
|
||||||
@@ -129,10 +131,15 @@ class RadianceRender(bpy.types.Operator):
|
|||||||
self.report({"ERROR"}, "PyRadiance is not available. Cannot perform rendering.")
|
self.report({"ERROR"}, "PyRadiance is not available. Cannot perform rendering.")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
# Get the resolution from the user input
|
print("Starting Radiance rendering process...")
|
||||||
|
|
||||||
props = context.scene.radiance_exporter_properties
|
props = context.scene.radiance_exporter_properties
|
||||||
resolution_x, resolution_y = props.radiance_resolution_x, props.radiance_resolution_y
|
resolution_x, resolution_y = props.radiance_resolution_x, props.radiance_resolution_y
|
||||||
|
|
||||||
|
context.scene.render.resolution_x = resolution_x
|
||||||
|
context.scene.render.resolution_y = resolution_y
|
||||||
|
|
||||||
|
aspect_ratio = resolution_x / resolution_y
|
||||||
|
|
||||||
quality = props.radiance_quality.upper()
|
quality = props.radiance_quality.upper()
|
||||||
detail = props.radiance_detail.upper()
|
detail = props.radiance_detail.upper()
|
||||||
variability = props.radiance_variability.upper()
|
variability = props.radiance_variability.upper()
|
||||||
@@ -141,6 +148,11 @@ class RadianceRender(bpy.types.Operator):
|
|||||||
output_file_format = props.output_file_format
|
output_file_format = props.output_file_format
|
||||||
use_hdr = props.use_hdr
|
use_hdr = props.use_hdr
|
||||||
choose_hdr_image = props.choose_hdr_image
|
choose_hdr_image = props.choose_hdr_image
|
||||||
|
|
||||||
|
print(f"Resolution: {resolution_x}x{resolution_y}")
|
||||||
|
print(f"Quality: {quality}, Detail: {detail}, Variability: {variability}")
|
||||||
|
print(f"Output directory: {output_dir}")
|
||||||
|
|
||||||
if use_hdr:
|
if use_hdr:
|
||||||
hdr_image = "noon_grass_2k.hdr"
|
hdr_image = "noon_grass_2k.hdr"
|
||||||
hdr_mask = "noon_grass_2k_mask.hdr"
|
hdr_mask = "noon_grass_2k_mask.hdr"
|
||||||
@@ -148,30 +160,34 @@ class RadianceRender(bpy.types.Operator):
|
|||||||
hdr_image_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_image)
|
hdr_image_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_image)
|
||||||
hdr_mask_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_mask)
|
hdr_mask_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_mask)
|
||||||
sky_map_cal_path = os.path.join(os.path.dirname(__file__), "HDRs", sky_map_cal)
|
sky_map_cal_path = os.path.join(os.path.dirname(__file__), "HDRs", sky_map_cal)
|
||||||
# os.chdir(output_dir)
|
|
||||||
|
|
||||||
obj_file_path = os.path.join(output_dir, "model.obj")
|
obj_file_path = os.path.join(output_dir, "model.obj")
|
||||||
|
|
||||||
sun_props = context.scene.BIMSolarProperties
|
sun_props = context.scene.BIMSolarProperties
|
||||||
|
sun_pos_props = context.scene.sun_pos_properties
|
||||||
sky_file_path = os.path.join(output_dir, "sky.rad")
|
sky_file_path = os.path.join(output_dir, "sky.rad")
|
||||||
latitude = sun_props.latitude
|
# latitude = sun_props.latitude
|
||||||
longitude = sun_props.longitude
|
# longitude = sun_props.longitude
|
||||||
timezone = sun_props.timezone
|
# month = sun_props.month
|
||||||
month = sun_props.month
|
# day = sun_props.day
|
||||||
day = sun_props.day
|
# hour = sun_props.hour
|
||||||
hour = sun_props.hour
|
# minute = sun_props.minute
|
||||||
minute = sun_props.minute
|
|
||||||
|
|
||||||
print("Sun Properties:")
|
# print("Sun Properties:")
|
||||||
print("Latitude: ", latitude)
|
# print("Latitude: ", latitude)
|
||||||
print("Longitude: ", longitude)
|
# print("Longitude: ", longitude)
|
||||||
print("Timezone: ", timezone)
|
# print("Timezone: ", timezone)
|
||||||
print("Month: ", month)
|
# print("Month: ", month)
|
||||||
print("Day: ", day)
|
# print("Day: ", day)
|
||||||
print("Hour: ", hour)
|
# print("Hour: ", hour)
|
||||||
print("Minute: ", minute)
|
# print("Minute: ", minute)
|
||||||
|
|
||||||
|
print("Setting up camera...")
|
||||||
|
if props.use_active_camera:
|
||||||
|
camera = context.scene.camera
|
||||||
|
else:
|
||||||
|
camera = props.selected_camera
|
||||||
|
|
||||||
camera = self.get_active_camera(context)
|
|
||||||
if camera is None:
|
if camera is None:
|
||||||
self.report({"ERROR"}, "No active camera found in the scene. Please add a camera and set it as active.")
|
self.report({"ERROR"}, "No active camera found in the scene. Please add a camera and set it as active.")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
@@ -179,19 +195,34 @@ class RadianceRender(bpy.types.Operator):
|
|||||||
# Get camera position and direction
|
# Get camera position and direction
|
||||||
camera_position, camera_direction = self.get_camera_data(camera)
|
camera_position, camera_direction = self.get_camera_data(camera)
|
||||||
|
|
||||||
dt = datetime(2024, month, day, hour, minute)
|
print(f"Camera position: {camera_position}")
|
||||||
|
print(f"Camera direction: {camera_direction}")
|
||||||
|
|
||||||
|
# azimuth, elevation = sun_position.sun_calc.get_sun_coordinates(
|
||||||
|
# sun_pos_props.time,
|
||||||
|
# sun_pos_props.latitude,
|
||||||
|
# sun_pos_props.longitude,
|
||||||
|
# -sun_pos_props.UTC_zone,
|
||||||
|
# sun_pos_props.month,
|
||||||
|
# sun_pos_props.day,
|
||||||
|
# sun_pos_props.year,
|
||||||
|
# )
|
||||||
|
|
||||||
|
dt = datetime(sun_pos_props.year, sun_props.month, sun_props.day, sun_props.hour, sun_props.minute)
|
||||||
|
|
||||||
sky_description = pr.gensky(
|
sky_description = pr.gensky(
|
||||||
dt=dt,
|
dt=dt,
|
||||||
latitude=latitude,
|
# azimuth=64.1,
|
||||||
longitude=longitude,
|
# altitude=-26.6,
|
||||||
timezone=timezone,
|
latitude=sun_props.latitude,
|
||||||
year=2024,
|
longitude=sun_props.longitude,
|
||||||
sunny_with_sun=True,
|
year=sun_pos_props.year,
|
||||||
sunny_without_sun=False,
|
timezone=-int(sun_props.UTC_zone),
|
||||||
cloudy=False,
|
# sunny_with_sun=False,
|
||||||
ground_reflectance=0.2,
|
# sunny_without_sun=False,
|
||||||
turbidity=3.0,
|
# cloudy=False,
|
||||||
|
# ground_reflectance=0.2,
|
||||||
|
# turbidity=3.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
sky_description_str = sky_description.decode("utf-8")
|
sky_description_str = sky_description.decode("utf-8")
|
||||||
@@ -292,11 +323,7 @@ ground_glow source ground
|
|||||||
|
|
||||||
props = context.scene.radiance_exporter_properties
|
props = context.scene.radiance_exporter_properties
|
||||||
|
|
||||||
if props.use_json_file:
|
data = props.get_mappings_dict()
|
||||||
with open(props.json_file, "r") as file:
|
|
||||||
data = json.load(file)
|
|
||||||
else:
|
|
||||||
data = props.get_mappings_dict()
|
|
||||||
|
|
||||||
materials_file = os.path.join(output_dir, "materials.rad")
|
materials_file = os.path.join(output_dir, "materials.rad")
|
||||||
written_materials = set()
|
written_materials = set()
|
||||||
@@ -318,9 +345,6 @@ ground_glow source ground
|
|||||||
file.write(material)
|
file.write(material)
|
||||||
written_materials.add(material.split()[2]) # Add material name to written set
|
written_materials.add(material.split()[2]) # Add material name to written set
|
||||||
|
|
||||||
print(data)
|
|
||||||
print(default_materials)
|
|
||||||
|
|
||||||
for style_id in all_materials:
|
for style_id in all_materials:
|
||||||
material = next((m for m in props.materials if m.style_id == style_id), None)
|
material = next((m for m in props.materials if m.style_id == style_id), None)
|
||||||
if material and material.is_mapped:
|
if material and material.is_mapped:
|
||||||
@@ -351,7 +375,7 @@ ground_glow source ground
|
|||||||
|
|
||||||
self.report({"INFO"}, "Exported Scene file to: {}".format(scene_file))
|
self.report({"INFO"}, "Exported Scene file to: {}".format(scene_file))
|
||||||
|
|
||||||
# Py Radiance Rendering code
|
print("Setting up Radiance scene...")
|
||||||
scene = pr.Scene("ascene")
|
scene = pr.Scene("ascene")
|
||||||
|
|
||||||
material_path = os.path.join(output_dir, "materials.rad")
|
material_path = os.path.join(output_dir, "materials.rad")
|
||||||
@@ -360,10 +384,39 @@ ground_glow source ground
|
|||||||
scene.add_material(material_path)
|
scene.add_material(material_path)
|
||||||
scene.add_surface(scene_path)
|
scene.add_surface(scene_path)
|
||||||
scene.add_source(sky_file_path)
|
scene.add_source(sky_file_path)
|
||||||
|
print("Setting up view...")
|
||||||
|
if camera.data.type == "PERSP":
|
||||||
|
# Perspective camera
|
||||||
|
camera_fov = camera.data.angle
|
||||||
|
# Calculate vertical FOV based on the desired aspect ratio
|
||||||
|
vertical_fov = 2 * math.atan(math.tan(camera_fov / 2) / aspect_ratio)
|
||||||
|
|
||||||
aview = pr.View(position=camera_position, direction=camera_direction)
|
aview = pr.View(
|
||||||
|
vtype="v", # Perspective view
|
||||||
|
position=camera_position,
|
||||||
|
direction=camera_direction,
|
||||||
|
vup=(0, 0, 1), # Assuming Z is up
|
||||||
|
horiz=math.degrees(camera_fov),
|
||||||
|
vert=math.degrees(vertical_fov),
|
||||||
|
)
|
||||||
|
else: # 'ORTHO'
|
||||||
|
# Orthographic camera
|
||||||
|
# Calculate the view size based on the camera's orthographic scale
|
||||||
|
ortho_scale = camera.data.ortho_scale
|
||||||
|
view_width = ortho_scale
|
||||||
|
view_height = ortho_scale / aspect_ratio
|
||||||
|
|
||||||
|
aview = pr.View(
|
||||||
|
vtype="l", # Parallel projection (orthographic)
|
||||||
|
position=camera_position,
|
||||||
|
direction=camera_direction,
|
||||||
|
vup=(0, 0, 1), # Assuming Z is up
|
||||||
|
horiz=view_width,
|
||||||
|
vert=view_height,
|
||||||
|
)
|
||||||
scene.add_view(aview)
|
scene.add_view(aview)
|
||||||
|
print("Starting render...")
|
||||||
|
start_time = time.time()
|
||||||
image = pr.render(
|
image = pr.render(
|
||||||
scene,
|
scene,
|
||||||
ambbounce=1,
|
ambbounce=1,
|
||||||
@@ -371,32 +424,34 @@ ground_glow source ground
|
|||||||
quality=quality,
|
quality=quality,
|
||||||
detail=detail,
|
detail=detail,
|
||||||
variability=variability,
|
variability=variability,
|
||||||
|
nproc=multiprocessing.cpu_count(),
|
||||||
)
|
)
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"Render completed in {end_time - start_time:.2f} seconds")
|
||||||
|
|
||||||
output_hdr_path = os.path.join(output_dir, f"{output_file_name}.{output_file_format.lower()}")
|
output_hdr_path = os.path.join(output_dir, f"{output_file_name}.{output_file_format.lower()}")
|
||||||
|
print(f"Saving HDR output to: {output_hdr_path}")
|
||||||
if output_file_format == "HDR":
|
if output_file_format == "HDR":
|
||||||
with open(output_hdr_path, "wb") as wtr:
|
with open(output_hdr_path, "wb") as wtr:
|
||||||
wtr.write(image)
|
wtr.write(image)
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
print("Applying tone mapping...")
|
||||||
pcond_image = pr.pcond(hdr=output_hdr_path, human=True)
|
pcond_image = pr.pcond(hdr=output_hdr_path, human=True)
|
||||||
|
|
||||||
tiff_path = os.path.join(output_dir, f"{output_file_name}.tiff")
|
tiff_path = os.path.join(output_dir, f"{output_file_name}.tiff")
|
||||||
|
print(f"Saving TIFF output to: {tiff_path}")
|
||||||
pr.ra_tiff(inp=pcond_image, out=tiff_path, lzw=True)
|
pr.ra_tiff(inp=pcond_image, out=tiff_path, lzw=True)
|
||||||
|
print("Radiance rendering process completed successfully.")
|
||||||
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(tiff_path))
|
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(tiff_path))
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def get_active_camera(self, context):
|
def get_active_camera(self, context):
|
||||||
if context.scene.camera:
|
props = context.scene.radiance_exporter_properties
|
||||||
|
if props.use_active_camera:
|
||||||
return context.scene.camera
|
return context.scene.camera
|
||||||
for obj in context.scene.objects:
|
else:
|
||||||
if obj.type == "CAMERA":
|
return props.selected_camera
|
||||||
return obj
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_camera_data(self, camera):
|
def get_camera_data(self, camera):
|
||||||
# Get camera position
|
# Get camera position
|
||||||
@@ -509,9 +564,29 @@ class RefreshIFCMaterials(bpy.types.Operator):
|
|||||||
for render_item in style.Styles:
|
for render_item in style.Styles:
|
||||||
if render_item.is_a("IfcSurfaceStyleRendering"):
|
if render_item.is_a("IfcSurfaceStyleRendering"):
|
||||||
style_id = f"IfcSurfaceStyleRendering-{render_item.id()}"
|
style_id = f"IfcSurfaceStyleRendering-{render_item.id()}"
|
||||||
ifc_materials.append(style_id)
|
|
||||||
style_name = style.Name or f"Unnamed Style {render_item.id()}"
|
style_name = style.Name or f"Unnamed Style {render_item.id()}"
|
||||||
props.add_material_mapping(style_id, style_name)
|
|
||||||
|
# Extract color and transparency
|
||||||
|
color = (1.0, 1.0, 1.0) # Default white
|
||||||
|
transparency = 0.0 # Default opaque
|
||||||
|
if render_item.SurfaceColour:
|
||||||
|
color = (
|
||||||
|
render_item.SurfaceColour.Red,
|
||||||
|
render_item.SurfaceColour.Green,
|
||||||
|
render_item.SurfaceColour.Blue,
|
||||||
|
)
|
||||||
|
if hasattr(render_item, "Transparency") and render_item.Transparency is not None:
|
||||||
|
transparency = render_item.Transparency
|
||||||
|
|
||||||
|
# Add material with color
|
||||||
|
material = props.add_material_mapping(style_id, style_name)
|
||||||
|
material.color = color
|
||||||
|
|
||||||
|
# If transparency is high, consider it as glass
|
||||||
|
if transparency > 0.5:
|
||||||
|
material.category = "Glass"
|
||||||
|
material.subcategory = "Clear Glass"
|
||||||
|
material.is_mapped = True
|
||||||
|
|
||||||
props.active_material_index = 0 if props.materials else -1
|
props.active_material_index = 0 if props.materials else -1
|
||||||
|
|
||||||
@@ -531,3 +606,70 @@ class UnmapMaterial(bpy.types.Operator):
|
|||||||
material = props.materials[self.material_index]
|
material = props.materials[self.material_index]
|
||||||
props.unmap_material(material.name)
|
props.unmap_material(material.name)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class RADIANCE_OT_select_camera(bpy.types.Operator):
|
||||||
|
bl_idname = "radiance.select_camera"
|
||||||
|
bl_label = "Select Camera"
|
||||||
|
bl_description = "Select a camera from the viewport"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object is not None and context.object.type == "CAMERA"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
props = context.scene.radiance_exporter_properties
|
||||||
|
props.selected_camera = context.object
|
||||||
|
props.use_active_camera = False
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class RADIANCE_OT_export_material_mappings(bpy.types.Operator, ExportHelper):
|
||||||
|
bl_idname = "radiance.export_material_mappings"
|
||||||
|
bl_label = "Export Material Mappings"
|
||||||
|
bl_description = "Export material mappings to a JSON file"
|
||||||
|
|
||||||
|
filename_ext = ".json"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
props = context.scene.radiance_exporter_properties
|
||||||
|
mappings = {}
|
||||||
|
|
||||||
|
for material in props.materials:
|
||||||
|
if material.is_mapped:
|
||||||
|
mappings[material.style_id] = {
|
||||||
|
"name": material.name,
|
||||||
|
"category": material.category,
|
||||||
|
"subcategory": material.subcategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(self.filepath, "w") as f:
|
||||||
|
json.dump(mappings, f, indent=4)
|
||||||
|
|
||||||
|
self.report({"INFO"}, f"Material mappings exported to {self.filepath}")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class RADIANCE_OT_import_material_mappings(bpy.types.Operator, ImportHelper):
|
||||||
|
bl_idname = "radiance.import_material_mappings"
|
||||||
|
bl_label = "Import Material Mappings"
|
||||||
|
bl_description = "Import material mappings from a JSON file"
|
||||||
|
|
||||||
|
filename_ext = ".json"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
props = context.scene.radiance_exporter_properties
|
||||||
|
props.import_mappings(self.filepath)
|
||||||
|
self.report({"INFO"}, f"Material mappings imported from {self.filepath}")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class RADIANCE_OT_open_spectraldb(bpy.types.Operator):
|
||||||
|
bl_idname = "radiance.open_spectraldb"
|
||||||
|
bl_label = "Open SpectralDB"
|
||||||
|
bl_description = "Open the SpectralDB website for reference"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
webbrowser.open("https://spectraldb.com")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from bpy.props import (
|
|||||||
FloatVectorProperty,
|
FloatVectorProperty,
|
||||||
BoolProperty,
|
BoolProperty,
|
||||||
CollectionProperty,
|
CollectionProperty,
|
||||||
|
PointerProperty,
|
||||||
)
|
)
|
||||||
import os
|
import os
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
@@ -107,6 +108,11 @@ def update_display_sun_path(self, context):
|
|||||||
SolarDecorator.uninstall()
|
SolarDecorator.uninstall()
|
||||||
|
|
||||||
|
|
||||||
|
def update_resolution(self, context):
|
||||||
|
context.scene.render.resolution_x = self.radiance_resolution_x
|
||||||
|
context.scene.render.resolution_y = self.radiance_resolution_y
|
||||||
|
|
||||||
|
|
||||||
def update_sun_path():
|
def update_sun_path():
|
||||||
if not SolarData.is_loaded:
|
if not SolarData.is_loaded:
|
||||||
SolarData.load()
|
SolarData.load()
|
||||||
@@ -142,6 +148,10 @@ def update_sun_path():
|
|||||||
rotation_euler = Euler((elevation - pi / 2, 0, -azimuth))
|
rotation_euler = Euler((elevation - pi / 2, 0, -azimuth))
|
||||||
rotation_quaternion = rotation_euler.to_quaternion()
|
rotation_quaternion = rotation_euler.to_quaternion()
|
||||||
|
|
||||||
|
props.azimuth = azimuth
|
||||||
|
props.elevation = elevation
|
||||||
|
props.UTC_zone = zone
|
||||||
|
|
||||||
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:
|
||||||
@@ -162,14 +172,11 @@ class RadianceMaterial(PropertyGroup):
|
|||||||
category: StringProperty(name="Category")
|
category: StringProperty(name="Category")
|
||||||
subcategory: StringProperty(name="Subcategory")
|
subcategory: StringProperty(name="Subcategory")
|
||||||
is_mapped: BoolProperty(name="Is Mapped", default=False)
|
is_mapped: BoolProperty(name="Is Mapped", default=False)
|
||||||
|
color: FloatVectorProperty(name="Color", subtype="COLOR", default=(1.0, 1.0, 1.0), min=0.0, max=1.0, size=3)
|
||||||
|
|
||||||
|
|
||||||
class RadianceExporterProperties(PropertyGroup):
|
class RadianceExporterProperties(PropertyGroup):
|
||||||
|
|
||||||
def update_json_file(self, context):
|
|
||||||
if self.json_file:
|
|
||||||
self.json_file = bpy.path.abspath(self.json_file)
|
|
||||||
|
|
||||||
def update_output_dir(self, context):
|
def update_output_dir(self, context):
|
||||||
if self.output_dir:
|
if self.output_dir:
|
||||||
self.output_dir = bpy.path.abspath(self.output_dir)
|
self.output_dir = bpy.path.abspath(self.output_dir)
|
||||||
@@ -184,8 +191,26 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
item.style_id = style_id
|
item.style_id = style_id
|
||||||
item.category = ""
|
item.category = ""
|
||||||
item.subcategory = ""
|
item.subcategory = ""
|
||||||
|
item.color = (1.0, 1.0, 1.0) # Default white
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
def import_mappings(self, filepath):
|
||||||
|
with open(filepath, "r") as f:
|
||||||
|
mappings = json.load(f)
|
||||||
|
|
||||||
|
for style_id, mapping in mappings.items():
|
||||||
|
material = self.get_material_mapping(mapping["name"])
|
||||||
|
if material:
|
||||||
|
material.style_id = style_id
|
||||||
|
material.category = mapping["category"]
|
||||||
|
material.subcategory = mapping["subcategory"]
|
||||||
|
material.is_mapped = True
|
||||||
|
else:
|
||||||
|
new_material = self.add_material_mapping(style_id, mapping["name"])
|
||||||
|
new_material.category = mapping["category"]
|
||||||
|
new_material.subcategory = mapping["subcategory"]
|
||||||
|
new_material.is_mapped = True
|
||||||
|
|
||||||
def get_material_mapping(self, style_name):
|
def get_material_mapping(self, style_name):
|
||||||
return next((item for item in self.materials if item.name == style_name), None)
|
return next((item for item in self.materials if item.name == style_name), None)
|
||||||
|
|
||||||
@@ -213,12 +238,6 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
item.subcategory = ""
|
item.subcategory = ""
|
||||||
item.is_mapped = False
|
item.is_mapped = False
|
||||||
|
|
||||||
use_json_file: BoolProperty(
|
|
||||||
name="Upload JSON",
|
|
||||||
description="Toggle between uploading a JSON file and using in-UI material mapping",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
is_exporting: bpy.props.BoolProperty(
|
is_exporting: bpy.props.BoolProperty(
|
||||||
name="Is Exporting", description="Whether the OBJ export is in progress", default=False
|
name="Is Exporting", description="Whether the OBJ export is in progress", default=False
|
||||||
)
|
)
|
||||||
@@ -237,6 +256,7 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
("Plant", "Plant", ""),
|
("Plant", "Plant", ""),
|
||||||
("Exterior", "Exterior", ""),
|
("Exterior", "Exterior", ""),
|
||||||
("Color Swatch", "Color Swatch", ""),
|
("Color Swatch", "Color Swatch", ""),
|
||||||
|
("Glass", "Glass", ""),
|
||||||
]
|
]
|
||||||
|
|
||||||
def update_material_mapping(self, context):
|
def update_material_mapping(self, context):
|
||||||
@@ -269,14 +289,13 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
should_load_from_memory: BoolProperty(
|
should_load_from_memory: BoolProperty(
|
||||||
name="Load from Memory",
|
name="Load from Memory",
|
||||||
default=False,
|
default=False,
|
||||||
description="Use IFC file currently loaded in Bonsai",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
radiance_resolution_x: IntProperty(
|
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, update=update_resolution
|
||||||
)
|
)
|
||||||
radiance_resolution_y: IntProperty(
|
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, update=update_resolution
|
||||||
)
|
)
|
||||||
output_dir: StringProperty(
|
output_dir: StringProperty(
|
||||||
name="Output Directory",
|
name="Output Directory",
|
||||||
@@ -292,13 +311,6 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
subtype="FILE_PATH",
|
subtype="FILE_PATH",
|
||||||
update=lambda self, context: self.update_ifc_file(context),
|
update=lambda self, context: self.update_ifc_file(context),
|
||||||
)
|
)
|
||||||
json_file: StringProperty(
|
|
||||||
name="JSON File",
|
|
||||||
description="Path to the JSON file",
|
|
||||||
default="",
|
|
||||||
subtype="FILE_PATH",
|
|
||||||
update=lambda self, context: self.update_json_file(context),
|
|
||||||
)
|
|
||||||
|
|
||||||
radiance_quality: EnumProperty(
|
radiance_quality: EnumProperty(
|
||||||
name="Quality",
|
name="Quality",
|
||||||
@@ -350,6 +362,17 @@ class RadianceExporterProperties(PropertyGroup):
|
|||||||
default="Noon",
|
default="Noon",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
use_active_camera: BoolProperty(
|
||||||
|
name="Use Active Camera", description="Use the active camera in the scene", default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_camera: PointerProperty(
|
||||||
|
type=bpy.types.Object,
|
||||||
|
name="Camera",
|
||||||
|
description="Select a camera to use for rendering",
|
||||||
|
poll=lambda self, object: object.type == "CAMERA",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BIMSolarProperties(PropertyGroup):
|
class BIMSolarProperties(PropertyGroup):
|
||||||
sites: EnumProperty(items=get_sites, name="Sites")
|
sites: EnumProperty(items=get_sites, name="Sites")
|
||||||
@@ -364,6 +387,9 @@ class BIMSolarProperties(PropertyGroup):
|
|||||||
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=(0, 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)
|
||||||
|
azimuth: FloatProperty(name="Azimuth")
|
||||||
|
elevation: FloatProperty(name="Elevation")
|
||||||
|
UTC_zone: FloatProperty(name="UTC Zone")
|
||||||
display_shadows: BoolProperty(
|
display_shadows: BoolProperty(
|
||||||
name="Display Shadows",
|
name="Display Shadows",
|
||||||
default=False,
|
default=False,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"Glass": {
|
||||||
|
"Clear Glass": "void BRTDfunc clear_glass\n10\n sr_clear_r sr_clear_g sr_clear_b\n st_clear_r st_clear_g st_clear_b\n 0 0 0\n glaze1.cal\n0\n19\n 0 0 0\n 0 0 0\n 0 0 0\n 1\n 0.074 0.077 0.079\n 0.074 0.077 0.079\n 0.862 0.890 0.886\n"
|
||||||
|
},
|
||||||
"Wall": {
|
"Wall": {
|
||||||
"White Painted Room Walls": "void plastic white_painted_room_walls \n0\n0\n5 0.8316 0.8116 0.7226 0.0036 0.2",
|
"White Painted Room Walls": "void plastic white_painted_room_walls \n0\n0\n5 0.8316 0.8116 0.7226 0.0036 0.2",
|
||||||
"White Painted Corridor Walls": "void plastic white_painted_corridor_walls \n0\n0\n5 0.8143 0.7984 0.715 0.0039 0.2",
|
"White Painted Corridor Walls": "void plastic white_painted_corridor_walls \n0\n0\n5 0.8143 0.7984 0.715 0.0039 0.2",
|
||||||
|
|||||||
@@ -50,43 +50,52 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
|||||||
row.prop(props, "output_dir")
|
row.prop(props, "output_dir")
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
layout.prop(props, "use_json_file")
|
layout.label(text="Info: Unmapped materials default to white")
|
||||||
|
row = layout.row()
|
||||||
|
row.template_list("MATERIAL_UL_radiance_materials", "", props, "materials", props, "active_material_index")
|
||||||
|
row.operator("radiance.open_spectraldb", text="", icon="WORLD") # Globe icon
|
||||||
|
if len(props.materials) > 0:
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.prop(props, "category")
|
||||||
|
if props.category:
|
||||||
|
col.prop(props, "subcategory")
|
||||||
|
|
||||||
if props.use_json_file:
|
if props.active_material_index >= 0 and props.active_material_index < len(props.materials):
|
||||||
row = layout.row()
|
active_material = props.materials[props.active_material_index]
|
||||||
row.prop(props, "json_file")
|
if active_material.category and active_material.subcategory:
|
||||||
|
layout.label(
|
||||||
|
text=f"Mapped: {active_material.name} to {active_material.category} - {active_material.subcategory}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
layout.label(text=f"Select category and subcategory for: {active_material.name}")
|
||||||
|
|
||||||
if not props.use_json_file:
|
row = layout.row()
|
||||||
row = layout.row()
|
row.operator("radiance.import_material_mappings", text="Import Mappings", icon="IMPORT")
|
||||||
layout.label(text="Info: Unmapped materials default to white")
|
row.operator("radiance.export_material_mappings", text="Export Mappings", icon="EXPORT")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.template_list("MATERIAL_UL_radiance_materials", "", props, "materials", props, "active_material_index")
|
row.operator("bim.refresh_ifc_materials", text="Refresh IFC Materials")
|
||||||
|
|
||||||
if len(props.materials) > 0:
|
|
||||||
col = layout.column(align=True)
|
|
||||||
col.prop(props, "category")
|
|
||||||
if props.category:
|
|
||||||
col.prop(props, "subcategory")
|
|
||||||
|
|
||||||
if props.active_material_index >= 0 and props.active_material_index < len(props.materials):
|
|
||||||
active_material = props.materials[props.active_material_index]
|
|
||||||
if active_material.category and active_material.subcategory:
|
|
||||||
layout.label(
|
|
||||||
text=f"Mapped: {active_material.name} to {active_material.category} - {active_material.subcategory}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
layout.label(text=f"Select category and subcategory for: {active_material.name}")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.operator("bim.refresh_ifc_materials", text="Refresh IFC Materials")
|
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
row = layout.row()
|
|
||||||
row.label(text="Resolution")
|
|
||||||
row.prop(props, "radiance_resolution_x", text="X")
|
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.label(text="")
|
layout.label(text="Step 1: Export geometry for simulation")
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("export_scene.radiance", text="Export Geometry for Simulation")
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text="Camera Settings")
|
||||||
|
row = box.row()
|
||||||
|
row.prop(props, "use_active_camera")
|
||||||
|
if not props.use_active_camera:
|
||||||
|
row = box.row()
|
||||||
|
row.prop(props, "selected_camera")
|
||||||
|
row.operator("radiance.select_camera", text="", icon="EYEDROPPER")
|
||||||
|
|
||||||
|
row = box.row(align=True)
|
||||||
|
row.label(text="Resolution")
|
||||||
|
row.prop(props, "radiance_resolution_x", text="X")
|
||||||
row.prop(props, "radiance_resolution_y", text="Y")
|
row.prop(props, "radiance_resolution_y", text="Y")
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
@@ -115,8 +124,7 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
|||||||
row.prop(props, "choose_hdr_image")
|
row.prop(props, "choose_hdr_image")
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.operator("export_scene.radiance", text="Export Geometry for Simulation")
|
layout.label(text="Step 2: Run the simulation")
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.operator("render_scene.radiance", text="Radiance Render")
|
row.operator("render_scene.radiance", text="Radiance Render")
|
||||||
row.enabled = not props.is_exporting
|
row.enabled = not props.is_exporting
|
||||||
|
|||||||
Reference in New Issue
Block a user