0
$\begingroup$

I have a UIList with a "name" string property, it has an update function, but I need access to the name before and after the update, but as you run name_update() the name has obviously already changed:

name : StringProperty(
        name = "test",
        default = "",
        update = name_update
    )

~~~

def name_update(self, context):
    old_name = # need old name
    new_name = self.name
    print(old_name)
    print(new_name)

    return None
$\endgroup$
3
$\begingroup$

To elaborate on @Hikariztw 's answer

Using the code from https://blender.stackexchange.com/a/141207/15543 as an example.

Update to the property group def.

class MaterialSlot(PropertyGroup):
    def get_name(self):
        return self.get("name", "Foo")

    def set_name(self, value):
        oldname = self.get("name", "Foo")   
        self["name"] = value
        print(oldname, value)

    material : PointerProperty(type=bpy.types.Material)
    name : StringProperty(
            default="Foo", 
            get=get_name, 
            set=set_name)

Note the example code uses the material name in list UI class. Change to show item in draw method to show collection item name instead.

layout.prop(item, "name", text= "", emboss=False, icon_value=layout.icon(ma))

Python console code, added a new scene material slot. It is given the default name "Foo". Updating the name to "Bar", in this case via console, same if done via UI, and the setter is called. Which prints old name "Foo" and new name "Bar"

>>> s = C.scene.material_slots.add()
>>> s.name
'Foo'

>>> s.name = "Bar"
Foo Bar

and again from "Bar" to "Choo"

>>> s.name = "Choo"
Bar Choo

But here is the "rub" re the property default value. The PropertyGroup class already has a name attribute. When a new item is first added, it doesn't use the name property default of "Foo", but rather defaults to "".

>>> C.scene.material_slots.clear()
>>> s = C.scene.material_slots.add()
>>> s.name
'Foo'

>>> C.scene.material_slots.keys()
['']

ie collection has a material_slots[""] member rather than material_slots["Foo"] However if we set it implicitly, it picks up as expected... which is usual practice I suppose.

>>> s.name = "Foo"
Foo Foo

>>> C.scene.material_slots.keys()
['Foo']

EDIT re comment

so when I update the list element's name I need to find all objects with the original element name and then change it to the new name

For example above, to set all items with old name to new name, do this on a custom property level too. Add something like below to set_name method above.

if oldname != value:
    same = [s for s in self.id_data.material_slots
            if oldname == s.get("name", "Foo")]
    for s in same:
        s["name"] = value
$\endgroup$
  • $\begingroup$ Works perfectly thanks again. $\endgroup$ – Way2Close May 27 at 17:30
3
$\begingroup$

You can define setter function for it:

Getter/Setter

Getter/setter functions can be used for boolean, int, float, string and enum properties. If these callbacks are defined the property will not be stored in the ID properties automatically. Instead, the get and set functions will be called when the property is respectively read or written from the API.

From example:

def get_float(self):
    return self["testprop"]

def set_float(self, value):
    # You should get your previous value here
    self["testprop"] = value

bpy.types.Scene.test_float = bpy.props.FloatProperty(get=get_float, set=set_float)

Another way is to use a global variable to store the information(might be easier and simple):

name : StringProperty(update = name_update)

_OLD_NAME = None

def name_update(self, context):
    global _OLD_NAME
    if _OLD_NAME:
        print(_OLD_NAME)
    print(self.name)
    _OLD_NAME = self.name

Update

The original answer for the question: how to get old value

Using a global might not be a bad idea, but not a good approach though:

import bpy

_OLD_VALUE = None

def temp_update(self, context):
    global _OLD_VALUE
    if _OLD_VALUE:
        print("Old: {}".format(_OLD_VALUE))
    print("New: {}".format(context.scene.temp))
    _OLD_VALUE = context.scene.temp

bpy.types.Scene.temp = bpy.props.StringProperty(update = temp_update)

bpy.context.scene.temp = 'hello'
bpy.context.scene.temp = 'world'

# >New: hello
# >Old: hello
# >New: world
$\endgroup$
  • 1
    $\begingroup$ Related blender.stackexchange.com/a/134310/15543 above will throw a key error if not set. Also instead of global use the previously set custom prop. blender.stackexchange.com/a/135167/15543 $\endgroup$ – batFINGER May 27 at 8:59
  • $\begingroup$ @batFINGER Sorry for the incorrect answer. What is the difference between global approach and another prop? A better way that blender can handle the old_prop maybe? $\endgroup$ – Hikariztw May 27 at 9:23
  • 1
    $\begingroup$ IMO it's the code in the getter setter docs that is incorrect. Re global: With a getter setter the custom prop ob["foo"] is the old value before re-settting. Looking at the question again the issue is quite likely trying to redefine the name property. Eg try above on bpy.types.Scene.name = name Setting context.scene.name = "foo" will call the update and set a custom prop, however internally the scene is still named "Scene". bpy.data.scenes['Scene'] $\endgroup$ – batFINGER May 27 at 9:37
  • $\begingroup$ I'm sorry, I just tried all of these methods and I still don't understand how to get the values before and after update. My UIList gives selected objects an object property with the name of the list element, so when I update the list element's name I need to find all objects with the original element name and then change it to the new name. Sorry if i'm just missing the answer. Very confused :p $\endgroup$ – Way2Close May 27 at 11:11
  • $\begingroup$ Re answer update. What if there is more than one scene? $\endgroup$ – batFINGER May 27 at 18:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.