In this final chapter, we add a new editor to the Blender codebase. Doing so will help us to better understand editor implementation. It will also provide a platform for adding a simple button layout using Blender’s custom user interface (UI) API. Along the way, we will encounter the gpu module, an abstraction layer for Blender’s underlying hardware-based rendering API (i.e., OpenGL).1 We will also register a few non-modal operators for our editor. All of this will be done directly in the codebase’s C code.
Drawing Editors from WM_main()
Before we discuss using the structs such as SpaceType, ARegion, etc. for creating an editor from scratch, let us consider again the main event loop WM_main(), from source/blender/windowmanager/intern/wm.c (shown in Listing 1-7). You will recall we covered the SpaceType, ARegion, and ARegionType structs in the previous chapter. These structs will be referenced again in this chapter.
The last function call in this loop is wm_draw_update(). It is in this call, after Blender’s data has been updated in the given loop, that we draw the ARegion “data-blocks ” for each of our editors.
Data-blocks are the member data portion (or “block of data”) of an object in Blender. As Blender is written in C, and not afforded the class construction of C++, it breaks the notion of a class into two parts: the type and the data-block. Thus, for a region, we have the ARegionType, which must be initialized with the required functions for the type using function pointer assignment, and ARegion, the “data-block” portion which has a pointer to the type-part along with instance-specific data, such as the viewable area of a region stored in its View2D type field, called v2d. As we first saw in Chapter 5, when we talked about how derived types are defined in the CPython API, this pattern is similar and generally how C programs perform “subclassing” and object creation using structs.
Elided wm_draw_update() from source/blender/windowmanager/intern/wm_draw.c. Here, we iterate over each wmWindow struct, from the singleton wmWindowManager object. For each wmWindow object, a call is made to wm_draw_window(). Lines of interest are in boldface
In Listing 8-2, we show wm_draw_window(). Only regions tagged for redraw are copied from an offscreen buffer (i.e., “blitted” to video memory) to an eventual front-buffer, to be shown on the computer monitor. The original off-screen drawing is done in wm_draw_window_offscreen(). We ignore the special cases for stereo rendering (right and left separate views) to the visible buffer.
Snippet from wm_draw_window() from source/blender/windowmanager/intern/wm_draw.c. Calls to wm_draw_window_offscreen() and wm_draw_window_onscreen() are shown in boldface
wm_draw_window_offscreen() from source/blender/windowmanager/intern/wm_draw.c
Finally, we look at ED_region_do_draw() in Listing 8-4. We have now reached the call to the registered function in the “data-block” ARegion.
ED_region_do_draw() snippet from source/blender/editors/screen/area.c
Adding a Custom Editor
We learned about the editors module in Chapter 7. Now, we add one of our own and outline the necessary files that need to be affected and the functions that a SpaceType needs to have defined.
The header will contain three buttons, with text descriptions, all implemented in C.
Each button will have its own operator, whose exec function will run when the button is clicked.
The exec functions will set a background color of red, green, or blue. And, this color will be stored in the SpaceTutorial object associated with a given instance of the editor.
- The window region will have three rectangles drawn into it using the gpu module.
The dimensions of the rectangles are determined by the View2D struct from the ARegion object of the window region.
The editor will be included in the makesrna module list, so that it is available to the Blender Python API. However, we will not provide the code necessary in any particular region for executing the register Python class’ code to either register operators or draw UI elements.
How to define UI buttons4 in an editor
How to register operators and associate them with UI elements
How to define separate regions within a SpaceType (i.e., editor)
How to draw into a window region, using the gpu module’s API
Editor Registration
ED_spacetype_tutorial() from the new file: source/blender/editors/space_tutorial/space_tutorial.c. A full list of updated and additional files for the tutorial editor are given in a later section. Note the enum value SPACE_TUTORIAL. This is defined in DNA_space_types.h and is provided with the source code for this chapter
We must allocate memory through the intern library guardedalloc, the Blender memory management API discussed in Chapter 1. We first do this for the SpaceType. We also assign two function pointers for the SpaceType, init and operatortypes. These functions will be discussed soon.
In ED_spacetype_tutorial(), two regions are defined for the editor and then added to the editor’s SpaceType. The blendlib module’s API function BLI_addhead() is used to append the region types to the SpaceType’s regiontypes ListBase field. In essence, adding each to the front of the linked list of region types.
Each region’s type is specified by filling out its corresponding ARegionType object, each allocated using MEM_callocN(). There are many fields in the ARegionType struct. As a minimum, we set the init and draw function pointers.
The SpaceType’s new Function
The new function pointer type from the SpaceType struct in source/blender/blenkernel/BKE_screen.h. A comment is included, as it describes the new function’s purpose in editor instantiation. The new function type’s definition is in boldface
The SpaceLink “base” struct from source/blender/makesdna/DNA_space_types.h
In our tutorial, we forgo the init function to keep things simple. However, we must define a “derived” type from SpaceLink.
Our tutorial editor’s SpaceLink is called SpaceTutorial (shown in Listing 8-8). As we want to store the background color of the editor’s main region, we provide the color field. The color field is of type int and is assigned a proper enum value from source/blender/editors/space_tutorial/space_tutorial.c.
Our SpaceTutorial struct , added to source/blender/makesdna/DNA_space_types.h. The color field is in boldface
tutorial_new() is the function we assign to SpaceType’s new field. It is shown in Listing 8-9. As already stated, it is the function that is called when a new instance of our editor is created by the user. The data portions of not only the SpaceType (i.e., the SpaceTutorial SpaceLink object) but the editor’s regions—its ARegion objects—are also allocated and initialized.
The tutorial_new() function from our new file source/blender/editors/space_tutorial/space_tutorial.c. The assignment of the spacetype field is in boldface. Notice that neither area or scene, while both parts of the required function type for SpaceType’s new field, are used in this implementation. There is an UNUSED macro that we introduce later, to mark such cases
Editor Regions
As we just saw, our editor contains two regions, a header and window (main region), and that there are two ARegionType structs for those regions—both defined in tutorial_new(). Here, we look at the function definitions in the ARegionTypes for draw and init, which were assigned earlier in ED_spacetype_tutorial().
The ARegionTypes’ init Functions
The regions’ init functions work similar to the editor top-level init function . Now, however, we must handle any specific lower-level initialization for the given region.
The tutorial_header_region_init() function . As we are not using the wmWindowManager parameter of the function type’s specification, we mark this parameter with the UNUSED macro. This is optional, but considered good practice
The tutorial_main_region_init() function
The Main Region’s draw Function
Listing 8-12 shows the tutorial_main_region_draw() function definition. We will talk more about the header region draw function later, when we address button creation. For now, consider tutorial_main_region_draw().
The tutorial_main_region_draw() function. In the section of the function, where we draw concentric rectangles, the offsets between rectangles are given specific values as opposed to scaling with the height and width of the main region’s extents5
Our implementation of CTX_wm_space_tutorial() is added to source/blender/blenkernel/intern/context.c and can be found in the source code provided for this chapter. Its purpose is to return the corresponding SpaceLink for the editor instance from the context. In addition to the editor’s data-block, we also make sure to obtain a pointer to the ARegion’s View2D, from the editor’s window-type region. This will allow us to use its mask field, that is, the drawing surface of the main region.
Subsequent to obtaining the needed pointers, we set the background color using the gpu module’s GPU_clear_color (). While this could have been done with a direct call to OpenGL, it is a better practice to use the gpu module’s wrapper for this. We also make a call to GPU_clear(GPU_COLOR_BIT) to actually clear the buffer.
The remainder of tutorial_main_region_draw() draws the concentric rectangles. Interesting, the gpu module still supports an immediate mode interface to OpenGL. Thus, the code for drawing the concentric rectangles resembles an obsolescent method relative to current versions of OpenGL. The earlier Blender codebase, however, often used immediate mode for drawing. The gpu module’s immediate mode interface (functions prefaced with imm*) has kept much of the codebase’s drawing code similar to its original form.
Operator Registration and Definition
Our operator definitions and registration are straightforward and follow the material from Chapter 7, where we talked about WM_operatortype_append() and wmOperatorType.
The tutorial_operatortypes() function
Calls to WM_operatortype_append() are passed our operator registration functions. We assign three separate, but very similar, operators for each button in our editor’s header. Button rendering will be discussed in the next section.
The SPACE_TUTORIAL_OT_red_region() function
As our tutorial editor is meant to be as simple as possible—such that the context or mode does not affect the operator’s function—the poll callback test_context_for_button_operator returns non-zero (i.e., “true”) for all calls.
The set_background_red_in_main_region() function
Each of the operator exec functions does the same, for their respective background color.
Header Buttons
The UI_* API is rather extensive. Blender “RNA” wraps it, for the Blender Python API, as much of the UI is scripted in Python. However, for illustration purposes, we use the UI_* API directly in our editor.
The partial implementation of draw_our_buttons_in_C(). The lines of code related to assigning a button’s operator type are in boldface. Note that this is done every time the header region is redrawn
Every time tutorial_header_region_draw() is executed, so is draw_our_buttons_in_C(). In draw_our_buttons_in_C(), we call uiDefBut(). The uiDefBut() function is passed a uiBlock struct, which is the grouping for our header buttons. It is used for coordination purposes (i.e., a “block” of buttons may be radial, in that only one of the group can be selected at any one time).
Our buttons do not retain a state after being clicked, so we use the toggle type identified by the UI_BTYPE_BUT_TOGGLE enum value. The button shown in Listing 8-16 is for the button that when clicked, sets the background color of our editor’s window (main) region to red. We therefore use a text label on the button, as can be seen in the actual parameters. The remaining parameters are used for setting the placement and size of a button in the region’s coordinate system, which starts in the lower left of the region. We will talk more about the UI_* API itself in an upcoming section of this chapter.
Importantly, we use the returned pointer to a uiBut object, to set the button’s operator type. In the very first line of draw_our_GUI_in_C(), we called WM_operatortype_find(). The idname field we set in SPACE_TUTORIAL_OT_red_region() is passed. WM_operatortype_find() returns the wmOperatorType from the SPACE_TUTORIAL_OT_red_region() registration. We have the operator type from the outset of draw_our_GUI_in_C(), allowing us to set the optype of the returned uiBut object. This connects the clicking event of the button to the operator. When the button is clicked, the poll callback is first called, then the exec callback, of the operator.
File Changes for the Tutorial Editor
Summary of source files. Boldface files are new
Source File |
---|
source/blender/blenkernel/intern/context.c |
source/blender/editors/space_api/spacetypes.c |
source/blender/editors/space_tutorial/space_tutorial.c |
source/blender/makesrna/intern/rna_space.c |
Summary of header files. Boldface files are new
Header File |
---|
source/blender/blenkernel/BKE_context.h |
source/blender/editors/include/ED_space_api.h |
source/blender/makesdna/DNA_space_types.h |
source/blender/makesrna/RNA_access.h |
Summary of build files. Boldface files are new
Build Script |
---|
source/blender/editors/CMakeLists.txt |
source/blender/editors/space_tutorial/CMakeLists.txt |
source/blender/makesrna/intern/CMakeLists.txt |
Completely new files are space_tutorial.c and space_tutorial.h, as seen in Tables 8-1 and 8-2, respectively. These source files contain code that was not covered in the previous section Editor Registration. You may choose to split up the code in space_tutorial.c into separate source files, as many of the editors do this. As the tutorial editor is rather short, this approach was not taken here. Often, the operators occupy a source file of their own, and each of the regions also has its own source and header files.
One thing to be aware of is that without updating rna_space.c or RNA_access.h, your editor will not be available to the Blender Python API. If you fail to update these two files, the editor type used in scripted UI will fail to find the new editor.
The User Interface API
There is official documentation on the UI_* API. It resides in doc/guides/interface_API.txt.
While this document is helpful, it is out-of-date.6 That said, it still provides insight on the origin and workings of the Blender UI system. Thus, interface_API.txt should be considered first, before using the API.
Headers
UI_interface.h
UI_view2d.h
UI_resources.h
UI_icons.h
UI_interface_icons.h
UI_interface.h, UI_view2d.h, and UI_resources.h are used in the tutorial editor from this chapter.
UI_interface.h
Snippet of enums and prototypes from UI_interface.h
UI_view2d.h
Example prototypes from UI_View2D.h
UI_resources.h
Snippet from UI_resources.h
While we do not cover the use of icons in the UI_* API, UI_icons.h and UI_interface_icons.h deal with this aspect of the Blender interface.
The UI_* API Implementation Files
Summary
If you are familiar with the Blender Python API, you will know that UI elements can be added via Python scripts. You will also be aware that operators can be registered via Python, and the python module will use the callbacks defined in Python scripts for the operator’s functionality.
In this chapter, we did everything in C—the language of the “core” codebase. It will be instructive to go back to the tutorial editor, after completing the material in this chapter. Try to substitute the buttons and operator implementation, by instead using the Blender Python API. It should also be useful to trace the drawing of the editor, for cases when UI is implemented using the Blender Python API. The fundamentals of Blender’s embedded Python interpreter were covered in Chapter 5 and are helpful in performing this exercise, along with knowledge of the “RNA” Data API8 discussed in Chapter 6.
While the editor in this chapter was simple in function, it illustrated much of the logistics of editor implementation. Using the gpu module’s drawing facilities, along with access to the Blender “DNA,” a more realistic editor can be written. Use an editor like the “outliner” (source/blender/editors/space_outliner/) to see how this is done in practice.
As mentioned at the beginning of this book, there are still many topics in the Blender codebase left to explore. This book has provided the impetus and foundation for further Blender development. If you want to explore mesh modeling functionality, the code in source/blender/editors/mesh and the bmesh module are the places to begin. Or, perhaps you are interested in Blender’s image processing capabilities. Thus, you should begin to look at the compositor module. These are just two of the many options to pursue.