Undo Crashing my Add-on - Something to do with collection linking?

Hey All. I’m banging my head against a wall. Below is a basic chunk of code I had, stripped down as simple as I can.

Running this code creates a panel called Tools in the UI (the one brought up with the N key)

Normally, clicking re(Generate) works normally. It creates a Grease Pencil object based on the name selected.

The crash I’m experiencing happens when I do this exactly.
a. Have an existing “Tools” collection.
b. Run the script in the text editor (this is to simulate an existing Tools collection)
c. From the panel, click Generate. (sometimes I click between One and Two, but it seems to crash reliably without now)
d. Do a Ctrl + Z to undo.
e. Crash.

The reason I want a collection to be pre-existing is because, I want the code to create the collection if it doesn’t exist, and if it does exist, use the collection.

The crash seems to be happening around the two “.link” lines for the collection (ie: if I don’t use the collection, it doesn’t crash), so I’m pretty certain something is going on with the Tools collection.

Any seasoned python folks have any ideas? I’ve tried this on fresh .blend files and the behavior is the same.

import bpy
from bpy import context
from bpy.props import PointerProperty, EnumProperty

bl_info = {
    'name': 'Tools',
    'category': 'All',
    'author': 'NC_Sketchy',
    'version': (1, 0, 0),
    'blender': (3, 1, 0),
    'location': '',
    'description': 'Tools'
}

#Global Vars
One = "One"
Two = "Two"
Three = "Three"
Tools_Collection = "Tools"


#PANEL FOR UI###############
class PP_PT_main(bpy.types.Panel):
    bl_idname = "pp.panel"
    bl_label = "Tools Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Tools"

    def draw(self, context):        
        #Staging
        scene = context.scene
        layout = self.layout
        PPSettings = scene.PPSettings
        NewOperator = layout.operator

        row = layout.row()
        row.prop(PPSettings, "PPEnum", text="Type", expand=True)

        NewOperator("pp.generator", icon='MESH_CUBE', text = "(re)Generate")


class PP_OT_properties(bpy.types.PropertyGroup):
    PPEnum : bpy.props.EnumProperty(
        name = "",
        description = "Select an Option",
        items = [
            ('OnePP_Option' , "One", "One"),
            ('TwoPP_Option' , "Two", "Two"),
            ('ThreePP_Option' , "Three", "Three"),
        ]
    )     

class PP_OT_generator(bpy.types.Operator):
    bl_idname = "pp.generator"
    bl_label = "Generator"

    def execute(self,context):
        def grid_generator(gp_name):
            try:
                bpy.data.collections[Tools_Collection]
            except:
                bpy.data.collections.new(Tools_Collection)
                bpy.context.scene.collection.children.link(bpy.data.collections[Tools_Collection])

            #data is added to an object and linked to the scene
            gp_data = bpy.data.grease_pencils.new(gp_name)            
            gp_obj = bpy.data.objects.new(gp_data.name, gp_data)

            bpy.data.collections[Tools_Collection].objects.link(gp_obj)  

        match bpy.context.scene.PPSettings.PPEnum:
            case "OnePP_Option":
                typeName=One
            case "TwoPP_Option":
                typeName=Two
            case "ThreePP_Option":
                typeName=Three

        try:
            bpy.data.grease_pencils.remove(bpy.data.grease_pencils[typeName])
        except:
            "Not There"
            
        grid_generator(typeName)
        
        return {'FINISHED'}

register_me = [
    PP_OT_properties, 
    PP_PT_main,
    PP_OT_generator
    ]

def register():
    for i in register_me:
        bpy.utils.register_class(i)
        
    bpy.types.Scene.PPSettings = bpy.props.PointerProperty(type=PP_OT_properties)

def unregister():
    for i in register_me:
        bpy.utils.unregister_class(i)
        
    del bpy.types.Scene.PPSettings

if __name__ == '__main__':
    register()

probably has something to do with the fact that you didn’t include the “UNDO” option in your operator.

class PP_OT_generator(bpy.types.Operator):
    bl_idname = "pp.generator"
    bl_label = "Generator"
    bl_options = {'UNDO'} # required if you want to undo.

https://docs.blender.org/api/current/bpy_types_enum_items/operator_type_flag_items.html#operator-type-flag-items

1 Like

Welp. that did it! Today I learned that bl_options exists :slight_smile: