This commit is contained in:
Chirag Singh
2024-07-20 12:02:57 +05:30
parent d6d5dbfaa3
commit e88abd881d
5 changed files with 200 additions and 136 deletions
@@ -168,6 +168,7 @@ classes = [
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, ui.BIM_PT_tab_solar_analysis,
ui.BIM_PT_tab_lighting,
# Structural analysis # Structural analysis
ui.BIM_PT_tab_structural, ui.BIM_PT_tab_structural,
# Construction scheduling # Construction scheduling
@@ -17,155 +17,148 @@
# 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
try:
import pyradiance as pr
except ImportError:
print("PyRadiance is not available. Rendering functionality will be disabled.")
pr = None
import bpy import bpy
import json
import shutil
import multiprocessing
import pyradiance as pr
import ifcopenshell
import ifcopenshell.geom
import blenderbim.tool as tool import blenderbim.tool as tool
from pathlib import Path from pathlib import Path
from typing import Union from typing import Union, Optional, Sequence
import json
import ifcopenshell
import ifcopenshell.geom
import multiprocessing
from mathutils import Vector
from blenderbim.bim.module.light.data import SolarData from blenderbim.bim.module.light.data import SolarData
class ExportOBJ(bpy.types.Operator): class ExportOBJ(bpy.types.Operator):
"""Exports the IFC File to OBJ""" """Exports the IFC File to OBJ"""
bl_idname = "export_scene.radiance" bl_idname = "export_scene.radiance"
bl_label = "Export" bl_label = "Export"
bl_description = "Export the IFC to OBJ" bl_description = "Export the IFC to OBJ"
def execute(self, context): def execute(self, context):
# Get the resolution from the user input
resolution_x, resolution_y = self.getResolution(context)
quality = context.scene.radiance_exporter_properties.radiance_quality.upper()
detail = context.scene.radiance_exporter_properties.radiance_detail.upper()
variability = context.scene.radiance_exporter_properties.radiance_variability.upper()
# Calculate the aspect ratio # Get the output directory
aspect_ratio = resolution_x / resolution_y should_load_from_memory = context.scene.radiance_exporter_properties.should_load_from_memory
output_dir = context.scene.radiance_exporter_properties.output_dir
json_file = context.scene.radiance_exporter_properties.json_file
# Get the blend file path and create the "Radiance Rendering" directory context.scene.radiance_exporter_properties.is_exporting = True
self.report({"INFO"}, "Exporting Radiance files...")
blend_file_path = bpy.data.filepath
if not blend_file_path:
self.report({"ERROR"}, "Please save the Blender file before exporting.")
return {"CANCELLED"}
blend_file_dir = os.path.dirname(blend_file_path)
radiance_dir = os.path.join(blend_file_dir, "RadianceRendering")
self.report({"INFO"}, "Radiance directory: {}".format(radiance_dir)) # Conversion from IFC to OBJ
if not os.path.exists(radiance_dir):
os.makedirs(radiance_dir)
# IFC file export and processing
settings = ifcopenshell.geom.settings()
# Settings for obj # Settings for obj
settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings() serializer_settings = ifcopenshell.geom.serializer_settings()
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.SURFACES_AND_SOLIDS) settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.SURFACES_AND_SOLIDS)
settings.set("apply-default-materials", True) settings.set("apply-default-materials", True)
serializer_settings.set("use-element-guids", True) serializer_settings.set("use-element-guids", True)
settings.set("use-world-coords", True) settings.set("use-world-coords", True)
ifc_file_name = context.scene.radiance_exporter_properties.ifc_file_name if should_load_from_memory:
ifc_file_path = os.path.join(blend_file_dir, f"{ifc_file_name}.ifc") ifc_file = tool.Ifc.get()
if not os.path.exists(ifc_file_path): else:
self.report({"ERROR"}, f"IFC file not found: {ifc_file_path}") ifc_file_path = context.scene.radiance_exporter_properties.ifc_file
return {"CANCELLED"} ifc_file = ifcopenshell.open(ifc_file_path)
ifc_file = ifcopenshell.open(ifc_file_path)
for material in ifc_file.by_type("IfcMaterial"): for material in ifc_file.by_type("IfcMaterial"):
self.report({"INFO"}, f"Material: {material.Name}, ID: {material.id()}") self.report({'INFO'}, f"Material: {material.Name}, ID: {material.id()}")
obj_file_path = os.path.join(radiance_dir, "model.obj") obj_file_path = os.path.join(output_dir, "model.obj")
mtl_file_path = os.path.join(radiance_dir, "model.mtl") mtl_file_path = os.path.join(output_dir, "model.mtl")
# serialiser = ifcopenshell.geom.serializers.obj(obj_file_path, mtl_file_path, settings, ifcopenshell.geom.serializer_settings())
serialiser = ifcopenshell.geom.serializers.obj(obj_file_path, mtl_file_path, settings, serializer_settings) serialiser = ifcopenshell.geom.serializers.obj(obj_file_path, mtl_file_path, settings, serializer_settings)
serialiser.setFile(ifc_file) serialiser.setFile(ifc_file)
serialiser.setUnitNameAndMagnitude("METER", 1.0) serialiser.setUnitNameAndMagnitude("METER", 1.0)
serialiser.writeHeader() serialiser.writeHeader()
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count()) if ifc_file.schema in ("IFC2X3", "IFC4"):
elements = ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcProxy")
else:
elements = ifc_file.by_type("IfcElement")
elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
if iterator.initialize(): if iterator.initialize():
while True: while True:
serialiser.write(iterator.get()) shape = iterator.get()
materials = shape.geometry.materials
material_ids = shape.geometry.material_ids
for material in materials:
print(material, dir(material))
print(material_ids)
serialiser.write(shape)
if not iterator.next(): if not iterator.next():
break break
serialiser.finalize() serialiser.finalize()
context.scene.radiance_exporter_properties.is_exporting = False
return {"FINISHED"} return {'FINISHED'}
def getResolution(self, context):
scene = context.scene
props = scene.radiance_exporter_properties
resolution_x = props.radiance_resolution_x
resolution_y = props.radiance_resolution_y
return resolution_x, resolution_y
class RadianceRender(bpy.types.Operator): class RadianceRender(bpy.types.Operator):
"""Radiance Rendering""" """Radiance Rendering"""
bl_idname = "render_scene.radiance" bl_idname = "render_scene.radiance"
bl_label = "Render" bl_label = "Render"
bl_description = "Renders the scene using Radiance" bl_description = "Renders the scene using Radiance"
def execute(self, context): def execute(self, context):
if pr is None: if pr is None:
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 # Get the resolution from the user input
resolution_x, resolution_y = self.getResolution(context) resolution_x, resolution_y = self.getResolution(context)
quality = context.scene.radiance_exporter_properties.radiance_quality.upper() quality = context.scene.radiance_exporter_properties.radiance_quality.upper()
detail = context.scene.radiance_exporter_properties.radiance_detail.upper() detail = context.scene.radiance_exporter_properties.radiance_detail.upper()
variability = context.scene.radiance_exporter_properties.radiance_variability.upper() variability = context.scene.radiance_exporter_properties.radiance_variability.upper()
# Get the blend file path and create the "Radiance Rendering" directory should_load_from_memory = context.scene.radiance_exporter_properties.should_load_from_memory
blend_file_path = bpy.data.filepath output_dir = context.scene.radiance_exporter_properties.output_dir
if not blend_file_path: json_file = context.scene.radiance_exporter_properties.json_file
self.report({"ERROR"}, "Please save the Blender file before rendering.")
return {"CANCELLED"}
blend_file_dir = os.path.dirname(blend_file_path) obj_file_path = os.path.join(output_dir, "model.obj")
radiance_dir = os.path.join(blend_file_dir, "RadianceRendering")
camera = self.get_active_camera(context)
if camera is None:
self.report({'ERROR'}, "No active camera found in the scene. Please add a camera and set it as active.")
return {'CANCELLED'}
# Get camera position and direction
camera_position, camera_direction = self.get_camera_data(camera)
# Material processing # Material processing
style = [] style = []
obj_file_path = os.path.join(radiance_dir, "model.obj")
with open(obj_file_path, "r") as obj_file: with open(obj_file_path, "r") as obj_file:
for line in obj_file: for line in obj_file:
if line.startswith("usemtl"): if line.startswith("usemtl"):
l = line.strip().split(" ") l = line.strip().split(" ")
style.append(l[1]) style.append(l[1])
# json_file_path = os.path.join(blend_file_dir, "material_mapping.json")
json_file_path = context.scene.radiance_exporter_properties.json_file_path # Check if json file is empty or not
self.report({"INFO"}, f"Selected JSON file: {json_file_path}") if json_file:
if json_file_path: with open(json_file, 'r') as file:
# self.report({'INFO'}, f"Selected JSON file: {json_file_name}") data = json.load(file)
json_dest_path = os.path.join(blend_file_dir, json_file_path.split("\\")[-1])
shutil.copy(json_file_path, json_dest_path)
self.report({"INFO"}, f"JSON file saved to: {json_dest_path}")
else: else:
self.report({"WARNING"}, "No JSON file selected") data = {}
with open(json_file_path, "r") as file:
data = json.load(file)
# Create materials.rad file # Create materials.rad file
materials_file = os.path.join(radiance_dir, "materials.rad") materials_file = os.path.join(output_dir, "materials.rad")
with open(materials_file, "w") as file: with open(materials_file, "w") as file:
file.write("void plastic white\n0\n0\n5 1 1 1 0 0\n") file.write("void plastic white\n0\n0\n5 0.6 0.6 0.6 0 0\n")
file.write("void plastic blue_plastic\n0\n0\n5 0.1 0.2 0.8 0.05 0.1\n") file.write("void plastic blue_plastic\n0\n0\n5 0.1 0.2 0.8 0.05 0.1\n")
file.write("void plastic red_plastic\n0\n0\n5 0.8 0.1 0.2 0.05 0.1\n") file.write("void plastic red_plastic\n0\n0\n5 0.8 0.1 0.2 0.05 0.1\n")
file.write("void metal silver_metal\n0\n0\n5 0.8 0.8 0.8 0.9 0.1\n") file.write("void metal silver_metal\n0\n0\n5 0.8 0.8 0.8 0.9 0.1\n")
@@ -176,48 +169,58 @@ class RadianceRender(bpy.types.Operator):
for i in set(style): for i in set(style):
file.write("inherit alias " + i + " " + data.get(i, "white") + "\n") file.write("inherit alias " + i + " " + data.get(i, "white") + "\n")
self.report({"INFO"}, "Exported Materials Rad file to: {}".format(materials_file)) self.report({'INFO'}, "Exported Materials Rad file to: {}".format(materials_file))
# Run obj2mesh # Run obj2mesh
rtm_file_path = os.path.join(radiance_dir, "model.rtm") rtm_file_path = os.path.join(output_dir, "model.rtm")
mesh_file_path = save_obj2mesh_output(obj_file_path, rtm_file_path, matfiles=[materials_file]) mesh_file_path = save_obj2mesh_output(obj_file_path, rtm_file_path, matfiles=[materials_file])
# subprocess.run(["obj2mesh", "-a", materials_file, obj_file_path, rtm_file_path]) # subprocess.run(["obj2mesh", "-a", materials_file, obj_file_path, rtm_file_path])
self.report({"INFO"}, "obj2mesh output: {}".format(mesh_file_path)) self.report({'INFO'}, "obj2mesh output: {}".format(mesh_file_path))
scene_file = os.path.join(radiance_dir, "scene.rad") scene_file = os.path.join(output_dir, "scene.rad")
with open(scene_file, "w") as file: with open(scene_file, "w") as file:
file.write("void mesh model\n1 " + rtm_file_path + "\n0\n0\n") file.write("void mesh model\n1 " + rtm_file_path + "\n0\n0\n")
self.report({"INFO"}, "Exported Scene file to: {}".format(scene_file)) self.report({'INFO'}, "Exported Scene file to: {}".format(scene_file))
# Py Radiance Rendering code # Py Radiance Rendering code
scene = pr.Scene("ascene") scene = pr.Scene("ascene")
material_path = os.path.join(radiance_dir, "materials.rad") material_path = os.path.join(output_dir, "materials.rad")
scene_path = os.path.join(radiance_dir, "scene.rad") scene_path = os.path.join(output_dir, "scene.rad")
scene.add_material(material_path) scene.add_material(material_path)
scene.add_surface(scene_path) scene.add_surface(scene_path)
aview = pr.View(position=(1, 1.5, 1), direction=(1, 0, 0)) aview = pr.View(position=camera_position, direction=camera_direction)
scene.add_view(aview) scene.add_view(aview)
octpath = os.path.join(blend_file_dir, "ascene.oct") image = pr.render(scene, ambbounce=1, resolution=(resolution_x, resolution_y),
print("Reached here") quality=quality, detail=detail, variability=variability)
image = pr.render(
scene, raw_hdr_path = os.path.join(output_dir, "raw.hdr")
ambbounce=1,
resolution=(resolution_x, resolution_y),
quality=quality,
detail=detail,
variability=variability,
)
raw_hdr_path = os.path.join(radiance_dir, "raw.hdr")
with open(raw_hdr_path, "wb") as wtr: with open(raw_hdr_path, "wb") as wtr:
wtr.write(image) wtr.write(image)
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(raw_hdr_path)) self.report({'INFO'}, "Radiance rendering completed. Output: {}".format(raw_hdr_path))
return {"FINISHED"} return {'FINISHED'}
def get_active_camera(self, context):
if context.scene.camera:
return context.scene.camera
for obj in context.scene.objects:
if obj.type == 'CAMERA':
return obj
return None
def get_camera_data(self, camera):
# Get camera position
position = camera.matrix_world.to_translation()
# Get camera direction
direction = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
direction.normalize()
return (position.x, position.y, position.z), (direction.x, direction.y, direction.z)
def getResolution(self, context): def getResolution(self, context):
scene = context.scene scene = context.scene
@@ -226,11 +229,10 @@ class RadianceRender(bpy.types.Operator):
resolution_y = props.radiance_resolution_y resolution_y = props.radiance_resolution_y
return resolution_x, resolution_y return resolution_x, resolution_y
def save_obj2mesh_output(inp: Union[bytes, str, Path], output_file: str, **kwargs): def save_obj2mesh_output(inp: Union[bytes, str, Path], output_file: str, **kwargs):
output_bytes = pr.obj2mesh(inp, **kwargs) output_bytes = pr.obj2mesh(inp, **kwargs)
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
@@ -139,47 +139,90 @@ def update_sun_path():
class RadianceExporterProperties(PropertyGroup): class RadianceExporterProperties(PropertyGroup):
def update_json_file_path(self, context): def update_json_file(self, context):
if self.json_file_path: if self.json_file:
self.json_file_path = bpy.path.abspath(self.json_file_path) self.json_file = bpy.path.abspath(self.json_file)
def update_output_dir(self, context):
if self.output_dir:
self.output_dir = bpy.path.abspath(self.output_dir)
def update_ifc_file(self, context):
if self.ifc_file:
self.ifc_file = bpy.path.abspath(self.ifc_file)
is_exporting: bpy.props.BoolProperty(
name="Is Exporting",
description="Whether the OBJ export is in progress",
default=False
)
should_load_from_memory: BoolProperty(
name="Load from Memory",
default=False,
)
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
) )
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
) )
ifc_file_name: StringProperty( output_dir: StringProperty(
name="IFC File Name", description="Name of the IFC file to use (without .ifc extension)", default="" name="Output Directory",
description="Directory to output Radiance files",
default="",
subtype="DIR_PATH",
update=lambda self, context: self.update_output_dir(context)
) )
ifc_file: StringProperty(
json_file_path: StringProperty( name="IFC File",
description="Path to the IFC file",
default="",
subtype="FILE_PATH",
update=lambda self, context: self.update_ifc_file(context)
)
json_file: StringProperty(
name="JSON File", name="JSON File",
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(context)
) )
radiance_quality: EnumProperty( radiance_quality: EnumProperty(
name="Quality", name="Quality",
description="Radiance rendering quality", description="Radiance rendering quality",
items=[("LOW", "Low", "Low quality"), ("MEDIUM", "Medium", "Medium quality"), ("HIGH", "High", "High quality")], items=[
default="MEDIUM", ('LOW', "Low", "Low quality"),
('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=[("LOW", "Low", "Low detail"), ("MEDIUM", "Medium", "Medium detail"), ("HIGH", "High", "High detail")], items=[
default="MEDIUM", ('LOW', "Low", "Low detail"),
('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",
) )
@@ -32,23 +32,28 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
bl_space_type = 'PROPERTIES' bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW' bl_region_type = 'WINDOW'
bl_context = "scene" bl_context = "scene"
bl_parent_id = "BIM_PT_tab_services" bl_parent_id = "BIM_PT_tab_lighting"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
scene = context.scene scene = context.scene
if sun_position is None:
layout.label(text="Enable 'Sun Position' addon to continue.")
return
props = scene.radiance_exporter_properties props = scene.radiance_exporter_properties
if tool.Ifc.get():
row = self.layout.row()
row.prop(props, "should_load_from_memory")
if not tool.Ifc.get() or not props.should_load_from_memory:
row = self.layout.row(align=True)
row.prop(props, "ifc_file")
# row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(props, "output_dir")
row = layout.row() row = layout.row()
row.prop(props, "ifc_file_name") row.prop(props, "json_file")
row = layout.row()
row.prop(props, "json_file_path")
row = layout.row() row = layout.row()
row.label(text="Resolution") row.label(text="Resolution")
@@ -68,11 +73,11 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
row.prop(props, "radiance_variability") row.prop(props, "radiance_variability")
row = layout.row() row = layout.row()
row.operator("export_scene.radiance", text="Export to OBJ") row.operator("export_scene.radiance", text="Export Geometry for 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
class BIM_PT_solar(bpy.types.Panel): class BIM_PT_solar(bpy.types.Panel):
"""Creates a Panel in the render properties window""" """Creates a Panel in the render properties window"""
+13
View File
@@ -693,6 +693,19 @@ class BIM_PT_tab_services(Panel):
def draw(self, context): def draw(self, context):
pass pass
class BIM_PT_tab_lighting(Panel):
bl_label = "Lighting"
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_zones(Panel): class BIM_PT_tab_zones(Panel):
bl_label = "Zones" bl_label = "Zones"