Files
blender-portable-repo/scripts/addons/lighting_overrider/override_picker.py
T
2026-03-17 14:58:51 -06:00

296 lines
11 KiB
Python

# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
import re
import idprop
from . import utils
from .categories import rna_overrides
def struct_from_rna_path(rna_path):
''' Returns struct object for specified rna path.
'''
if not '.' in rna_path:
return None
elements = rna_path.rsplit('.', 1)
if '][' in elements[1]:
struct_path = f"{elements[0]}.{elements[1]}"
else:
struct_path = f"{elements[0]}.bl_rna.properties['{elements[1]}']"
try:
return eval(struct_path)
except:
return None
def stylize_name(path):
''' Splits words by '_', capitalizes them and separates them with ' '.
'''
custom_prop = utils.parse_rna_path_for_custom_property(path)
if custom_prop:
return f"{eval(custom_prop[0]+'.name')}: {custom_prop[1]}"
path_elements = utils.parse_rna_path_to_elements(path)
parent = '.'.join(path_elements[:-1])
main = path_elements[-1]
try:
if main in ['default_value']:
return eval(parent).name
else:
return f"{eval(parent).name}: {' '.join([word.capitalize() for word in main.split('_')])}"
except:
return ' '.join([word.capitalize() for word in main.split('_')])
class LOR_OT_override_picker(bpy.types.Operator):
"""Adds an operator on button mouse hover"""
bl_idname = "lighting_overrider.override_picker"
bl_label = "Add RNA Override"
bl_options = {'UNDO'}
rna_path: bpy.props.StringProperty(name="Data path to override", default="")
override_float: bpy.props.FloatProperty(name="Override", default=0)
batch_override: bpy.props.BoolProperty(name="Batch Override", default=False, options={'SKIP_SAVE'})
init_val = None
property = None
override = None
name_string = 'RNA Override'
type = 'VALUE'
_array_path_re = re.compile(r'^(.*)\[[0-9]+\]$')
@classmethod
def poll(cls, context):
#print(f'poll: {bpy.ops.ui.copy_data_path_button.poll()}')
return True
def draw(self, context):
layout = self.layout
property = self.property
if property is None:
return
col = layout.column()
col.scale_y = 1.8
col.scale_x = 1.5
if self.type == 'COLOR':
col.template_color_picker(context.scene, 'override', value_slider=True)
col = layout.column()
col.prop(context.scene, 'override')
self.override = context.scene['override']
if self.batch_override:
row = layout.row()
row.alert = True
row.label(text=f'Batch Overriding {len(context.selected_objects)} Objects', icon='DOCUMENTS')
def invoke(self, context, event):
if not bpy.ops.ui.copy_data_path_button.poll():
return {'PASS_THROUGH'}
clip = context.window_manager.clipboard
bpy.ops.ui.copy_data_path_button(full_path=True)
rna_path = context.window_manager.clipboard
context.window_manager.clipboard = clip
if rna_path.endswith('name'):
print("Warning: Don't override datablock names.")
return {'CANCELLED'}
if not rna_path.startswith('bpy.data.objects'):
self.batch_override = False
# Strip off array indices (f.e. 'a.b.location[0]' -> 'a.b.location')
m = self._array_path_re.match(rna_path)
if m:
rna_path = m.group(1)
self.rna_path = rna_path
self.property = struct_from_rna_path(rna_path)
if self.property is None:
print("Warning: No struct was found for given RNA path.")
return {'CANCELLED'}
self.name_string = stylize_name(self.rna_path)
if 'type' in dir(self.property):
# Gather UI data
keys = ['description', 'default', 'min', 'max', 'soft_min', 'soft_max', 'step', 'precision', 'subtype']
vars = {}
for key in keys:
try:
vars[key] = eval(f'self.property.{key}')
except:
print(f'{key} not in property')
if self.property.type == 'FLOAT':
vars['unit'] = self.property.unit
if not self.property.is_array:
bpy.types.Scene.override = bpy.props.FloatProperty(name = self.name_string, **vars)
else:
vars['size'] = self.property.array_length
vars['default'] = self.property.default_array[:]
if vars['subtype'] == 'COLOR':
self.type = 'COLOR'
else:
self.type = 'VECTOR'
bpy.types.Scene.override = bpy.props.FloatVectorProperty(name = self.name_string, **vars)
elif self.property.type == 'STRING':
bpy.types.Scene.override = bpy.props.StringProperty(name = self.name_string, **vars)
self.type = 'STRING'
elif self.property.type == 'BOOLEAN':
bpy.types.Scene.override = bpy.props.BoolProperty(name = self.name_string, **vars)
self.type = 'BOOL'
elif self.property.type == 'INT':
bpy.types.Scene.override = bpy.props.IntProperty(name = self.name_string, **vars)
self.type = 'INTEGER'
elif self.property.type == 'ENUM':
self.type = 'STRING'
items = [(item.identifier, item.name, item.description, item.icon, i) for i, item in enumerate(self.property.enum_items)]
vars.pop('subtype', None)
bpy.types.Scene.override = bpy.props.EnumProperty(items = items, name = self.name_string, **vars)
else:
vars = {}
custom_prop = utils.parse_rna_path_for_custom_property(self.rna_path)
if custom_prop:
data_block = eval(custom_prop[0])
property_name = custom_prop[1]
vars = data_block.id_properties_ui(property_name).as_dict()
if type(self.property) is float:
bpy.types.Scene.override = bpy.props.FloatProperty(name = self.name_string, **vars)
elif type(self.property) is idprop.types.IDPropertyArray:
bpy.types.Scene.override = bpy.props.FloatVectorProperty(name = self.name_string, size=len(self.property), **vars)
if vars['subtype'] in ['COLOR', 'COLOR_GAMMA']:
self.type = 'COLOR'
else:
self.type = 'VECTOR'
elif type(self.property) is str:
bpy.types.Scene.override = bpy.props.StringProperty(name = self.name_string, **vars)
self.type = 'STRING'
elif type(self.property) is int:
bpy.types.Scene.override = bpy.props.IntProperty(name = self.name_string, **vars)
self.type = 'INTEGER'
elif type(self.property) == bool:
bpy.types.Scene.override = bpy.props.BoolProperty(name = self.name_string, **vars)
self.type = 'BOOL'
# check for custom property
context.scene.override = eval(rna_path)
self.override = context.scene.override
self.init_val = eval(rna_path)
wm = context.window_manager
state = wm.invoke_props_dialog(self)
if state in {'FINISHED', 'CANCELLED'}:
del context.scene['override']
return state
else:
return state
def execute(self, context):
meta_settings = context.scene.LOR_Settings
settings = utils.get_settings(meta_settings)
path_elements = utils.parse_rna_path_to_elements(self.rna_path)
if context.scene.override==self.init_val:
del context.scene['override']
return {'CANCELLED'}
utils.mute_animation_on_rna_path(self.rna_path)
if type(eval(self.rna_path)) == idprop.types.IDPropertyArray:
exec(self.rna_path+f'[:] = context.scene.override') # workaround for Blender not retaining UI data of property (see https://projects.blender.org/blender/blender/pulls/109203)
else:
exec(self.rna_path+f' = context.scene.override')
add_info=[self.name_string, self.rna_path, context.scene.override, self.type]
rna_overrides.add_rna_override(context, add_info)
# TODO deduplicate with utils
if path_elements[2].startswith('objects'):
db_path = '.'.join(path_elements[:3])
if 'session_uid' in dir(eval(db_path)):
data_block = eval(db_path)
subpath = f'.{".".join(path_elements[3:])}'
else: # handle custom props
db_path, c_prop = utils.parse_rna_path_for_custom_property(self.rna_path)
data_block = eval(db_path)
subpath = f'["{c_prop}"]'
data_block.update_tag()
if not self.batch_override:
del context.scene['override']
return {'FINISHED'}
for ob in context.selected_objects:
if ob.library:
rna_path = f'bpy.data.objects["{ob.name}", "{ob.library.filepath}"]{subpath}'
else:
rna_path = f'bpy.data.objects["{ob.name}"]{subpath}'
utils.mute_animation_on_rna_path(rna_path)
try:
eval(rna_path)
except:
continue
if type(eval(rna_path)) == idprop.types.IDPropertyArray:
exec(rna_path+f'[:] = context.scene.override') # workaround for Blender not retaining UI data of property (see https://projects.blender.org/blender/blender/pulls/109203)
else:
exec(rna_path+f' = context.scene.override')
name_string = stylize_name(rna_path)
add_info = [name_string, rna_path, context.scene.override, self.type]
rna_overrides.add_rna_override(context, add_info)
utils.kick_evaluation(list(context.selected_objects))
del context.scene['override']
utils.kick_evaluation()
return {'FINISHED'}
def cancel(self, context):
del context.scene['override']
return
classes = [
LOR_OT_override_picker,
]
def register():
for c in classes:
bpy.utils.register_class(c)
wm = bpy.context.window_manager
if wm.keyconfigs.addon is not None:
km = wm.keyconfigs.addon.keymaps.new(name="User Interface")
kmi = km.keymap_items.new("lighting_overrider.override_picker","O", "PRESS",shift=False, ctrl=False)
kmi.properties.batch_override = False
kmi = km.keymap_items.new("lighting_overrider.override_picker","O", "PRESS",shift=False, ctrl=False, alt=True)
kmi.properties.batch_override = True
def unregister():
for c in classes:
bpy.utils.unregister_class(c)
if __name__ == "__main__":
register()