Is storing operator options in the scene / window manager (etc) still the way to go in 2023?

I am drafting a panel to set options for an operator. I gave the operator a property, which I figured I could expose to be set via the panel, but it appeared read-only / locked / frozen / unchageable:

After a lot of searching, lots of tabs opened, some hair and sanity lost and the beginning of a question was drafted, I stumbled upon this thread which shed some light on why:

An example given in that thread puts a reference to the PropertyGroup that has the operator options shared by the operator itself and the panel as a property of bpy.types.WindowManager (accessed via bpy.context.window_manager); the same approach as a much older Blender SE QA.

A tutorial on Medium that appears highly-ranked in search results stores properties under bpy.types.Scene instead.

Things can change over time, but old results linger. Q: Is stashing properties/options somewhere accessible to both the operator and the panel still the preferred approach in 2023? If so, is the WindowManager or the Scene preferred?

Cheers!

For others’ clarity - panels are redrawn via the draw() function constantly. The operator isn’t really initialized yet. The operator properties you declare in your panel are getting reset to their default values just as constantly, and therefore appear frozen.

Blender really wants to isolate properties in operators for decoupling purposes and prefers users use the redo panel for tweaking instead of exposed default values. However, there are more exceptions to that (the built-in remesh panel). And the redo panel also encourages operators to be fast with minimal side effects - tweaking a value and waiting for half a minute to see results gets discouraging.

The short but vague answer to your question is that it depends on your needs. But to be more helpful, this is a rough guide to how I determine where properties should go (I’m not pretending my opinion is factual, others are welcome to correct me):

  1. Given your property - is it specific to only one (or a couple) operators? Then its an operator property.
    • Would users run this operator multiple times with their own values? Then I will expose the default values on a panel via a keymap (maybe a PointerProperty if it’s more than one operator). Otherwise, I just leave it to be tweaked in the redo panel each time.
  2. So, the property is beyond just an operator or two, or even unrelated to any operator. Is it a setting users would want saved to the file and persist between sessions? Because one big distinction between WindowManager and Scene properties is that the latter are saved with the file. So if the property should be saved, make it a scene property. Otherwise, save it to the window manager.
    Granted, properties being saved with the file are probably not going to be a memory issue. I wouldn’t grumble that an add-on should be using the window manager instead, because most of the time the memory footprint is minimal.

EDIT:

  1. If the property’s persistence should span over multiple or all projects/files, then put it in the add-on preferences.

Again, Blender has a very strange user and developer experience when it comes to operators and their properties. It’s getting better though. Also listen to your customers (including if that’s just you). How do they prefer to manage those settings? Unless I’m running an operator many times, usually I’m okay with just using the redo panel each time.

1 Like

Thanks for taking the time to write this up. I would agree with ā€œBlender has a very strange user and developer experience when it comes to operators and their propertiesā€! It would help new addon developers if there was better user documentation on both how operators and their properties work, and why it is designed that way.

Interestingly, I have used the redo panel approximately… never? That might be on me being a relative Blender newbie, as my focus is on VSE these days. I looked up the key to show it (F9), and if I hit that after using my operator or @tin2tin’s add outline, it just shows what I assume is the panel’s title, eg

I guess to get options to show up on there they would have to be added as *-Properties to the operator itself?

It’s also interesting (and, er, not obvious!) that there’s a difference in which part of bpy.types
a property is saved makes a difference to how it gets stored. Are WM-properties persisted in any way?

I ran into something similar wrt persistence of properties/preferences, and asked some questions in another thread:

which I still have some questions outstanding from. I must confess I am kinda confused rereading that thread- and I wrote it!

Blender is great from both a user and a developer perspective as TMTOWTDI; but it does make things potentially confusing. Lack of clarity or explanation from the official documentation and tutorials kinda ends up leading to cargo cult / ā€œchange something until this [appears to] workā€ programming-- at least I must confess that has been my experience!

I have used the redo panel approximately… never?

It’s true that it’s used in more areas of Blender than others. It’s huge for modeling, at least (which makes sense, Blender was originally inspired by Maya). But for VSE, probably not as much.

I guess to get options to show up on there they would have to be added as *-Properties to the operator itself?

That’s correct - when you add a property to the operator, it’ll add them to the redo panel automatically. You can also override the default panel by overriding the draw() function in the operator class.

There’s also a way you can invoke a popup to set parameters before running:

import bpy
 
class MessageBox(bpy.types.Operator):
    bl_idname = "message.messagebox"
    bl_label = ""
 
    message = bpy.props.StringProperty(
        name = "message",
        description = "message",
        default = ''
    )
 
    def execute(self, context):
        self.report({'INFO'}, self.message)
        print(self.message)
        return {'FINISHED'}
 
    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self, width = 400)
 
def register():
    bpy.utils.register_class(MessageBox)
 
def unregister():
    bpy.utils.unregister_class(MessageBox)
 
if __name__ == "__main__":
    register()

It can be weird if users expect to have it run immediately, but if you want to force parameter tweaking, that’s an option.

Are WM-properties persisted in any way?

Beyond the current Blender session, no (at least not that I can tell).

Lack of clarity or explanation from the official documentation and tutorials kinda ends up leading to cargo cult / ā€œchange something until this [appears to] workā€ programming.

I think it’s a mix of Blender’s previously bad UX that developers have been working against for so long, and also that most online examples (either documentation or stack exchange) tend to be very simplified - as they should be. But while the examples make sense to have the properties where they are, some use cases not documented aren’t as straightforward.

Ah! That’s why AFWS’s example in your thread inherits from a class that only does drawing. Neat!

I’ll need to look into that. I suspect for the operator I’m writing storing defaults in Preferences might be the way to go.

I think you’re right in what you say. Anything that’s been around for a long time will accrue cruft and have out-of-date references. I was reading a thread on devtalk on improving the development process and there were some useful ideas there, particularly the Code Qualty [Fri]Days that gives me hope for things improving. In the meantime, while there are still are the odd TODO or two in the docs, there still here, stackexchange, devtalk and chat for filling in gaps in API knowledge.

All of what I’ve said notwithstanding, Blender is a fantastic set of tools to have available as a FOSS project. :slight_smile:

1 Like

I suspect for the operator I’m writing storing defaults in Preferences might be the way to go.

Ahh, I forgot about preferences in my list. Updated my guide.

And I agree. Overall they are getting better, despite some weird moments here and there. The small kinks will smooth themselves out.

In terms of architecture, adding properties to an existing data structure is like trying extend it with more capabilities. Say for example you would add extra properties to bpy.types.Mesh or bpy.types.Object for some purpose.

Now adding properties to a scene is more likely to ā€œenhanceā€ the scene datatype and decorate it with more capabilities.

In general terms to use bpy.types.Scene as a property placeholder, in terms of 1000% perfect design is not a good idea. However for pragmatic purposes and ease of use, everybody uses it, and so far there were never any great problems.

Some possible problems that might occur is if the operator is modal and runs on the background, and you work on two scenes at the same time. Or for any purpose there is a new scene created, or an old one deleted. This can cause some unexpected behavior, depending on what situation you are in.

But as the saying goes, you can cross the bridge once you get there. These are very rare use cases to happen.

Thanks for chiming in on that, particularly with the note on modality and multiple scenes. I don’t think I should run into either of those, but it could be useful for someone reading this topic to get a better idea of where to store operator-related data.

That’s a good way of looking at it. I guess in my case my operators work on text strips, so there’s a case to be amde that I am ā€˜extending’ bpy.types.TextSequence. That being said, since the options / properties I am implementing are more relevant to the user than the object (TextSequence), there is a good argument for storing the properties in preferences instead, I think.

1 Like

Yeah if you want to do something like textSequence.my_update_count += 1 in order to know how many times an item has been updated. Which is different than scene.operator_updated_count += 1. Depending on if you want to track each object individually or the entire process, and in that way you can use the most useful. :slight_smile: