Dev:2.8/Source/Custom Manipulators
Custom Manipulators
Introduction
Custom manipulators aim to bring the interaction with content attributes right to the content itself, providing natural ways to interact with your content. They allow users to focus on the creative act of changing their content, rather than being disturbed by the UI. To facilitate the creation of new manipulators, we tried to design a system that makes it easy to create and manage them. We also paid special attention to making this system maintainable, simple and extendable.
Initially, we used the term widget but decided to switch to manipulator to avoid confusion with traditional widgets, such as buttons, scrollbars, etc. If you find some leftovers from this transition, feel free to correct them :)
Design Overview
Manipulators are bound to an editor and not saved in .blend files. Undoing interactions with them still work though. The architecture to store and manage manipulators is split into three levels:
- Manipulator
- On the lowermost level, there is the manipulator itself. There are quite some details to say about manipulators, but we'll cover them later.
- Manipulator-Group
- To manage manipulators, they are grouped together into a manipulator-group. Programming the behaviour of multiple manipulators that somehow relate to each other (e.g. all manipulators for lamp objects) becomes simpler this way.
- Manipulator-Map
- A manipulator-map is mainly used to bind a bunch of manipulator-groups to a region of an editor. It's the uppermost level of the manipulator code.
These three levels are really essential to understand the manipulators' architecture. For a quick overview, knowing this should be enough, the next section will go into a bit more detail.
Users will want to use a set of manipulators to control some properties during a specific kind of workflow in a specific kind of editor.
Example:
Sarah wants to use two manipulators in pose mode in the 3D viewport:
One to tweak the maximum angle of rotation for selected hinge type bones in her rig.
Another to tweak the rotation of the bone.
To make that work, she has to make a new widgetgroup.
This widgetgroup will contain the two manipulators that are mentioned above, and is responsible to connect them to the property or operators that they will control. It is also responsible to control when the manipulators will be shown (when a hinge type bone is selected in pose mode, in this example) as well as update their position if necessary. During interaction, the manipulators can update their position themselves, however before users start to interact with them, they must be placed in 3D or 2D space somehow, and widgetmaps also take care of that.
After defining the widgetgroup, Sarah will want to register it with some of the widgetmaps within blender. Widgetmaps are collections of widgetgroups for a specific editor and area. In our example, Sam wants to register her widgetgroup to a widgetmap that contains widgetgroups for the 3D viewport.
However, registration is done using the type of the widgetmap. Generally, blender will take that type and use it to instantiate actual widgetmaps corresponding to that type for all editors of the type that Sarah wants. This way, if Sarah splits a 3D editor in two, blender can recreate all registered widgetmaps from their registered types for the new area without her worrying about recreating them herself.
Design Details
Manipulator
The manipulator has quite a few jobs, it has to:
- Define its own appearance (drawing).
- Define its own behaviour (handling).
- Define own intersection checks (ideally, this could be handled by the manipulator back-end - it partially is for 3D manipulators which use OpenGL selection).
- Allow binding a RNA property to itself and react on changes to it (right now this is not mandatory though).
- Ideally allow influencing its own appearance and behaviour through an API.
- Optionally provide a number of variations of itself.
The idea we had was to save all various manipulator types (arrows, dials, planes, etc) in a manipulator library. Manipulator-groups could then reuse the manipulator types from this library. This however requires them to be generic enough for multiple use-cases, which might be an issue at times, so we yet have to find out if we need to improve the system here (and how!).
The basic manipulator struct, that is independent from the actual type, is called wmManipulator
. Its functions are placed in source/blender/windowmanager/manipulators/intern/wm_manipulator.c
.
Assigning an Operator to a Manipulator
Assigning an operator to a manipulator basically makes the operator work in parallel to the manipulator. This is probably only useful for modal operators. Before an event is handled by the manipulator, it is sent to the operator. The manipulator can then act on data that was changed by the operator before.
Note that this is probably a bit of a hack, introduced to allow hooking up manipulators with the transform system. We didn't want to rewrite or duplicate big parts of the transform system, so that manipulators would transform objects exactly like the transform system would (including things like snapping, precision tweaking, constraining, etc). Ideally, manipulators wouldn't depend on data changes by operators, they'd only manipulate a property by themselves.
Assigning an operator to a manipulator is simply done using WM_manipulator_set_operator
. It returns a pointer for the operator that can be used to set values for an operator property.
Manipulator Library
C-style inheritance is used to create the individual manipulator types (arrows, dials, planes, etc) whereby wmManipulator
acts as the base struct.
typedef struct FooManipulator {
wmManipulator manipulator; /* base manipulator we inherit from */
...
float scale[2];
int style;
} FooManipulator;
Creating a manipulator is done in a simple creation function that can look like this:
wmManipulator *MANIPULATOR_foo_new(wmManipulatorGroup *mgroup, const char *name, const int style)
{
FooManipulator *foo = MEM_callocN(sizeof(*foo), name);
/* Set callbacks for this manipulator type */
foo->manipulator.draw = manipulator_foo_draw;
foo->manipulator.invoke = manipulator_foo_invoke;
...
/* Set default values */
foo->manipulator.flag |= WM_MANIPULATOR_DRAW_ACTIVE;
foo->scale[0] = foo->scale[1] = 1.0f;
/* Set variation info */
foo->style = style;
/* Register the manipulator base as part of the manipulator-group */
WM_manipulator_register(mgroup, &foo->manipulator, name);
return (wmManipulator *)foo;
}
The callbacks that are assigned in the creation function should be used to fulfil most of the jobs mentioned above. Further, there should be an API to set and get all needed data that defines look and behaviour of the manipulator type. (Note: Adding a wmManipulatorType
would probably simplify things a bit. Something to look at later).
It is possible to draw manipulators using geometry exported from Blender objects. For this, you can use the widget_export.py script (currently outdated and not bundled with vanilla Blender). This will generate geometry info that can be stored using the ManipulatorGeometryInfo
struct and drawn simply by calling wm_manipulator_geometryinfo_draw
.
Everything related to the manipulator library is located in source/blender/windowmanager/manipulators/intern/manipulator_library/
. There are also some utility functions in manipulator_library_utils.c
that can be used in the manipulator type callbacks. We hope to make this utility API really strong, so that it'll be easy to add new manipulators or variations for existing ones.
Manipulator-Group
As said earlier, it's often useful to group some manipulators together. The current design requires manipulators to be stored in a manipulator-group even. They work a bit similar to operators in Blender: The actual instances of a manipulator-group are created from a manipulator-group-type (similar to how operators are created from an operator-type) and those assign callbacks that are executed to initialize and update the manipulator-group after it's been created. This allows us to create many instances of a manipulator-group, but all based on a single manipulator-group-type. Manipulator-group-types are registered as early as possible (usually on startup) and only destroyed when exiting Blender.
Registering a manipulator-group-type looks like this:
void VIEW3D_MGT_lamp(wmManipulatorGroupType *mgt)
{
/* manipulator-group name - displayed in UI (keymap editor) */
mgt->name = "Lamp Manipulators";
/* poll if manipulator-group should be visible */
mgt->poll = manipulatorgroup_lamp_poll;
/* initially create manipulators and set permanent data - stuff you only need to do once */
mgt->init = manipulatorgroup_lamp_init;
/* refresh data, only called if refresh flag is set (WM_manipulatormap_tag_refresh) */
mgt->refresh = manipulatorgroup_lamp_refresh;
}
And in the SpaceType.manipulators
callback of the dedicated space-type, add the manipulator-group to a manipulator-map:
static void view3d_manipulators(void)
{
... /* get manipulator-map-type, append other manipulator-groups, etc */
WM_manipulatorgrouptype_append(mmaptype, VIEW3D_MGT_lamp);
}
Notice how this is once again really similar to how operator-types are registered.
Manipulator-Groups and Operators
It is possible to assign a manipulator-group to an operator (don't confuse with operators that can be assigned to manipulators). This will cause the manipulator-group to be only available while the operator runs. It's probably only useful with modal operators. Example use-case would be the node editor backdrop: Pressing some hotkey would enable a modal operator that displays a manipulator to transform the background. Various actions like confirming or aborting would be possible then. To assign a manipulator-group to an operator, you have to do something like this:
void GRAPH_OT_widget_backdrop_transform(struct wmOperatorType *ot)
{
... /* usual operator-type definition stuff, get manipulator-map-type */
/* Assign manipulator-group-type to the operator type. The manipulator-group is created each time the operator is created then. */
ot->mgrouptype = WM_manipulatorgrouptype_append(mmaptype, GRAPH_MGT_backdrop_transform);
}
Now, you may want to update some data while the user uses the manipulator. To allow that, a event of type EVT_MANIPULATOR_UPDATE
is sent after each event the manipulator receives. You can simply listen to it in the modal operator.
Manipulator-Group Keymaps
Interactions with manipulator-groups are handled using operators. This makes it possible to allow user configurable keymaps for manipulator-groups. User's can then change how manipulators in a group react to input which may come in handy (e.g. so that some manipulators are triggered on a key-press, others on a drag/tweak event). It's also possible to write custom widget-group operators with own keymaps, even in Python, adding many new possibilities.
Manipulator-Map
While in most cases, there's only a need for one manipulator-map per region, it's possible to have multiple ones. This was added to support displaying manipulators in 2D and 3D space within the 3D View editor. Now, here's something that might make things a bit confusing to understand, so let's try to describe it really simple: Since it's possible to have multiple editors of the same type, we also need to support multiple manipulator-maps of the same type. To do that we added manipulator-map-types (typeception!). It contains information on the region type it's attached to, as well as a list of manipulator-group-types.
Within each manipulator-map, there can only be one highlighted, one active/dragged, but multiple selected manipulators. This can be considered the manipulator-map context.
Manipulator Updating
At an earlier point in the development, manipulators were always recreated on each redraw (just like our UI system always recreates its widgets). This made updating really simple (updating basically didn't exist) but obviously wasn't really smart when it comes to performance. Instead of recreating on each redraw, we introduced a pretty simple updating system that works on three stages:
- Initializing
- When creating a manipulator-map, all of its manipulator-groups are initialized just before they're drawn the first time.
- Refresh
- A refresh only happens if the manipulator-map has been explicitly tagged to do so by calling
WM_manipulatormap_tag_refresh
. - Draw Preparation
- A draw preparation happens each time before a manipular-group is drawn.
The updating itself, happens per widget-group through callbacks of the wmManipulatorGroupType
. The callbacks are called
init
(required)refresh
(optional)draw_prepare
(optional)
Got Lost in the Type Jungle?
The following diagram visualizes the relation between all the manipulator types and the instances created from them:
File Structure
Almost all files related to manipulators are placed in source/blender/windowmanager/manipulators/
. The files are:
WM_manipulator_api.h
- Contains forward declarations of the main manipulator API for external access. Included in
WM_api.h
. WM_manipulator_types.h
- Contains manipulator structs, enums and callback type definitions for external access. Included in
WM_types.h
. WM_manipulator_library.h
- Contains forward declarations for APIs of the manipulator types and definitions for external access. Included in
WM_api.h
. wm_manipulator_wmapi.h
- Contains forward declarations and definitions for use on window manager level only. It's basically everything needed to hook up the manipulator code with Blender's window manger. Included in
wm.h
. /intern/
- Contains only files that must not be included in external files.
/intern/wm_manipulator_intern.h
- Contains forward declarations and defines for usage in
/intern/
and sub-folders only. /intern/wm_foobar.c
- Source files for the
wmManipulator
types (wmManipulator
,wmManipulatorGroup
,wmManipulatorGroupType
, ...). /intern/manipulator_library/
- Contains everything related to the various manipulator types.
/intern/manipulator_library/manipulator_library_intern.h
- Contains forward declarations and defines for use inside
/manipulator_library/
only. Mostly acts as header file for the utility API, but can also contain other internal defines. /intern/manipulator_library/manipulator_library_geometry.h
- Contains forward declarations of global variables used to store geometry data that can be assigned to
ManipulatorGeometryInfo
. /intern/manipulator_library/geom_foobar_manipulator.c
- Sets values for global variables used to store geometry data that can be assigned to
ManipulatorGeometryInfo
. /intern/manipulator_library/foobar_manipulator.c
- Source files to define the manipulator types and their APIs.
Like in most modules of the Blender code, an uppercase prefix indicates a file or function for external access (e.g. WM_
or MANIPULATOR_
).
The only file that's not located in /manipulators/
is DNA_manipulator_types.h
, which only defines the wmManipulatorGroup
struct. It needs to be on DNA level for RNA integration, although it could be moved away from there since the RNA integration is not merged into the master branch yet.