0
$\begingroup$

I have an addon which shows a panel for objects with a custom data field.

    bpy.types.Object.AS_placeholder_properties = bpy.props.PointerProperty(
        type=AS_placeholder_properties,
        poll=lambda self, object: self['AS_placeholder'],
    )

I have an operator which creates the object and adds the AS_placeholder field, but I want to change the name of the field.

The issue is that there are already some files that have the old name, so if I update my addon with the new name for the custom data field, the old files will stop working.

Is there any way to update the files when load? I am thinking on adding some "migration" code in my addon which, when a file is loaded, iterates on all the objects, checks if it has the old name and update it, but I have no idea how to do something when the files is loaded.

$\endgroup$
1
$\begingroup$

On load handlers.

One way to approach the load event is with a persistent load post handler. However many side cases will be missed by it. Addon first enabled. An appended object with the old prop.

bl_info = { # fill in to test as addon
}


import bpy
from bpy.props import PointerProperty, BoolProperty, FloatProperty
from bpy.types import PropertyGroup

class Foo(PropertyGroup):
    bar : BoolProperty()
    foo : FloatProperty()

classes = (Foo,)

@bpy.app.handlers.persistent
def convert(dummy):
    old_key = "prop"
    new_key = "foo"
    print("Convert") 
    obs = [o for o in bpy.data.objects if o.get(old_key)]
    if obs:
        print("Converting...")
        for o in obs:
            if o.get(old_key):
                o[new_key] = o[old_key]
                del o[old_key]

def draw_test(self, context):
    layout = self.layout
    layout.prop(context.object.foo, "foo")            
        
def register():
    for cls in classes:
        bpy.utils.register_class(cls)
        
    bpy.types.Object.foo = PointerProperty(
            type=Foo,
            )
    bpy.types.TEXT_HT_footer.prepend(draw_test)
    bpy.app.handlers.load_post.append(convert)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    bpy.app.handlers.remove(convrt)

if __name__ == "__main__":
    register()

Copy Custom Properties from one object to another Can ignore the '_RNA_UI' in this case, since its members are emulating those defined by the bpy.props property for drawing the prop in the UI. (name, min, max, soft_min etc).

Consider wiring into panel code.

Instead could roll back the panel to do no more than alert that an old property is set on an object and the fix button.

Example below, a red splash screen button is displayed if an object in the file is given a custom prop named "prop".

import bpy
### hacked in by way of example
class AS_placeholder_properties(bpy.types.PropertyGroup):
    foo : bpy.props.BoolProperty()
    pass
bpy.utils.register_class(AS_placeholder_properties)

bpy.types.Object.AS_placeholder_properties = bpy.props.PointerProperty(
        type=AS_placeholder_properties,
        poll=lambda self, obj: obj.get('AS_placeholder') is not None,
    )
####
class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"
            
    @classmethod
    def poll(cls, context):
        old_prop = "prop"
        if any(ob.get(old_prop) is not None for ob in context.blend_data.objects):
            cls.draw = cls.convert
        else:
            cls.draw = cls.draw_normal
        return True

    def convert(self, context):
        layout=self.layout
        layout.alert = True
        layout.operator("wm.splash") # convert op
        
    def draw_normal(self, context):
        layout = self.layout

        obj = context.object
        row = layout.row()
        row.prop(obj.AS_placeholder_properties, "foo")

        row = layout.row()
        row.operator("mesh.primitive_cube_add")
    draw = draw_normal

def register():
    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()

The poll method above could instead test if your convert operator polls

if bpy.ops.object.convert2newpropname.poll():

intrigued by the poll method used for property group above. Would seem to always poll unless class is totally passed in which case will be boolean False. (eg comment out foo boolean above) In as much as the bpy.props is defining the class and creating the custom prop, IMO always safest to use

ob.get("custom_prop_name")

or

ob.get("custom_prop_name", some_fallback_other_than_None)
$\endgroup$
6
  • $\begingroup$ The issue with the button is that the user has to select the object to be able to see the info. What happens if the user doesn't select the object? $\endgroup$
    – jjcasmar
    Feb 4 at 15:05
  • $\begingroup$ Why is this the case? $\endgroup$
    – batFINGER
    Feb 4 at 15:10
  • $\begingroup$ I think I read incorrectly the code. This is showing the button if any of the objects in the file contains the old property, right? $\endgroup$
    – jjcasmar
    Feb 4 at 16:44
  • $\begingroup$ Yes, the poll always returns True. When any object has property "prop" it displays only a red button. The return of the poll can be manipulated by this too. Can run the code in the text editor to test. $\endgroup$
    – batFINGER
    Feb 4 at 16:46
  • $\begingroup$ Thanks. its not automatic, but probably may be the best. Is there any way to show a warning in big somewhere or something like that? $\endgroup$
    – jjcasmar
    Feb 4 at 16:48

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.