The button click is basically connected with the operator calling in the Blender user interface. However, some times actions, that need to be performed when a button is pressed, are quite simple and do not require a separate operator for them. And it makes no sense to fill a registered operators stack with a multitude of specific operators designed to perform one highly specialized function. It would be much more convenient to associate a button press with a separate function call but the Blender API allows to associate buttons only with an operator call.
To solve the problem of creating a separate operator for each button we can use the fact that the operator can be called with the input parameters.
Let’s create a simple operator class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from bpy.types import Operator from bpy.utils import register_class, unregister_class class TEST_OT_test_op(Operator): bl_idname = 'test.test_op' bl_label = 'Test' bl_description = 'Test' bl_options = {'REGISTER', 'UNDO'} def execute(self, context): return {'FINISHED'} def register(): register_class(TEST_OT_test_op) def unregister(): unregister_class(TEST_OT_test_op) |
and define here some separate functions:
- clear_scene – for removing all the meshes from the scene
- add_cube – for adding a cube
- add_sphere – for adding a sphere
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class TEST_OT_test_op(Operator): bl_idname = 'test.test_op' bl_label = 'Test' bl_description = 'Test' bl_options = {'REGISTER', 'UNDO'} def execute(self, context): return {'FINISHED'} @staticmethod def clear_scene(context): for obj in bpy.data.objects: bpy.data.objects.remove(obj) @staticmethod def add_cube(context): bpy.ops.mesh.primitive_cube_add() @staticmethod def add_sphere(context): bpy.ops.mesh.primitive_uv_sphere_add() |
Now the main feature – let’s add an input parameter of EnumProperty type for our operator.
1 2 3 4 5 6 7 |
action: EnumProperty( items=[ ('CLEAR', 'clear scene', 'clear scene'), ('ADD_CUBE', 'add cube', 'add cube'), ('ADD_SPHERE', 'add sphere', 'add sphere') ] ) |
According to this parameter value, we will call the desired function. The first item in the list is a text identifier, which will be used as a parameter value.
Modify the operator’s “execute” method to call the desired function depending on the “action” value:
1 2 3 4 5 6 7 8 |
def execute(self, context): if self.action == 'CLEAR': self.clear_scene(context=context) elif self.action == 'ADD_CUBE': self.add_cube(context=context) elif self.action == 'ADD_SPHERE': self.add_sphere(context=context) return {'FINISHED'} |
Now, if the operator is called with the “CLEAR” parameter, its “clear_scene” function will be executed, with the “ADD_CUBE” parameter – the “add_cube” function will be executed and so on.
The complete operator code looks the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import bpy from bpy.props import EnumProperty from bpy.types import Operator from bpy.utils import register_class, unregister_class class TEST_OT_test_op(Operator): bl_idname = 'test.test_op' bl_label = 'Test' bl_description = 'Test' bl_options = {'REGISTER', 'UNDO'} action: EnumProperty( items=[ ('CLEAR', 'clear scene', 'clear scene'), ('ADD_CUBE', 'add cube', 'add cube'), ('ADD_SPHERE', 'add sphere', 'add sphere') ] ) def execute(self, context): if self.action == 'CLEAR': self.clear_scene(context=context) elif self.action == 'ADD_CUBE': self.add_cube(context=context) elif self.action == 'ADD_SPHERE': self.add_sphere(context=context) return {'FINISHED'} @staticmethod def clear_scene(context): for obj in bpy.data.objects: bpy.data.objects.remove(obj) @staticmethod def add_cube(context): bpy.ops.mesh.primitive_cube_add() @staticmethod def add_sphere(context): bpy.ops.mesh.primitive_uv_sphere_add() def register(): register_class(TEST_OT_test_op) def unregister(): unregister_class(TEST_OT_test_op) |
Let’s create a tab in the N-panel with our custom sub-panel to place the buttons:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from bpy.types import Panel from bpy.utils import register_class, unregister_class class TEST_PT_panel(Panel): bl_idname = 'TEST_PT_panel' bl_label = 'Test' bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Test' def draw(self, context): layout = self.layout layout.operator('test.test_op', text='Clear scene').action = 'CLEAR' layout.operator('test.test_op', text='Add cube').action = 'ADD_CUBE' layout.operator('test.test_op', text='Add sphere').action = 'ADD_SPHERE' def register(): register_class(TEST_PT_panel) def unregister(): unregister_class(TEST_PT_panel) |
In the “draw” method of our panel we defined three buttons: “Clear scene”, “Add cube” and “Add sphere”. And assigned each of them a call of our operator, but for each – with its own “action”: “CLEAR”, “ADD_CUBE “and” ADD_SPHERE”. As a result, when the button is pressed, the same operator is called, but with a different parameter to call different functions in accordance with this parameter value.
The final code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import bpy from bpy.props import EnumProperty from bpy.types import Operator, Panel from bpy.utils import register_class, unregister_class class TEST_PT_panel(Panel): bl_idname = 'TEST_PT_panel' bl_label = 'Test' bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Test' def draw(self, context): layout = self.layout layout.operator('test.test_op', text='Clear scene').action = 'CLEAR' layout.operator('test.test_op', text='Add cube').action = 'ADD_CUBE' layout.operator('test.test_op', text='Add sphere').action = 'ADD_SPHERE' class TEST_OT_test_op(Operator): bl_idname = 'test.test_op' bl_label = 'Test' bl_description = 'Test' bl_options = {'REGISTER', 'UNDO'} action: EnumProperty( items=[ ('CLEAR', 'clear scene', 'clear scene'), ('ADD_CUBE', 'add cube', 'add cube'), ('ADD_SPHERE', 'add sphere', 'add sphere') ] ) def execute(self, context): if self.action == 'CLEAR': self.clear_scene(context=context) elif self.action == 'ADD_CUBE': self.add_cube(context=context) elif self.action == 'ADD_SPHERE': self.add_sphere(context=context) return {'FINISHED'} @staticmethod def clear_scene(context): for obj in bpy.data.objects: bpy.data.objects.remove(obj) @staticmethod def add_cube(context): bpy.ops.mesh.primitive_cube_add() @staticmethod def add_sphere(context): bpy.ops.mesh.primitive_uv_sphere_add() def register(): register_class(TEST_OT_test_op) register_class(TEST_PT_panel) def unregister(): unregister_class(TEST_OT_test_op) unregister_class(TEST_PT_panel) if __name__ == '__main__': register() |
Now we got a mechanism for calling various functions by pressing buttons without creating and registering a set of operators for each of the buttons.
Nice code thanks !
Is there any way of adding a tooltip when hovering over the buttons ?
I know we can use
but I don’t know how to access the current id of the property
any thoughts ?
thanks
You have the properties parameter to do this.
Sorry for the trivial question but I’m new to object oriented programming. How can I call TEST_OT_test_op from inside another function? If for example I would like to execute the ‘CLEAR’ option without any button press or other UI action from another function?
All registered operators could be called through the bpy.ops
For example for the operator from this article with the “CLEAR” parameter:
bpy.ops.test.test_op(action=’CLEAR’)
Many thanks! This helps me a lot to understand the combination of Python and Blender.
Thanks for showing how to call static method. Isnt there an “easier” way as well. I somehow get the feeling using that enum to call a function is not needed. I tried a couple methods, but cant get it to run though
Enum is needed to know – what kind of function you want to call. You can use string, boolean, or int properties instead of enum, but I think that would be more difficult and you will need more conditions to check what function to call.
but you cannot create a costume function, this method is so poor that it rely on fixed functions of the blender. For example, you cannot add your own function, it has to be within (clear add cube add sphere etc). lets say i want to make a function called (clint_eastwood). ERROR
You can make any functions you like. But of course, you need an operator to connect them to the Blender interface.
the enum approach is poor, because you cannot work on your own dictionary. Direct approach always better, check this out
If you want to have a separate operator for each action – of course, you can have it. But the way having multiple functions in a single operator sometimes can simplify your code and make it easier very much.
but arn’t you limited by the functions that blender provide? like if i want to add a costume enum field value , not (add sphere or add cube)<- these are simple. Lets assume i want to add another field to the enum randomly to do complicated actions. Like adding modifier and change that modifier then execute the modifier on the selected object with one button. always generate error. then your example is only fit for 1 task, creating objects.
No. Why? For a simple example – I need to have 6 buttons on the panel with arrows, each pressing of the button moves the active object in the correspondence direction (up, bottom, right, left, forward, backward). Do you really want to have 6 operators in this case? Or you can have only one operator with 6 functions. I think that the second variant is much simpler and more convinient.