Utility_Apps/Blender/addons/View‑Aligned HDRI Lighting/__init__.py

145 lines
4.6 KiB
Python

bl_info = {
"name": "View-Aligned HDRI Lighting",
"author": "ChatGPT",
"version": (1, 4, 0),
"blender": (3, 3, 0),
"location": "3D Viewport > Sidebar (N) > View tab",
"description": "Keeps studio HDRI lighting aligned relative to the camera/view. Includes UI and offset control. Starts OFF by default.",
"category": "3D View",
}
import bpy
import math
from bpy.app.handlers import persistent
# -----------------------------------------------------------------------------
# Properties
# -----------------------------------------------------------------------------
class VAH_Properties(bpy.types.PropertyGroup):
enabled: bpy.props.BoolProperty(
name="View-Aligned HDRI",
description="Rotate viewport HDRI relative to camera orientation",
default=False,
update=lambda self, ctx: ensure_timer_running()
)
offset_degrees: bpy.props.FloatProperty(
name="Light Angle Offset",
description="Offset angle relative to the camera (degrees)",
default=35.0,
min=-180.0,
max=180.0,
soft_min=-180.0,
soft_max=180.0,
step=10,
precision=2
)
# -----------------------------------------------------------------------------
# Core logic
# -----------------------------------------------------------------------------
_timer_running = False
def get_view_yaw(rv3d):
eul = rv3d.view_rotation.to_euler('XYZ')
return eul.z
def apply_to_view(space, props):
if space.shading.type not in {'SOLID', 'MATERIAL'}:
return
rv3d = space.region_3d
deg = ((props.offset_degrees + 180.0) % 360.0) - 180.0
offset = math.radians(deg)
yaw = get_view_yaw(rv3d)
space.shading.studiolight_rotate_z = -yaw + offset
def sync_light():
global _timer_running
wm = bpy.context.window_manager
if not hasattr(wm, "vah_props"):
return None
props = wm.vah_props
if not props.enabled:
_timer_running = False
return None
for window in wm.windows:
for area in window.screen.areas:
if area.type != 'VIEW_3D':
continue
for space in area.spaces:
if space.type == 'VIEW_3D':
try:
apply_to_view(space, props)
except Exception:
pass
return 0.05
def ensure_timer_running():
global _timer_running
wm = bpy.context.window_manager
if not hasattr(wm, "vah_props"):
return
if wm.vah_props.enabled and not _timer_running:
bpy.app.timers.register(sync_light, first_interval=0.1)
_timer_running = True
# -----------------------------------------------------------------------------
# UI Panel
# -----------------------------------------------------------------------------
class VIEW3D_PT_view_aligned_hdri(bpy.types.Panel):
bl_label = "View-Aligned HDRI"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'View'
def draw(self, context):
layout = self.layout
wm = context.window_manager
props = wm.vah_props
col = layout.column(align=True)
col.prop(props, "enabled", toggle=True)
sub = col.column(align=True)
sub.enabled = props.enabled
sub.prop(props, "offset_degrees", slider=True)
layout.label(text="Works in Solid & Material Preview modes", icon='INFO')
# -----------------------------------------------------------------------------
# Startup behavior
# -----------------------------------------------------------------------------
@persistent
def load_post_handler(dummy):
# Do nothing automatically; user will turn it on manually
pass
# -----------------------------------------------------------------------------
# Registration
# -----------------------------------------------------------------------------
classes = (
VAH_Properties,
VIEW3D_PT_view_aligned_hdri,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.WindowManager.vah_props = bpy.props.PointerProperty(type=VAH_Properties)
if load_post_handler not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(load_post_handler)
def unregister():
global _timer_running
if load_post_handler in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(load_post_handler)
if hasattr(bpy.types.WindowManager, "vah_props"):
del bpy.types.WindowManager.vah_props
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
_timer_running = False
if __name__ == "__main__":
register()