1
$\begingroup$

I would think that there would be some (add-on?) way to hover over a property and RMB create a driver that is driven by a new Property of the similar name:

enter image description here

enter image description here

Does such a function or add-on exist? It's a rather laborious process to make the new property and copy/paste as a driver, especially if I need many properties that allow for many linked library overrides.

$\endgroup$
1
  • 2
    $\begingroup$ I don't know of one, and because of the way the UI works, I think it might be hard to do. You can't hover over an arbitrary input and find out what property that input is for, as far as I know. $\endgroup$ Commented Jul 15, 2022 at 22:47

2 Answers 2

1
$\begingroup$

I have come up with an easy workaround solution that will lessen your burden with two (2) easy steps using keying_sets to do the copying of the field's data_path which will also be used to derive the name of the field using get_property_name.

1.) Right click ( RMB ) the property to drive then select Add to Keying Set
2.) Run Script and you're done!

This will automatically add a driver to the property you have right clicked and is driven by a newly created Custom Property with the same name. Note that the script will only run if you have done step 1. It automatically removes the keying_sets after execution.

enter image description here

import bpy

# First right click property and select "Add to Keying Set"
ks = bpy.context.scene.keying_sets[0].paths[0]
ks_use_entire_array = ks if ks.use_entire_array else False
obj = bpy.context.object

# ========================================================================
# Function declarations
# ========================================================================

def get_property_name(data_path) -> str:
    s = data_path
    i = s.find('.')
    data_path_1 = ""
    data_path_2 = data_path
    if i > 0:
        ri = s.rindex('.')
        data_path_1 = '.' + s[0:ri]
        data_path_2 = s[ri+1:len(s)]
    data = eval("bpy.context.object" + data_path_1)
    return data.rna_type.properties[data_path_2].name if hasattr(data, data_path_2) else ''

def clear_drivers(obj, data_path, index):
    if obj.animation_data is None:
        return
    drivers = obj.animation_data.drivers

    for d in drivers:
        if d.data_path != DATA_PATH:
            continue
        if index < 0:
            for idx in range(-1,3):
                obj.driver_remove(d.data_path, idx)
        else:
            obj.driver_remove(d.data_path, index)

def create_driver(fcurve, obj, custom_prop):
    d = fcurve.driver
    v = d.variables.new()
    v.name = "myvar"

    target = v.targets[0]
    target.id_type = 'OBJECT'
    target.id = obj
    target.data_path = '["' + custom_prop + '"]'
    d.expression = "myvar"

# ========================================================================
# Initialize important properties
# ========================================================================

DATA_PATH = ks.data_path
INDEX = -1 if ks_use_entire_array else ks.array_index
CUSTOM_PROPERTY = get_property_name(DATA_PATH)

bpy.ops.anim.keying_set_remove()

if INDEX < 0:
    for idx in range(-1,3):
        custom_prop = CUSTOM_PROPERTY + ' [' + str(idx) + ']'
        if custom_prop in obj:
            del obj[custom_prop]
else:
    CUSTOM_PROPERTY = CUSTOM_PROPERTY + ' [' + str(INDEX) + ']'

# ========================================================================
# Execute: Add property and driver
# ========================================================================

if CUSTOM_PROPERTY in obj:
    print("WARNING: to prevent overwriting custom property, substituting with data_path name")

obj[CUSTOM_PROPERTY] = 1.0 # you can add the custom property like this

clear_drivers(obj, DATA_PATH, INDEX)

fcurve_or_list = obj.driver_add(DATA_PATH, INDEX)

if type(fcurve_or_list) is list:
    for fcurve in fcurve_or_list:
        create_driver(fcurve, obj, CUSTOM_PROPERTY)
else:
    create_driver(fcurve_or_list, obj, CUSTOM_PROPERTY)

Sample output of some properties I have added driver to. You can use Add Single to Keying Set which will create a name with array Property Name [x] as well as Add All to Keying Set. It will work with almost all fields including fields in Modifiers.

enter image description here

$\endgroup$
3
  • $\begingroup$ This would be a timesaving workflow. LM try it out: seems to only convert one item from the keyset per Run (and deletes all). Perhaps just some needed surrounding for loop? $\endgroup$
    – james_t
    Commented Jul 16, 2022 at 15:47
  • 1
    $\begingroup$ Thanks again! I added my own answer where I use your solution with some changes to add a loop for all keying set additions, and do a bit more about type() $\endgroup$
    – james_t
    Commented Jul 16, 2022 at 20:02
  • 1
    $\begingroup$ you're welcome! glad it works for u. yeah I didn't do the loop coz i wanted to see the result immediately per driver add but good thing u were able to adjust the code to ur needs. btw i'm still working on finding a solution to the get_property question so we can handle node trees (shaders/geometry-nodes) as well. maybe u can find a general solution $\endgroup$
    – Harry McKenzie
    Commented Jul 16, 2022 at 20:20
1
$\begingroup$

I took Harry McKenzie's answer and added some of my needs:

  1. needed a for loop to process all of the keying set members added
  2. detect the type of the property for the Custom Property and set up some values
  3. enabled the Library Override in the created custom property
import bpy

# ========================================================================
# Function declarations
# ========================================================================

def get_property_name(obj,data_path) -> str:
    s = data_path
    i = s.find('.')
    data_path_1 = ""
    data_path_2 = data_path
    if i > 0:
        ri = s.rindex('.')
        data_path_1 = '.' + s[0:ri]
        data_path_2 = s[ri+1:len(s)]
    data = eval("bpy.context.object" + data_path_1)
    print('get_property_name data=',data)
    return data.rna_type.properties[data_path_2].name if hasattr(data, data_path_2) else ''

def get_property_type(obj,data_path) -> type:
    print(obj,'....',data_path)
    #getattr(obj,data_path)
    return type(obj.path_resolve(data_path))

def clear_drivers(obj, data_path, index):
    if obj.animation_data is None:
        return
    drivers = obj.animation_data.drivers

    for d in drivers:
        if d.data_path != data_path:
            continue
        if index < 0:
            for idx in range(-1,3):
                obj.driver_remove(d.data_path, idx)
        else:
            obj.driver_remove(d.data_path, index)

def create_driver(fcurve, obj, custom_prop):
    d = fcurve.driver
    v = d.variables.new()
    v.name = "myvar"

    target = v.targets[0]
    target.id_type = 'OBJECT'
    target.id = obj
    target.data_path = '["' + custom_prop + '"]'
    d.expression = "myvar"
    print('create_driver for ',custom_prop)

# ========================================================================
# Initialize properties from first keying set
# ========================================================================
def keySetToDrivers():
    if(len(bpy.context.scene.keying_sets) == 0):
        print('First right click property and select "Add to Keying Set".')
        return
    ksPaths = bpy.context.scene.keying_sets[0].paths
    if(len(ksPaths)==0):
        print('First right click property and select "Add to Keying Set"')
        bpy.ops.anim.keying_set_remove()
        return

    for ks in ksPaths:   
        obj = bpy.data.objects[ks.id.name] # get the object for the custom props
        ks_use_entire_array = ks if ks.use_entire_array else False
        if(ks_use_entire_array):print('ks_use_entire_array for',ks.id.name)
        DATA_PATH = ks.data_path
        INDEX = -1 if ks_use_entire_array else ks.array_index
        
        CUSTOM_PROPERTY = get_property_name(obj,DATA_PATH)
        
        if ( CUSTOM_PROPERTY == '' ): continue # skip if error return

        print('DATA_PATH',DATA_PATH)

        if INDEX < 0:
            for idx in range(-1,3):
                custom_prop = CUSTOM_PROPERTY + ' [' + str(idx) + ']'
                if custom_prop in obj:
                    del obj[custom_prop]
        else:
            CUSTOM_PROPERTY = CUSTOM_PROPERTY + ' [' + str(INDEX) + ']'
            if CUSTOM_PROPERTY in obj:
                del obj[CUSTOM_PROPERTY]

        # ========================================================================
        # Add property and driver
        # ========================================================================

        if CUSTOM_PROPERTY in obj:
            print("WARNING: to prevent overwriting custom property, substituting with data_path name")
            continue

        clear_drivers(obj, DATA_PATH, INDEX)
        print('CUSTOM_PROPERTY',CUSTOM_PROPERTY)
        p = bpy.props.BoolProperty(name=CUSTOM_PROPERTY, default=0, options={'ANIMATABLE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}, tags=set())
        initVal=0
        t=get_property_type(obj, DATA_PATH)
        print('get_property_type',t)
        if( t is type(bool())):
            print('type is bool (int)')
            initVal=0
        elif( t is type(int())):
            print('type is int')
            initVal=0
        elif( t is type(float())):
            print('type is float')
            initVal=0.0
        elif( t is type(str())):
            print('type is string')
            initVal=''
        
        obj[CUSTOM_PROPERTY] = initVal # you can add the custom property like this
        ui = obj.id_properties_ui(CUSTOM_PROPERTY)
        ui.update(description=DATA_PATH)
        if( t is type(bool())):
            print('type is bool')
            ui.update(default=0,min=0,max=1)
        elif( t is type(int())):
            print('type is int')
            ui.update(default=0,min=0)
        elif( t is type(float())):
            print('type is float')
            ui.update(default=0,min=0.0)
        elif( t is type(str())):
            print('type is string')
            #ui.update(default=0,min=0)
        #ui.update(default=0,min=0)
        obj.property_overridable_library_set('["'+CUSTOM_PROPERTY+'"]', True)
        
        fcurve_or_list = obj.driver_add(DATA_PATH, INDEX)

        if type(fcurve_or_list) is list:
            for fcurve in fcurve_or_list:
                create_driver(fcurve, obj, CUSTOM_PROPERTY)
        else:
            create_driver(fcurve_or_list, obj, CUSTOM_PROPERTY)

    bpy.ops.anim.keying_set_remove() # clean up
    
keySetToDrivers()
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .