Operators¶
Operators are the main abstraction in Blender to execute functions with additional functionality and input settings. If a shortcut or a push-button is pressed, usually that calls an operator. Gizmos, tools, scripts and such often call operators too. The operator system provides features like saving the current data state for undo ("undo push") after the operator executed successfully. It also allows users to redo or repeat the operation easily with different input settings. For example, the Adjust Last Operation popup is automatically generated from the available input settings of the operator:
Operators connect the UI with the application data. As such, they act as controllers in the Model, View, Controller design of Blender:
- Model: Application data (mostly Blender file data, i.e. DNA) and code to manage data consistently (BKE, RNA, etc.)
- View: The user interface
- Controller: Handlers requesting updates in the model in response to user interface events.
Some more information can be found in the documentation archives.
Operators and Context¶
Operators are designed to act on context. Context is an important part of the Blender design; it's defined by the user interface. In essence the idea is that operators will act on what the user is focusing on in the user interface.
For example, the user may have two windows open showing different scenes. Depending on which window an operator is executed in, the context will hold data of either one, or the other scene (like the selected objects). By getting the scene data through context, the operator will act on whatever window is in focus, hopefully resulting in just what the user expected to happen.
A more delicate example is local view: Context will contain only objects visible in the 3D View the mouse is hovering, so that operators will only act on data that is visible in the user's focus.
Put differently: The UI "broadcasts" the data it wants operators to act on via context.
More on the technical design of the context is explained on a dedicated page.
Return Values¶
When the exec()
, invoke()
or modal()
callbacks of an
operator return control back to the operator system, they have to tell
it which state the operator should be left in, as well as how to further
handle the event passed to the operator. This is done via return values.
The return values have the following meanings and effects:
OPERATOR_RUNNING_MODAL
- The operator should be kept alive and running. The
exec()
orinvoke()
callbacks can return this after a call toWM_event_add_modal_handler()
to start sending further events to themodal()
callback. Or themodal()
callback returns it to signal that it wants to keep running (i.e. keep receiving further events). OPERATOR_CANCELLED
-
The operator failed to execute the action for some reason (e.g. invalid context that couldn't be checked by the
poll()
callback for performance reasons). No undo push should be performed. Reports will be displayed, and it's encouraged to provide more information to the user that way: OPERATOR_FINISHED
- The operator is done and performed the wanted action. As applicable to the operator type, perform an undo push, display/update the Adjust Last Operation popup, display reports from the operator, ... Should always be returned when the operator modified some data that is covered by the undo/redo system.
OPERATOR_PASS_THROUGH
- Do not "swallow" the event that triggered the operator (or
modal()
callback call), let the event system pass the event on to further handlers (shortcuts, operators, gizmos, etc.). OPERATOR_HANDLED
:- Used for internal purposes only, don't use outside of the event system. Denotes that a operator executed another operator which already handled functionality like sending an undo push. So the operator returning this can just be freed without further action. This may be unused currently.
OPERATOR_INTERFACE
:- The operator can be destructed without handling further functionality (undo, reports, Adjust Last Operation popup, etc.), because a UI was spawned that has taken over control, and should receive further events. Should be executed whenever the operator spawned some kind of popup.
The returned value may be OR'ed with OPERATOR_PASS_THROUGH
. This
makes the operator "transparent" in that it doesn't swallow the current
event but allows it to be passed on to further handlers (shortcuts,
operators, gizmos, etc.). For example, a modal operator may run that
listens to some specific keyboard events, while keeping all other user
interactions, like mouse hover highlights, shortcuts, gizmo usages, etc.
working. It does that by returning OPERATOR_PASS_THROUGH | OPERATOR_RUNNING_MODAL
for everything but the keyboard events it wants
to operate on itself. Note that it's generally better to avoid such
"transparent" modal operators. It's better to have operators dedicated
to the events in question. While a modal operator runs, no auto-saves or
undo pushes are performed by Blender.
Modal Operators¶
Operator Types¶
Blender needs to keep a registry of all known operators and everything
that is needed to display and execute them (name, callbacks, options,
properties, ...). This information for each operator is combined into a
so called operator type (wmOperatorType
). It is essentially the
blueprint for constructing the actual operator. All these operator types
are stored in a global registry. At the time of writing, Blender has
around 2000 such operator types registered by default. Each operator
type also contains an identifier (wmOperatorType.idname
), which is
used as a key in the registry. This way, the identifier can be used to
reference an operator-type, for example:
layout.operator("object.delete")
. So the UI only references the
operator-type and can already use that to query information about the
operator, like the name, tooltip or if the operator can be executed in
the current context (determined by calling the poll()
callback).
Only when the operator is executed, for example by pressing a button in
the UI, the actual operator is constructed and executed/invoked. It is
kept alive until a return
value indicates
that it ended execution (successfully or not). In theory, the same
operator may run multiple times in parallel even. In Python, the
operator type is defined/represented by the operator's class.
Note that there are two possible formats for the identifier of the
operator type, "OBJECT_OT_delete"
equivalent to "object.delete"
.
The latter is used in Python mostly.
Operator Macros¶
Callbacks¶
Operator-types register a set of callbacks. Refer to the source code
documentation of them inside of wmOperatorTypes
(WM_types.h
).
Input Settings (Operator Properties)¶
Options¶
Operators support a number of features that can be enabled with the following flags on the operator type:
OPTYPE_REGISTER
- TODO
OPTYPE_UNDO
OPTYPE_BLOCKING
OPTYPE_MACRO
OPTYPE_GRAB_CURSOR_XY
OPTYPE_GRAB_CURSOR_X
OPTYPE_GRAB_CURSOR_Y
OPTYPE_PRESET
OPTYPE_INTERNAL
OPTYPE_LOCK_BYPASS
OPTYPE_UNDO_GROUPED
OPTYPE_DEPENDS_ON_CURSOR
Good Practice¶
Differentiate between functions and operators¶
There are two typical, related code quality issues with operators:
- Abusing operator callbacks as functions: Often, operator
exec()
andinvoke()
callbacks contain a bunch of logic at mixed levels of abstraction. For example, iterators, complex condition checking, bit-flag operations, calls to other functions, ... This indicates that the operators deal with business logic themselves, rather than letting the model handle it -- a violation of the Model, View, Controller design that has consequences. Operators should just use high-level API functions of well defined modules. These should be unit tested and may be used by other parts of Blender, like the Python API. The operator then just puts a few pieces together to perform an action through the UI. - Passing context from operators to functions:
bContext
has the tendency to spread throughout the code like cancer. It often seems convenient to just pass it to functions. But sooner or later, this function needs to be called from a different place, where context may not be available. So a bunch of functions have to be updated to also take context as parameter, or other hacks are used to make it available (search forevil_C
in the code). See #74429 for further code quality issues with context. While operators are designed to act on context, making functions act on context too causes problems.
Instead, functions should take just the data needed as function arguments, if necessary/useful wrapped in helper structs. C++ classes can also be a good way to encapsulate data and functionality nicely.
Generally an operator's exec()
or invoke()
callback should have
a structure like this:
static int some_operator_exec_or_invoke(/* ... */)
{
/* Retrieve and evaluate data from context and properties. Return if not valid. */
/* Call a few high-level functions on the input data. */
/* Cleanup. */
/* UI updates: Reports, notifiers, update tagging, etc. */
}
Retrieving and evaluating data may also be done "just before use", to reduce its scope.
Use "Disabled Hints"¶
"Disabled Hints" are displayed in tooltips to indicate why a button is
disabled in the UI. This can be very useful information to users, so
it's encouraged highly to make good use of them. Operators can provide
this before returning false
in the poll()
callback: