mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-14 03:14:23 +00:00
Light: Fixed all issues
This commit is contained in:
@@ -39,7 +39,6 @@ classes = (
|
||||
operator.MoveSunPathTo3DCursor,
|
||||
operator.RadianceRender,
|
||||
operator.FalseColorRadiance,
|
||||
operator.ConvertHDRToFootCandles,
|
||||
operator.ViewFromSun,
|
||||
operator.LightPickCoordinates,
|
||||
operator.LightSetTimeToNow,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pyradiance as pr
|
||||
from datetime import datetime
|
||||
@@ -105,7 +106,27 @@ class ExportOBJ(bpy.types.Operator):
|
||||
elements += ifc_file.by_type("IfcSite")
|
||||
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)
|
||||
# Filter elements by visibility in Blender
|
||||
# Only export elements that are visible in the viewport
|
||||
visible_elements = []
|
||||
for element in elements:
|
||||
# Get the corresponding Blender object
|
||||
blender_obj = tool.Ifc.get_object(element)
|
||||
|
||||
# If no Blender object exists, include the element (not yet represented)
|
||||
if blender_obj is None:
|
||||
visible_elements.append(element)
|
||||
continue
|
||||
|
||||
# Check if the object is visible
|
||||
# visible_get() returns True if the object is visible (not hidden, not on hidden layer, etc.)
|
||||
if blender_obj.visible_get():
|
||||
visible_elements.append(element)
|
||||
else:
|
||||
print(f"Skipping hidden element: {element.GlobalId if hasattr(element, 'GlobalId') else element.id()}")
|
||||
|
||||
print(f"Exporting {len(visible_elements)} visible elements out of {len(elements)} total elements")
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=visible_elements)
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
shape = iterator.get()
|
||||
@@ -243,8 +264,8 @@ class PrepareRadianceScene(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Build datetime for Radiance gensky
|
||||
# Note: sun_pos_props and sun_props are synchronized by update_sun_path()
|
||||
dt = datetime(sun_pos_props.year, sun_props.month, sun_props.day, sun_props.hour, sun_props.minute)
|
||||
# Note: sun_props (BIMSolarProperties) is the authoritative source, synchronized to sun_pos_props
|
||||
dt = datetime(sun_props.year, sun_props.month, sun_props.day, sun_props.hour, sun_props.minute)
|
||||
|
||||
print(f"Sun position data for Radiance gensky:")
|
||||
print(f" DateTime: {dt}")
|
||||
@@ -258,12 +279,19 @@ class PrepareRadianceScene(bpy.types.Operator):
|
||||
sunny_without_sun = sky_condition == "SUNNY_WITHOUT_SUN"
|
||||
cloudy = sky_condition == "CLOUDY"
|
||||
|
||||
# Note: Radiance gensky expects longitude and timezone in "degrees west of Greenwich"
|
||||
# Standard geographic coordinates have positive east, so we need to negate for eastern hemisphere
|
||||
# longitude: positive = east of Greenwich (negate for gensky), negative = west (keep as is)
|
||||
# timezone: UTC offset hours × 15 degrees, but negative for eastern hemisphere (UTC+)
|
||||
longitude_for_gensky = -sun_props.longitude # Negate because geographic coords are positive east
|
||||
timezone_for_gensky = -int(sun_props.UTC_zone) * 15 # Negate because UTC+ is east
|
||||
|
||||
sky_description = pr.gensky(
|
||||
dt=dt,
|
||||
latitude=sun_props.latitude,
|
||||
longitude=sun_props.longitude,
|
||||
longitude=longitude_for_gensky,
|
||||
year=sun_pos_props.year,
|
||||
timezone=-int(sun_props.UTC_zone),
|
||||
timezone=timezone_for_gensky,
|
||||
sunny_with_sun=sunny_with_sun,
|
||||
sunny_without_sun=sunny_without_sun,
|
||||
cloudy=cloudy,
|
||||
@@ -433,7 +461,9 @@ ground_glow source ground
|
||||
if ies_light.ies_file_path and ies_light.target_object:
|
||||
obj = ies_light.target_object
|
||||
pos = obj.location
|
||||
z_rot = ies_light.rotation_z
|
||||
# Convert rotation from radians to degrees
|
||||
# (Blender's ANGLE subtype stores values in radians)
|
||||
z_rot = math.degrees(ies_light.rotation_z)
|
||||
|
||||
if idx in converted_ies_lights:
|
||||
# Light is enabled and was converted
|
||||
@@ -620,10 +650,6 @@ class FalseColorRadiance(bpy.types.Operator):
|
||||
print(f" Multiplier: {multiplier}")
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
# Build command line arguments for falsecolor
|
||||
# Format: falsecolor [options] -ip input.hdr > output.hdr
|
||||
fc_output_name = props.false_color_output_name
|
||||
fc_hdr_path = os.path.join(output_dir, f"{fc_output_name}.hdr")
|
||||
|
||||
@@ -631,73 +657,82 @@ class FalseColorRadiance(bpy.types.Operator):
|
||||
light_module_dir = os.path.dirname(__file__)
|
||||
falsecolor_exe = os.path.join(light_module_dir, "Radiance", "bin", "falsecolor.exe")
|
||||
radiance_lib = os.path.join(light_module_dir, "Radiance", "lib")
|
||||
falsecolor_bin = falsecolor_exe if os.path.exists(falsecolor_exe) else "falsecolor"
|
||||
radiance_bin_dir = os.path.join(light_module_dir, "Radiance", "bin")
|
||||
|
||||
print(f"Looking for bundled falsecolor at: {falsecolor_exe}")
|
||||
|
||||
if os.path.exists(falsecolor_exe):
|
||||
falsecolor_bin = falsecolor_exe
|
||||
print(f"Using bundled falsecolor: {falsecolor_bin}")
|
||||
else:
|
||||
# Try to find falsecolor in system PATH
|
||||
import shutil
|
||||
|
||||
system_falsecolor = shutil.which("falsecolor.exe") or shutil.which("falsecolor")
|
||||
if system_falsecolor:
|
||||
falsecolor_bin = system_falsecolor
|
||||
radiance_bin_dir = os.path.dirname(system_falsecolor)
|
||||
radiance_lib = os.path.join(os.path.dirname(radiance_bin_dir), "lib")
|
||||
print(f"Using system falsecolor: {falsecolor_bin}")
|
||||
else:
|
||||
falsecolor_bin = "falsecolor"
|
||||
print(f"Warning: falsecolor.exe not found in bundle or system PATH, will try system PATH")
|
||||
|
||||
# Build command line arguments for falsecolor
|
||||
cmd = [falsecolor_bin]
|
||||
|
||||
# Add multiplier
|
||||
cmd.extend(["-m", str(multiplier)])
|
||||
|
||||
# Add scale
|
||||
cmd.extend(["-s", fc_scale])
|
||||
|
||||
# Add label
|
||||
cmd.extend(["-l", props.false_color_label])
|
||||
|
||||
# Add contour lines if enabled
|
||||
# Determine contour mode and build command accordingly
|
||||
if props.false_color_contour_lines:
|
||||
cmd.append("-cl")
|
||||
|
||||
# Add -ip flag (use same image for intensity and overlay)
|
||||
cmd.append("-ip")
|
||||
cmd.append(hdr_path)
|
||||
if props.false_color_contour_mode == "WITH_BG":
|
||||
# Contour lines with background - use -cl and -ip
|
||||
print("Generating false color with contour lines (with background)...")
|
||||
cmd.append("-cl")
|
||||
cmd.append("-ip")
|
||||
cmd.append(hdr_path)
|
||||
else: # WITHOUT_BG
|
||||
# Contour lines only (with legend but no colored background)
|
||||
print("Generating false color with contour lines only (with legend, no background)...")
|
||||
cmd.append("-cl")
|
||||
cmd.append("-i")
|
||||
cmd.append(hdr_path)
|
||||
else:
|
||||
# No contour lines - standard false color with -ip
|
||||
print("Generating standard false color image...")
|
||||
cmd.append("-ip")
|
||||
cmd.append(hdr_path)
|
||||
|
||||
print(f"Running falsecolor command: {' '.join(cmd)}")
|
||||
print(f"Using falsecolor binary: {falsecolor_bin}")
|
||||
|
||||
# Setup environment for Radiance tools
|
||||
env = os.environ.copy()
|
||||
env["RAYPATH"] = "." + os.pathsep + radiance_lib
|
||||
|
||||
# Add Radiance bin directory to PATH so falsecolor can find its dependencies
|
||||
if radiance_bin_dir and os.path.exists(radiance_bin_dir):
|
||||
env["PATH"] = radiance_bin_dir + os.pathsep + env.get("PATH", "")
|
||||
print(f"Added to PATH: {radiance_bin_dir}")
|
||||
|
||||
# Set RAYPATH for Radiance library files
|
||||
if os.path.exists(radiance_lib):
|
||||
env["RAYPATH"] = "." + os.pathsep + radiance_lib
|
||||
print(f"Set RAYPATH: {radiance_lib}")
|
||||
else:
|
||||
print(f"Note: Radiance lib folder not found at {radiance_lib}")
|
||||
|
||||
# Execute falsecolor and capture output
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, check=True, env=env)
|
||||
fc_image = result.stdout
|
||||
# Use cwd=output_dir to ensure falsecolor writes to the correct location
|
||||
result = subprocess.run(cmd, capture_output=True, check=True, env=env, cwd=output_dir)
|
||||
fc_image = result.stdout
|
||||
|
||||
print(f"Saving false color HDR to: {fc_hdr_path}")
|
||||
with open(fc_hdr_path, "wb") as f:
|
||||
f.write(fc_image)
|
||||
print(f"Saving false color HDR to: {fc_hdr_path}")
|
||||
with open(fc_hdr_path, "wb") as f:
|
||||
f.write(fc_image)
|
||||
|
||||
print(f"False color HDR generated successfully: {fc_hdr_path}")
|
||||
self.report({"INFO"}, f"False color image generated: {fc_hdr_path}")
|
||||
|
||||
except FileNotFoundError:
|
||||
# falsecolor binary not found in PATH or bundled location
|
||||
# Fallback: use pcond as an alternative visualization
|
||||
print("WARNING: falsecolor binary not found")
|
||||
print(f" Checked locations:")
|
||||
print(f" 1. Bundled: {falsecolor_exe}")
|
||||
print(f" 2. System PATH: falsecolor")
|
||||
print("Using alternative method: Tone-mapped HDR for luminance visualization...")
|
||||
print("Note: This is a fallback visualization without false color overlay")
|
||||
|
||||
fc_image = pr.pcond(hdr=hdr_path, human=True)
|
||||
|
||||
# Still save as HDR for consistency
|
||||
with open(fc_hdr_path, "wb") as f:
|
||||
f.write(fc_image)
|
||||
|
||||
print(f"Tone-mapped HDR saved to: {fc_hdr_path}")
|
||||
self.report(
|
||||
{"WARNING"},
|
||||
"falsecolor binary not found. Generated tone-mapped HDR instead. "
|
||||
"Ensure Radiance/bin folder is present in the light module directory.",
|
||||
)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_msg = f"falsecolor failed: {e.stderr.decode() if e.stderr else str(e)}"
|
||||
print(f"ERROR: {error_msg}")
|
||||
raise RuntimeError(error_msg)
|
||||
print(f"False color HDR generated successfully: {fc_hdr_path}")
|
||||
self.report({"INFO"}, f"False color image generated: {fc_hdr_path}")
|
||||
|
||||
# Generate TIFF version for convenience
|
||||
try:
|
||||
@@ -714,81 +749,27 @@ class FalseColorRadiance(bpy.types.Operator):
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to generate false color image: {str(e)}"
|
||||
self.report({"ERROR"}, error_msg)
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_msg = f"falsecolor failed: {e.stderr.decode() if e.stderr else str(e)}"
|
||||
print(f"ERROR: {error_msg}")
|
||||
self.report({"ERROR"}, error_msg)
|
||||
return {"CANCELLED"}
|
||||
except FileNotFoundError as fnf_error:
|
||||
# Check if falsecolor was not found
|
||||
if "falsecolor" in str(fnf_error).lower() or not os.path.exists(falsecolor_exe):
|
||||
error_msg = "falsecolor not found! Please install Radiance and add it to your system PATH"
|
||||
else:
|
||||
error_msg = f"falsecolor binary not found at: {falsecolor_exe}. Please ensure the Radiance/bin folder is present in the light module directory."
|
||||
print(f"ERROR: {error_msg}")
|
||||
print(f"Traceback:")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class ConvertHDRToFootCandles(bpy.types.Operator):
|
||||
"""Convert HDR image to foot-candles unit"""
|
||||
|
||||
bl_idname = "render_scene.convert_hdr_to_fc"
|
||||
bl_label = "Convert HDR to Foot-Candles"
|
||||
bl_description = "Convert the main render HDR to foot-candles unit for illuminance verification"
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Blender.get_radiance_exporter_props()
|
||||
output_dir = props.output_dir
|
||||
output_file_name = props.output_file_name
|
||||
|
||||
# Check if the main render HDR exists
|
||||
hdr_path = os.path.join(output_dir, f"{output_file_name}.hdr")
|
||||
if not os.path.exists(hdr_path):
|
||||
error_msg = f"HDR file not found at: {hdr_path}. Please run 'Radiance Render' first."
|
||||
self.report({"ERROR"}, error_msg)
|
||||
print(f"ERROR: {error_msg}")
|
||||
return {"CANCELLED"}
|
||||
|
||||
print(f"Converting HDR to foot-candles: {hdr_path}")
|
||||
|
||||
# Conversion factor from cd/m² to foot-candles
|
||||
# 1 foot-candle = 10.764 lux = 10.764 cd/m²
|
||||
# So to convert: multiply by 1/10.764 ≈ 0.0929
|
||||
fc_scale_factor = 0.0929005
|
||||
|
||||
try:
|
||||
# Use pyradiance Pcomb to convert
|
||||
print(f"Applying pcomb with scale factor {fc_scale_factor} and original flag...")
|
||||
|
||||
# Create Pcomb command with original=True (-o flag) and scaler for unit conversion
|
||||
# Note: fout=False ensures the output is a proper HDR file with headers, not raw data
|
||||
pcomb_result = pr.Pcomb(fout=False).add(hdr_path, original=True, scaler=fc_scale_factor)()
|
||||
|
||||
# Save the converted HDR
|
||||
fc_output_name = props.hdr_to_fc_output_name
|
||||
fc_hdr_path = os.path.join(output_dir, f"{fc_output_name}.hdr")
|
||||
|
||||
print(f"Saving foot-candles HDR to: {fc_hdr_path}")
|
||||
with open(fc_hdr_path, "wb") as f:
|
||||
f.write(pcomb_result)
|
||||
|
||||
print(f"HDR converted to foot-candles successfully: {fc_hdr_path}")
|
||||
self.report({"INFO"}, f"HDR converted to foot-candles: {fc_hdr_path}")
|
||||
|
||||
# Also generate TIFF version for convenient viewing
|
||||
try:
|
||||
print("Generating TIFF version of foot-candles image...")
|
||||
pcond_fc_image = pr.pcond(hdr=fc_hdr_path, human=True)
|
||||
fc_tiff_path = os.path.join(output_dir, f"{fc_output_name}.tiff")
|
||||
print(f"Saving foot-candles TIFF to: {fc_tiff_path}")
|
||||
pr.ra_tiff(inp=pcond_fc_image, out=fc_tiff_path, lzw=True)
|
||||
print(f"TIFF version generated: {fc_tiff_path}")
|
||||
self.report({"INFO"}, f"TIFF version also generated: {fc_tiff_path}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to generate foot-candles TIFF: {str(e)}")
|
||||
self.report({"WARNING"}, f"TIFF generation failed: {str(e)}")
|
||||
|
||||
print("Foot-candles conversion completed successfully.")
|
||||
return {"FINISHED"}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to convert HDR to foot-candles: {str(e)}"
|
||||
# Check if it's a falsecolor not found error
|
||||
if "falsecolor" in str(e).lower() or isinstance(e, FileNotFoundError):
|
||||
error_msg = "falsecolor not found! Please install Radiance and add it to your system PATH"
|
||||
else:
|
||||
error_msg = f"Failed to generate false color image: {str(e)}"
|
||||
|
||||
self.report({"ERROR"}, error_msg)
|
||||
print(f"ERROR: {error_msg}")
|
||||
print(f"Traceback:")
|
||||
|
||||
@@ -568,11 +568,25 @@ class RadianceExporterProperties(PropertyGroup):
|
||||
)
|
||||
|
||||
false_color_contour_lines: BoolProperty(
|
||||
name="Contour Lines",
|
||||
name="Enable Contour Lines",
|
||||
description="Add contour lines to the false color image",
|
||||
default=True,
|
||||
)
|
||||
|
||||
false_color_contour_mode: EnumProperty(
|
||||
name="Contour Mode",
|
||||
description="Contour line display mode",
|
||||
items=[
|
||||
(
|
||||
"WITH_BG",
|
||||
"Contour Lines with Background",
|
||||
"Show contour lines overlaid on the colored false color background",
|
||||
),
|
||||
("WITHOUT_BG", "Contour Lines Only", "Show only contour lines without the false color background"),
|
||||
],
|
||||
default="WITH_BG",
|
||||
)
|
||||
|
||||
false_color_multiplier: FloatProperty(
|
||||
name="Multiplier",
|
||||
description="Conversion multiplier (179.0 for lux, 16.6295 for foot-candles)",
|
||||
@@ -587,19 +601,6 @@ class RadianceExporterProperties(PropertyGroup):
|
||||
default="false_color",
|
||||
)
|
||||
|
||||
# HDR to foot-candles conversion properties
|
||||
convert_hdr_to_fc: BoolProperty(
|
||||
name="Convert to Foot-Candles",
|
||||
description="Convert the main render HDR to foot-candles unit for illuminance verification",
|
||||
default=False,
|
||||
)
|
||||
|
||||
hdr_to_fc_output_name: StringProperty(
|
||||
name="FC Output Name",
|
||||
description="Name of the foot-candles converted HDR file (without extension)",
|
||||
default="render_fc",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_exporting: bool
|
||||
category: str
|
||||
@@ -630,10 +631,9 @@ class RadianceExporterProperties(PropertyGroup):
|
||||
false_color_label: Literal["fc", "lux", "cd/m2"]
|
||||
false_color_scale: float
|
||||
false_color_contour_lines: bool
|
||||
false_color_contour_mode: Literal["WITH_BG", "WITHOUT_BG"]
|
||||
false_color_multiplier: float
|
||||
false_color_output_name: str
|
||||
convert_hdr_to_fc: bool
|
||||
hdr_to_fc_output_name: str
|
||||
|
||||
|
||||
class BIMSolarProperties(PropertyGroup):
|
||||
|
||||
@@ -241,6 +241,11 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
row = box.row()
|
||||
row.prop(props, "false_color_contour_lines")
|
||||
|
||||
# Contour mode dropdown (only visible if contour lines enabled)
|
||||
if props.false_color_contour_lines:
|
||||
row = box.row()
|
||||
row.prop(props, "false_color_contour_mode")
|
||||
|
||||
row = box.row()
|
||||
row.label(text="Multiplier")
|
||||
row.prop(props, "false_color_multiplier", text="")
|
||||
@@ -252,22 +257,6 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
row = box.row()
|
||||
row.operator("render_scene.false_color_radiance", text="Generate False Color Image")
|
||||
|
||||
layout.separator()
|
||||
|
||||
# Step 5: Convert to foot-candles
|
||||
box = layout.box()
|
||||
box.label(text="Step 5: Convert HDR to Foot-Candles")
|
||||
row = box.row()
|
||||
row.prop(props, "convert_hdr_to_fc")
|
||||
|
||||
if props.convert_hdr_to_fc:
|
||||
row = box.row()
|
||||
row.label(text="Output Name")
|
||||
row.prop(props, "hdr_to_fc_output_name", text="")
|
||||
|
||||
row = box.row()
|
||||
row.operator("render_scene.convert_hdr_to_fc", text="Convert to Foot-Candles")
|
||||
|
||||
|
||||
class BIM_PT_solar(bpy.types.Panel):
|
||||
"""Creates a Panel in the render properties window"""
|
||||
|
||||
Reference in New Issue
Block a user