forked from LeenkxTeam/LNXSDK
Live patch - Light properties, object visibility and world
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import math
|
||||||
from typing import Any, Type
|
from typing import Any, Type
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@ -52,6 +53,24 @@ def start():
|
|||||||
for light_type in (bpy.types.AreaLight, bpy.types.PointLight, bpy.types.SpotLight, bpy.types.SunLight):
|
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, "color", "light_color")
|
||||||
listen(light_type, "energy", "light_energy")
|
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
|
global __running
|
||||||
__running = True
|
__running = True
|
||||||
@ -78,9 +97,59 @@ def get_obj_transforms(obj):
|
|||||||
|
|
||||||
def get_light_props(obj):
|
def get_light_props(obj):
|
||||||
light = obj.data
|
light = obj.data
|
||||||
color = (round(light.color[0], 6), round(light.color[1], 6), round(light.color[2], 6))
|
props = {
|
||||||
energy = round(light.energy, 6)
|
'light_color': (round(light.color[0], 6), round(light.color[1], 6), round(light.color[2], 6)),
|
||||||
return color, energy
|
'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 = {
|
DEPSGRAPH_HANDLERS = {
|
||||||
'is_updated_transform': [
|
'is_updated_transform': [
|
||||||
@ -107,20 +176,54 @@ def on_depsgraph_update(updates):
|
|||||||
for event_id, extractor in DEPSGRAPH_HANDLERS['is_updated_transform']:
|
for event_id, extractor in DEPSGRAPH_HANDLERS['is_updated_transform']:
|
||||||
current = extractor(uid)
|
current = extractor(uid)
|
||||||
cache_key = (event_id, name)
|
cache_key = (event_id, name)
|
||||||
if current != scene_cache.get(cache_key):
|
if cache_key not in scene_cache:
|
||||||
scene_cache[cache_key] = current
|
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
|
changed = True
|
||||||
|
|
||||||
if update.is_updated_shading and isinstance(uid.data, bpy.types.Light):
|
if update.is_updated_shading and isinstance(uid.data, bpy.types.Light):
|
||||||
color, energy = get_light_props(uid)
|
for event_id, current in get_light_props(uid).items():
|
||||||
for event_id, current in (('light_color', color), ('light_energy', energy)):
|
|
||||||
cache_key = (event_id, uid.data.name)
|
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
|
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
|
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
|
return changed
|
||||||
|
|
||||||
|
|
||||||
@ -189,47 +292,134 @@ def send_event(event_id: str, opt_data: Any = None):
|
|||||||
if not __running:
|
if not __running:
|
||||||
return
|
return
|
||||||
|
|
||||||
if hasattr(bpy.context, 'object') and bpy.context.object is not None:
|
obj_name = opt_data if isinstance(opt_data, str) else None
|
||||||
obj = bpy.context.object.name
|
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 obj_data is not None and obj_data.mode == "OBJECT":
|
||||||
if event_id == "obj_location":
|
if event_id == "obj_location":
|
||||||
vec = bpy.context.object.location
|
vec = obj_data.location
|
||||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.loc.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
|
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)
|
write_patch(js)
|
||||||
|
|
||||||
elif event_id == 'obj_scale':
|
elif event_id == 'obj_scale':
|
||||||
vec = bpy.context.object.scale
|
vec = obj_data.scale
|
||||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.scale.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
|
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)
|
write_patch(js)
|
||||||
|
|
||||||
elif event_id == 'obj_rotation':
|
elif event_id == 'obj_rotation':
|
||||||
vec = bpy.context.object.rotation_euler.to_quaternion()
|
vec = obj_data.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;'
|
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)
|
write_patch(js)
|
||||||
|
|
||||||
elif event_id == 'light_color':
|
elif event_id == 'light_color':
|
||||||
light: bpy.types.Light = bpy.context.object.data
|
light: bpy.types.Light = obj_data.data
|
||||||
vec = light.color
|
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]};'
|
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)
|
write_patch(js)
|
||||||
|
|
||||||
elif event_id == 'light_energy':
|
elif event_id == 'light_energy':
|
||||||
light: bpy.types.Light = bpy.context.object.data
|
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()
|
elif event_id == 'light_shadow':
|
||||||
# TODO: Use exporter.export_light() and simply reload all raw light data in Iron?
|
light: bpy.types.Light = obj_data.data
|
||||||
strength_fac = 1.0
|
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.cast_shadow={"true" if light.use_shadow else "false"};'
|
||||||
if light.type == 'SUN':
|
write_patch(js)
|
||||||
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};'
|
elif event_id == 'light_near':
|
||||||
write_patch(js)
|
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:
|
elif event_id == 'light_far':
|
||||||
patch_export()
|
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':
|
if event_id == 'ln_insert_link':
|
||||||
node: LnxLogicTreeNode
|
node: LnxLogicTreeNode
|
||||||
@ -414,6 +604,12 @@ def on_operator(operator_id: str):
|
|||||||
send_event('obj_rotation')
|
send_event('obj_rotation')
|
||||||
elif operator_id == 'TRANSFORM_OT_resize':
|
elif operator_id == 'TRANSFORM_OT_resize':
|
||||||
send_event('obj_scale')
|
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
|
# Rebuild
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user