Source/Interface/Operators

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:

Automatically generated Adjust Last Operation popup. The operator system will reapply the operation as the user edits the settings.


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

Context-operator.png

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.


The 3D View in Local View (left) provides a different context than the regular 3D view (right). This way operators executed in Local View only operate on the visible objects of this view.

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() or invoke() callbacks can return this after a call to WM_event_add_modal_handler() to start sending further events to the modal() callback. Or the modal() 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:
 BKE_report(op->reports, RPT_ERROR, "Useful explanation of what's wrong");
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 this is just an explanation of the pass-through behavior, operators shouldn't be used this way.

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() and invoke() 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 for evil_C in the code). See T74429 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:

bool some_operator_poll(bContext *C)
{
  if (some_condition() == false) {
    CTX_wm_operator_poll_msg_set(C, TIP_("Some useful explanation"));
    return false;
  }
  return true;
}