Live patch - Light properties, object visibility and world
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import os
|
||||
import shutil
|
||||
import math
|
||||
from typing import Any, Type
|
||||
|
||||
import bpy
|
||||
@ -52,6 +53,24 @@ def start():
|
||||
for light_type in (bpy.types.AreaLight, bpy.types.PointLight, bpy.types.SpotLight, bpy.types.SunLight):
|
||||
listen(light_type, "color", "light_color")
|
||||
listen(light_type, "energy", "light_energy")
|
||||
listen(light_type, "use_shadow", "light_shadow")
|
||||
listen(light_type, "lnx_clip_start", "light_near")
|
||||
listen(light_type, "lnx_clip_end", "light_far")
|
||||
listen(light_type, "lnx_fov", "light_fov")
|
||||
listen(light_type, "lnx_shadows_bias", "light_bias")
|
||||
|
||||
listen(bpy.types.SpotLight, "spot_size", "light_spot_size")
|
||||
listen(bpy.types.SpotLight, "spot_blend", "light_spot_blend")
|
||||
listen(bpy.types.PointLight, "shadow_soft_size", "light_size")
|
||||
listen(bpy.types.SpotLight, "shadow_soft_size", "light_size")
|
||||
listen(bpy.types.AreaLight, "size", "light_area_size")
|
||||
listen(bpy.types.AreaLight, "size_y", "light_area_size")
|
||||
|
||||
listen(bpy.types.Camera, "lens", "cam_props")
|
||||
listen(bpy.types.Camera, "sensor_width", "cam_props")
|
||||
listen(bpy.types.Camera, "clip_start", "cam_props")
|
||||
listen(bpy.types.Camera, "clip_end", "cam_props")
|
||||
listen(bpy.types.Camera, "lnx_frustum_culling", "cam_props")
|
||||
|
||||
global __running
|
||||
__running = True
|
||||
@ -78,9 +97,59 @@ def get_obj_transforms(obj):
|
||||
|
||||
def get_light_props(obj):
|
||||
light = obj.data
|
||||
color = (round(light.color[0], 6), round(light.color[1], 6), round(light.color[2], 6))
|
||||
energy = round(light.energy, 6)
|
||||
return color, energy
|
||||
props = {
|
||||
'light_color': (round(light.color[0], 6), round(light.color[1], 6), round(light.color[2], 6)),
|
||||
'light_energy': round(light.energy, 6),
|
||||
'light_shadow': bool(light.use_shadow),
|
||||
'light_near': round(light.lnx_clip_start, 6),
|
||||
'light_far': round(light.lnx_clip_end, 6),
|
||||
'light_fov': round(light.lnx_fov, 6),
|
||||
'light_bias': round(light.lnx_shadows_bias * 0.0001, 6),
|
||||
}
|
||||
if light.type == 'SPOT':
|
||||
half_angle = light.spot_size * 0.5
|
||||
outer_cos = math.cos(half_angle)
|
||||
blend = max(0.0, min(1.0, light.spot_blend))
|
||||
inner_angle = math.atan(math.tan(half_angle) * (1.0 - blend))
|
||||
props['light_spot_size'] = round(outer_cos, 6)
|
||||
props['light_spot_blend'] = round(max(0.0001, math.cos(inner_angle) - outer_cos), 6)
|
||||
if light.type in ('POINT', 'SPOT'):
|
||||
props['light_size'] = round(light.shadow_soft_size * 10, 6)
|
||||
if light.type == 'AREA':
|
||||
props['light_area_size'] = (round(light.size, 6), round(light.size_y, 6))
|
||||
return props
|
||||
|
||||
|
||||
def get_camera_props(obj):
|
||||
cam = obj.data
|
||||
return {
|
||||
'cam_props': (
|
||||
round(cam.lens, 6),
|
||||
round(cam.sensor_width, 6),
|
||||
round(cam.clip_start, 6),
|
||||
round(cam.clip_end, 6),
|
||||
bool(cam.lnx_frustum_culling),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_world_clear_color():
|
||||
scene = bpy.context.scene
|
||||
if scene.world is None:
|
||||
return (0.051, 0.051, 0.051, 1.0), 1.0
|
||||
if scene.world.node_tree is None:
|
||||
c = scene.world.color
|
||||
return (round(c[0], 6), round(c[1], 6), round(c[2], 6), 1.0), 1.0
|
||||
if 'Background' in scene.world.node_tree.nodes:
|
||||
bg = scene.world.node_tree.nodes['Background']
|
||||
col = bg.inputs[0].default_value
|
||||
strength = bg.inputs[1].default_value
|
||||
r = max(min(col[0], 1.0), 0.0)
|
||||
g = max(min(col[1], 1.0), 0.0)
|
||||
b = max(min(col[2], 1.0), 0.0)
|
||||
a = max(min(col[3], 1.0), 0.0)
|
||||
return (round(r, 6), round(g, 6), round(b, 6), round(a, 6)), round(strength, 6)
|
||||
return (0.051, 0.051, 0.051, 1.0), 1.0
|
||||
|
||||
DEPSGRAPH_HANDLERS = {
|
||||
'is_updated_transform': [
|
||||
@ -107,20 +176,54 @@ def on_depsgraph_update(updates):
|
||||
for event_id, extractor in DEPSGRAPH_HANDLERS['is_updated_transform']:
|
||||
current = extractor(uid)
|
||||
cache_key = (event_id, name)
|
||||
if current != scene_cache.get(cache_key):
|
||||
if cache_key not in scene_cache:
|
||||
scene_cache[cache_key] = current
|
||||
send_event(event_id)
|
||||
elif current != scene_cache[cache_key]:
|
||||
scene_cache[cache_key] = current
|
||||
send_event(event_id, name)
|
||||
changed = True
|
||||
|
||||
if update.is_updated_shading and isinstance(uid.data, bpy.types.Light):
|
||||
color, energy = get_light_props(uid)
|
||||
for event_id, current in (('light_color', color), ('light_energy', energy)):
|
||||
for event_id, current in get_light_props(uid).items():
|
||||
cache_key = (event_id, uid.data.name)
|
||||
if current != scene_cache.get(cache_key):
|
||||
if cache_key not in scene_cache:
|
||||
scene_cache[cache_key] = current
|
||||
send_event(event_id)
|
||||
elif current != scene_cache[cache_key]:
|
||||
scene_cache[cache_key] = current
|
||||
send_event(event_id, name)
|
||||
changed = True
|
||||
|
||||
if update.is_updated_shading and isinstance(uid.data, bpy.types.Camera):
|
||||
for event_id, current in get_camera_props(uid).items():
|
||||
cache_key = (event_id, uid.data.name)
|
||||
if cache_key not in scene_cache:
|
||||
scene_cache[cache_key] = current
|
||||
elif current != scene_cache[cache_key]:
|
||||
scene_cache[cache_key] = current
|
||||
send_event(event_id, name)
|
||||
changed = True
|
||||
|
||||
elif isinstance(uid, bpy.types.Scene):
|
||||
for obj in bpy.data.objects:
|
||||
visibility_key = ('obj_visibility', obj.name)
|
||||
current = not obj.hide_get()
|
||||
if visibility_key not in scene_cache:
|
||||
scene_cache[visibility_key] = current
|
||||
elif current != scene_cache[visibility_key]:
|
||||
scene_cache[visibility_key] = current
|
||||
send_event('obj_visibility', obj.name)
|
||||
changed = True
|
||||
|
||||
elif isinstance(uid, bpy.types.World):
|
||||
clear_color_key = ('world_clear_color', uid.name)
|
||||
current = get_world_clear_color()
|
||||
if clear_color_key not in scene_cache:
|
||||
scene_cache[clear_color_key] = current
|
||||
elif current != scene_cache[clear_color_key]:
|
||||
scene_cache[clear_color_key] = current
|
||||
send_event('world_clear_color')
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
@ -189,47 +292,134 @@ def send_event(event_id: str, opt_data: Any = None):
|
||||
if not __running:
|
||||
return
|
||||
|
||||
if hasattr(bpy.context, 'object') and bpy.context.object is not None:
|
||||
obj = bpy.context.object.name
|
||||
obj_name = opt_data if isinstance(opt_data, str) else None
|
||||
if obj_name is not None:
|
||||
obj_data = bpy.data.objects.get(obj_name)
|
||||
elif hasattr(bpy.context, 'object') and bpy.context.object is not None:
|
||||
obj_data = bpy.context.object
|
||||
obj_name = obj_data.name
|
||||
else:
|
||||
obj_data = None
|
||||
|
||||
if bpy.context.object.mode == "OBJECT":
|
||||
if event_id == "obj_location":
|
||||
vec = bpy.context.object.location
|
||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.loc.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
|
||||
write_patch(js)
|
||||
if obj_data is not None and obj_data.mode == "OBJECT":
|
||||
if event_id == "obj_location":
|
||||
vec = obj_data.location
|
||||
js = f'var o = iron.Scene.active.getChild("{obj_name}"); if (o) {{ o.transform.loc.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true; }}'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'obj_scale':
|
||||
vec = bpy.context.object.scale
|
||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.scale.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
|
||||
write_patch(js)
|
||||
elif event_id == 'obj_scale':
|
||||
vec = obj_data.scale
|
||||
js = f'var o = iron.Scene.active.getChild("{obj_name}"); if (o) {{ o.transform.scale.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true; }}'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'obj_rotation':
|
||||
vec = bpy.context.object.rotation_euler.to_quaternion()
|
||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.rot.set({vec[1]}, {vec[2]}, {vec[3]}, {vec[0]}); o.transform.dirty = true;'
|
||||
write_patch(js)
|
||||
elif event_id == 'obj_rotation':
|
||||
vec = obj_data.rotation_euler.to_quaternion()
|
||||
js = f'var o = iron.Scene.active.getChild("{obj_name}"); if (o) {{ o.transform.rot.set({vec[1]}, {vec[2]}, {vec[3]}, {vec[0]}); o.transform.dirty = true; }}'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_color':
|
||||
light: bpy.types.Light = bpy.context.object.data
|
||||
vec = light.color
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.color[0]={vec[0]}; lRaw.color[1]={vec[1]}; lRaw.color[2]={vec[2]};'
|
||||
write_patch(js)
|
||||
elif event_id == 'light_color':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
vec = light.color
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.color[0]={vec[0]}; lRaw.color[1]={vec[1]}; lRaw.color[2]={vec[2]};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_energy':
|
||||
light: bpy.types.Light = bpy.context.object.data
|
||||
elif event_id == 'light_energy':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
# Align strength to Leenkx, see exporter.export_light()
|
||||
# TODO: Use exporter.export_light() and simply reload all raw light data in Iron?
|
||||
strength_fac = 1.0
|
||||
if light.type == 'SUN':
|
||||
strength_fac = 0.325
|
||||
elif light.type in ('POINT', 'SPOT', 'AREA'):
|
||||
strength_fac = 0.01
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.strength={light.energy * strength_fac};'
|
||||
write_patch(js)
|
||||
|
||||
# Align strength to Leenkx, see exporter.export_light()
|
||||
# TODO: Use exporter.export_light() and simply reload all raw light data in Iron?
|
||||
strength_fac = 1.0
|
||||
if light.type == 'SUN':
|
||||
strength_fac = 0.325
|
||||
elif light.type in ('POINT', 'SPOT', 'AREA'):
|
||||
strength_fac = 0.01
|
||||
elif event_id == 'light_shadow':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.cast_shadow={"true" if light.use_shadow else "false"};'
|
||||
write_patch(js)
|
||||
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.strength={light.energy * strength_fac};'
|
||||
write_patch(js)
|
||||
elif event_id == 'light_near':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.near_plane={light.lnx_clip_start};'
|
||||
write_patch(js)
|
||||
|
||||
else:
|
||||
patch_export()
|
||||
elif event_id == 'light_far':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.far_plane={light.lnx_clip_end};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_fov':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.fov={light.lnx_fov};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_bias':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.shadows_bias={light.lnx_shadows_bias * 0.0001};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_spot_size':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
half_angle = light.spot_size * 0.5
|
||||
outer_cos = math.cos(half_angle)
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.spot_size={outer_cos};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_spot_blend':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
half_angle = light.spot_size * 0.5
|
||||
outer_cos = math.cos(half_angle)
|
||||
blend = max(0.0, min(1.0, light.spot_blend))
|
||||
inner_angle = math.atan(math.tan(half_angle) * (1.0 - blend))
|
||||
spot_blend = max(0.0001, math.cos(inner_angle) - outer_cos)
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.spot_blend={spot_blend};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_size':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.light_size={light.shadow_soft_size * 10};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'light_area_size':
|
||||
light: bpy.types.Light = obj_data.data
|
||||
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.size={light.size}; lRaw.size_y={light.size_y};'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'cam_props':
|
||||
cam: bpy.types.Camera = obj_data.data
|
||||
render = bpy.context.scene.render
|
||||
projection = obj_data.calc_matrix_camera(
|
||||
bpy.context.evaluated_depsgraph_get(),
|
||||
x=render.resolution_x,
|
||||
y=render.resolution_y,
|
||||
scale_x=render.pixel_aspect_x,
|
||||
scale_y=render.pixel_aspect_y)
|
||||
focal_y = projection[1][1]
|
||||
depth_z = projection[2][2]
|
||||
depth_w = projection[2][3]
|
||||
far_near_ratio = (depth_z - 1.0) / (depth_z + 1.0)
|
||||
fov = 2.0 * math.atan(1.0 / focal_y)
|
||||
near = (depth_w * (1.0 - far_near_ratio)) / (2.0 * far_near_ratio)
|
||||
far = far_near_ratio * near
|
||||
cam_obj = f'iron.Scene.active.getCamera("{cam.name}")'
|
||||
js = f'var c = {cam_obj}; var cRaw = c.data.raw; cRaw.fov={fov}; cRaw.near_plane={near}; cRaw.far_plane={far}; cRaw.frustum_culling={"true" if cam.lnx_frustum_culling else "false"}; c.buildProjection();'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'obj_visibility':
|
||||
visible = not obj_data.hide_get()
|
||||
js = f'var o = iron.Scene.active.getChild("{obj_name}"); if (o) o.visible = {"true" if visible else "false"};'
|
||||
write_patch(js)
|
||||
|
||||
elif obj_data is not None:
|
||||
patch_export()
|
||||
|
||||
if event_id == 'world_clear_color':
|
||||
color, strength = get_world_clear_color()
|
||||
bg_int = (int(color[3] * 255) << 24) + (int(color[0] * 255) << 16) + (int(color[1] * 255) << 8) + int(color[2] * 255)
|
||||
js = f'var w = iron.Scene.active.world; if (w) {{ w.raw.background_color={bg_int}; if (w.probe) w.probe.raw.strength={strength}; }}'
|
||||
write_patch(js)
|
||||
|
||||
if event_id == 'ln_insert_link':
|
||||
node: LnxLogicTreeNode
|
||||
@ -414,6 +604,12 @@ def on_operator(operator_id: str):
|
||||
send_event('obj_rotation')
|
||||
elif operator_id == 'TRANSFORM_OT_resize':
|
||||
send_event('obj_scale')
|
||||
elif operator_id in ('OBJECT_OT_hide_view_set', 'OBJECT_OT_hide_view_clear', 'OBJECT_OT_visible_all', 'OUTLINER_OT_hide'):
|
||||
visible = operator_id != 'OBJECT_OT_hide_view_set'
|
||||
if hasattr(bpy.context, 'object') and bpy.context.object is not None:
|
||||
obj_name = bpy.context.object.name
|
||||
js = f'var o = iron.Scene.active.getChild("{obj_name}"); if (o) o.visible = {"true" if visible else "false"};'
|
||||
write_patch(js)
|
||||
|
||||
# Rebuild
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user