0
$\begingroup$

Is it possible to create any type of non-property variables for operator window types? I'm trying to define a base class for all operator windows, and would like to define some common states in the base class. I believe the typical Python workflow would be to define some normal variables in the base class, but this doesn't seem possible with Blender.

Anyone tried this before?

$\endgroup$
7
  • $\begingroup$ Not sure if multiple inheritance is a good design choice. What exactly do you mean by "operator window"? Are you referring to the Adjust Last Operation panel? $\endgroup$ – Robert Gützkow Nov 9 '19 at 16:58
  • $\begingroup$ I was only aware of the windows that we can create by deriving from operator class and using context.window_manager.invoke_props_dialog(). But I have a feeling there is a lot more available than I currently understand. Would there happen to be any information about it out there? Anyway, I was wanting to build core operation into a single class, then make use of it in all of the windows. But yeah, its looking like it won't work very well in Blender. $\endgroup$ – Robert Nov 9 '19 at 17:12
  • 1
    $\begingroup$ Ok that is a popup for adjusting the setting before executing the operator. The properties that are displayed are tied to the operator not the window. Since the operator instance only exists for the time it's being executed, the instance attribute will have the same lifetime. Alternatively you can create a class attribute which is shared by all operators instances. $\endgroup$ – Robert Gützkow Nov 9 '19 at 17:33
  • $\begingroup$ I'm currently using the operator class and props-dialog window to allow the user to modify settings that relate to the active element. They select an element, push a button/key to start the operator and open the window, modify properties relating to that element, then close the window. Would there be a better way to handle this type of situation? I originally had the element properties on side panels, but there ended up being too much overhead in computing the dynamic properties. $\endgroup$ – Robert Nov 9 '19 at 19:13
  • 1
    $\begingroup$ Yes by class attribute I mean static variable. If you use an instance attribute for an operator, you can't actually store state because as soon as execution has finished that state is gone (because the instance of the operator doesn't exist anymore). See: gist.github.com/robertguetzkow/be4a4f25335be4d584605c168f2784c9 $\endgroup$ – Robert Gützkow Nov 9 '19 at 22:24
1
$\begingroup$

Since the instances of the operators only exist for the duration of their execution, instance attributes aren't a suitable solution for storing the state. Once the execution is finished the operator is destroyed and subsequent calls to execute() or invoke() will not have access to the previous state. Class attributes, also known as static variable in other programming languages, could be used since all instances of the class access the very same variable. Therefore the state remains, even when one particular object ceased to exist.

The example below demonstrates the lifetime of an operator and the difference in using a class or instance attribute. Since the operator instance gets destroyed once it's finished executing, the instance attribute always remains "1" no matter how often the operator is called.

bl_info = {
    "name": "Class vs Instance Attribute",
    "author": "Robert Guetzkow",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "",
    "description": "Demonstrates the lifetime of the operator.",
    "warning": "",
    "wiki_url": "",
    "category": "3D View"}

import bpy


class EXAMPLE_OT_class_attribute(bpy.types.Operator):
    bl_idname = "example.class_attribute"
    bl_label = "Increment and print class attribute"
    bl_description = "Increment and print instance attribute"

    x = 0

    def execute(self, context):
        EXAMPLE_OT_class_attribute.x += 1
        print(f"EXAMPLE_OT_class_attribute: {EXAMPLE_OT_class_attribute.x}")
        return {"FINISHED"}

class EXAMPLE_OT_instance_attribute(bpy.types.Operator):
    bl_idname = "example.instance_attribute"
    bl_label = "Increment and print instance attribute"
    bl_description = "Increment and print instance attribute"

    def __init__(self):
        self.x = 0

    def execute(self, context):
        self.x += 1
        print(f"self.x: {self.x}")
        return {"FINISHED"}

classes = (EXAMPLE_OT_class_attribute, EXAMPLE_OT_instance_attribute)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)


def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()

    for i in range(5):
        bpy.ops.example.class_attribute()

    for i in range(5):
        bpy.ops.example.instance_attribute()
$\endgroup$
3
  • $\begingroup$ In general this is of course not the only way how this can be solved. Usually you'd have control over the lifetime of your object and could store your state within it. You could also perform dependency injection and pass objects it depends on as arguments into the constructor in order to achieve separation of concerns. $\endgroup$ – Robert Gützkow Nov 10 '19 at 16:09
  • $\begingroup$ I think your solution is probably the best for the specific situation of creating a window within/for an operator. I believe it would be impossible for such a window to exist more than once at a time, so using class attributes is just as good as instance attributes. $\endgroup$ – Robert Nov 10 '19 at 16:52
  • 1
    $\begingroup$ Just a heads up for anyone looking to do this. Blender generates its own class types for operators, and defines them in bpy.types.***. With my operator class, which has bl_label = "wm.fbxg_ovr", it generates the class bpy.types.WM_OT_fbxg_ovr. And when Blender executes a property updater (also likely the setter and getter), it sends self as an instance of this internal class type, which means any class attributes you have defined will not exist in it. Its possible this problem spreads wider than setters/getters/updaters. As far as I can tell, draw() sends the correct class instance. $\endgroup$ – Robert Nov 15 '19 at 19:47

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.