© The Author(s), under exclusive license to APress Media, LLC , part of Springer Nature 2021
B. E. HollisterCore Blender Developmenthttps://doi.org/10.1007/978-1-4842-6415-7_8

8. Editor Creation

Brad E. Hollister1  
(1)
Computer Science Department, CSUDH, Carson, CA, USA
 

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 ARegiondata-blocks ” for each of our editors.

Note

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.

Remember, Blender supports multiple windows as well, so within wm_draw_update(), we iterate over all of the windows and then their respective screens (the ScrArea data-block) down to each ARegion object in a wmWindow’s hierarchy. You should refer back to earlier chapters for the relevant structs, if necessary. In Listing 8-1, we see a snippet from wm_draw_update() .
void wm_draw_update(bContext *C)
{
  Main *bmain = CTX_data_main(C);
  wmWindowManager *wm = CTX_wm_manager(C);
  wmWindow *win;
  ...
  for(win = wm->windows.first; win; win = win->next){
  ...
      ...
      CTX_wm_window_set(C, win);
      ...
      wm_draw_window(C, win);
      ...
      wm_window_swap_buffers(win);
      ...
  }
}
Listing 8-1

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.

For the remainder of this section, we focus on the path of execution down to the editor’s draw functions. Editor draw functions themselves will be covered in the Adding a Custom Editor section of this chapter.
static void wm_draw_window(bContext *C, wmWindow *win)
{
  bScreen *screen = WM_window_get_active_screen(win);
  bool stereo = WM_stereo3d_enabled(win, false);
  ...
  wm_draw_window_offscreen(C, win, stereo);
  /* Now we draw into the window framebuffer, in full window coordinates. */
  if (!stereo) {
    /* Regular mono drawing. */
    wm_draw_window_onscreen(C, win, -1);
  }
  ...
  screen->do_draw = false;
}
Listing 8-2

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

Next, we see wm_draw_window_offscreen(), in Listing 8-3. The lines of code dedicated to stereo view, menu drawing, etc. have been omitted. Note the eventual call to ED_region_do_draw() in the nested loop interior. Lines of current interest are in boldface. The ED_screen_areas_iter macro was discussed in Chapter 7.
static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo)
{
  Main *bmain = CTX_data_main(C);
  wmWindowManager *wm = CTX_wm_manager(C);
  bScreen *screen = WM_window_get_active_screen(win);
  /* Draw screen areas into own frame buffer. */
  ED_screen_areas_iter(win, screen, sa)
  {
    CTX_wm_area_set(C, sa);
    /* Compute UI layouts for dynamically size regions. */
    for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
     ...
    /* Then do actual drawing of regions. */
    for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
      if (ar->visible && ar->do_draw) {
        CTX_wm_region_set(C, ar);
        bool use_viewport = wm_region_use_viewport(sa, ar);
      ...
          wm_draw_region_buffer_create(ar, false, use_viewport);
          wm_draw_region_bind(ar, 0);
          ED_region_do_draw(C, ar);
          wm_draw_region_unbind(ar, 0);
        }
        ar->do_draw = false;
        CTX_wm_region_set(C, NULL);
      ...
    }
    ...
}
Listing 8-3

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.

This struct has an ARegionType member, representing its type-based attributes, such as function pointers. The one of concern here is draw, which points to a registered draw function for an editor’s region. The call to a region’s draw function is shown in boldface.
void ED_region_do_draw(bContext *C, ARegion *ar)
{
  wmWindow *win = CTX_wm_window(C);
  ScrArea *sa = CTX_wm_area(C);
  ARegionType *at = ar->type;
  ...
    at->draw(C, ar);
  ...
  /* XXX test: add convention to end regions always in pixel space,
   * for drawing of borders/gestures etc */
  ED_region_pixelspace(ar);
  ED_region_draw_cb_draw(C, ar, REGION_DRAW_POST_PIXEL);
  region_draw_azones(sa, ar);
  ...
  }
Listing 8-4

ED_region_do_draw() snippet from source/blender/editors/screen/area.c

Adding a Custom Editor

In this section, we cover the basics of creating an editor within the Blender application. The Blender codebase does provide the ability to customize the existing editors, using Blender’s extended and embedded Python interpreter. However, it is not possible to create new editors. In Figure 8-1, we can see this chapter’s tutorial editor as a selection in the Editor menu.
../images/495914_1_En_8_Chapter/495914_1_En_8_Fig1_HTML.jpg
Figure 8-1

The “Tutorial Editor” selectable from the Editor Type menu

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 previous section led us to the draw function, which is one of the needed functions for the SpaceType struct, used in defining an editor. Our editor (shown in Figure 8-2) will consist of the following:
  • It will have two separate regions:
    • The first is a header-type region.2

    • The second is a window-type region.3

  • 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.

../images/495914_1_En_8_Chapter/495914_1_En_8_Fig2_HTML.jpg
Figure 8-2

The “Tutorial Editor.” Three header buttons and the prominent RGN_TYPE_WINDOW main region

Our editor, while minimal, illustrates
  • 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

Each editor is registered from the windowmanager module during WM_init(). From WM_init(), ED_spacetypes_init() is called, which itself then calls each of the editors’ separate registration functions. Our tutorial editor’s registration function is ED_spacetype_tutorial() , and is shown in Listing 8-5.
void ED_spacetype_tutorial(void)
{
  SpaceType *st = MEM_callocN(sizeof(SpaceType), "spacetype tutorial");
  ARegionType *art;
  st->spaceid = SPACE_TUTORIAL;
  strncpy(st->name, "Tutorial", BKE_ST_MAXNAME);
  st->new = tutorial_new;
  st->operatortypes = tutorial_operatortypes;
  /* regions: main window */
  art = MEM_callocN(sizeof(ARegionType), "spacetype tutorial main region");
  art->regionid = RGN_TYPE_WINDOW;
  art->init = tutorial_main_region_init;
  art->draw = tutorial_main_region_draw;
  BLI_addhead(&st->regiontypes, art);
  /* regions: header */
  art = MEM_callocN(sizeof(ARegionType), "spacetype tutorial header region");
  art->regionid = RGN_TYPE_HEADER;
  art->prefsizey = HEADERY;
  art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D | ED_KEYMAP_HEADER;
  art->init = tutorial_header_region_init;
  art->draw = tutorial_header_region_draw;
  art->prefsizey = HEADERY;
  BLI_addhead(&st->regiontypes, art);
  BKE_spacetype_register(st);
}
Listing 8-5

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’s type definition for SpaceType is shown in Listing 8-6. Of note, this function returns a pointer to a heap-allocated SpaceLink type (Listing 8-7) object, which represents the data portion of an editor.
/* Initial allocation, after this WM will call init() too. Some editors need
   * area and scene data (e.g. frame range) to set their initial scrolling. */
  struct SpaceLink *(*create)(const struct ScrArea *sa, const struct Scene *scene);
Listing 8-6

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 base structure all the other spaces
 * are derived (implicitly) from. Would be
 * good to make this explicit.
 */
typedef struct SpaceLink {
  struct SpaceLink *next, *prev;
  /** Storage of regions for inactive spaces. */
  ListBase regionbase;
  char spacetype;
  char link_flag;
  char _pad0[6];
} SpaceLink;
Listing 8-7

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.

We will see that our button operators set the color field in a SpaceTutorial object. If we had included an init function for our editor’s SpaceType, then we may have initialized the color field there. Instead, we simply have a default case in our operators’ exec function.
/* SpaceTutorial */
typedef struct SpaceTutorial {
  SpaceLink *next, *prev;
  /** Storage of regions for inactive spaces. */
  ListBase regionbase;
  char spacetype;
  int color;
  char _pad0[6];
  /* End 'SpaceLink' header. */
} SpaceTutorial;
Listing 8-8

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.

Note that the spacetype field of the SpaceLink (i.e., SpaceTutorial) is assigned the enum value SPACE_TUTORIAL, just as the editor’s SpaceType’s spaceid field was. Similarly, the ARegions objects have their regiontype fields assigned to the same value as the editor’s corresponding ARegionTypes’ regionid fields. In this case, we have RGN_TYPE_HEADER assigned to the header’s ARegion object, and RGN_TYPE_WINDOW assigned to the main region’s ARegion object.
static SpaceLink *tutorial_new(const ScrArea * area, const Scene* scene)
{
  ARegion *ar;
  SpaceTutorial *stut;
  stut = MEM_callocN(sizeof(*stut), "new tutorial");
  stut->spacetype = SPACE_TUTORIAL;
  /* header */
  ar = MEM_callocN(sizeof(ARegion), "header for tutorial");
  BLI_addtail(&stut->regionbase, ar);
  ar->regiontype = RGN_TYPE_HEADER;
  ar->alignment = RGN_ALIGN_BOTTOM;
  /* main region */
  ar = MEM_callocN(sizeof(ARegion), "main region of tutorial");
  BLI_addtail(&stut->regionbase, ar);
  ar->regiontype = RGN_TYPE_WINDOW;
  return (SpaceLink *)stut;
}
Listing 8-9

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.

As there are many standard procedures for initializing headers in editors, we simply call the ED_region_header_init() API function in tutorial_header_region_init(). This can be seen in Listing 8-10.
static void tutorial_header_region_init(wmWindowManager *UNUSED(wm), ARegion *ar)
{
  ED_region_header_init(ar);
}
Listing 8-10

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

In Listing 8-11, we show tutorial_main_region_init(). We will be drawing into the main region using its ARegion’s View2D struct field. Therefore, we call UI_view2d_region_reinit().
static void tutorial_main_region_init(wmWindowManager *UNUSED(wm), ARegion *region)
{
  UI_view2d_region_reinit(&region->v2d, V2D_COMMONVIEW_CUSTOM, region->winx, region->winy);
}
Listing 8-11

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 first thing we do in tutorial_main_region_draw() is to gain access to the SpaceTutorial (data-block for our editor), from the context, using CTX_wm_space_tutorial().
static void tutorial_main_region_draw(const bContext *C, ARegion *ar)
{
   SpaceTutorial* stut = CTX_wm_space_tutorial(C);
  View2D *v2d = &ar->v2d;
  switch(stut->color){
      case GREEN:
        GPU_clear_color(0.0, 1.0, 0.0, 1.0);
        break;
      case BLUE:
        GPU_clear_color(0.0, 0.0, 1.0, 1.0);
        break;
      case RED:
      default:
        GPU_clear_color(1.0, 0.0, 0.0, 1.0);
    }
  GPU_clear(GPU_COLOR_BIT);
  /* draw colored rectangles within mask area of region */
  uint pos = GPU_vertformat_attr_add(
    immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
  immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
  immUniformColor4ub(255, 0, 255, 255);
  immRecti(pos,
           v2d->mask.xmin + 50,
           v2d->mask.ymin + 50,
           v2d->mask.xmax - 50,
           v2d->mask.ymax - 50);
  immUniformColor4ub(0, 255, 255, 255);
  immRecti(pos,
           v2d->mask.xmin + 80,
           v2d->mask.ymin + 80,
           v2d->mask.xmax - 80,
           v2d->mask.ymax - 80);
  immUniformColor4ub(255, 255, 0, 255);
  immRecti(pos,
           v2d->mask.xmin + 110,
           v2d->mask.ymin + 110,
           v2d->mask.xmax - 110,
           v2d->mask.ymax - 110);
  immUnbindProgram();
}
Listing 8-12

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.

We first inspect the operators registered for our editor, in Listing 8-13, via tutorial_operatortypes(). It should be noted that tutorial_operatortypes() has already been assigned to our editor’s SpaceType’s operatortypes field, in ED_spacetype_tutorial().
void tutorial_operatortypes(void)
{
  WM_operatortype_append(
     SPACE_TUTORIAL_OT_red_region);
  WM_operatortype_append(
     SPACE_TUTORIAL_OT_green_region);
  WM_operatortype_append(
     SPACE_TUTORIAL_OT_blue_region);
}
Listing 8-13

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.

We show one of our operator registration functions, SPACE_TUTORIAL_OT_red_region() in Listing 8-14. Here, the operators are all non-modal. Only the required exec and poll function pointers are assigned in each of the SPACE_TUTORIAL_OT_*_region() functions.
void SPACE_TUTORIAL_OT_red_region(wmOperatorType *ot)
{
  /* identifiers */
  ot->name = "Red Region Button";
  ot->description = "Turns the main region background to red";
  ot->idname = "SPACE_TUTORIAL_OT_red_region";
  /* api callbacks */
  ot->exec = set_background_red_in_main_region;
  ot->poll = test_context_for_button_operator;
  /* flags */
  ot->flag = OPTYPE_REGISTER;
}
Listing 8-14

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 exec callback, set_background_red_in_main_region(), works by setting the proper enum value in the editor’s data-block color field—that is, a particular instance’s SpaceTutorial object. This can be seen in Listing 8-15.
int set_background_red_in_main_region(struct bContext *C, struct wmOperator *oper) {
      SpaceTutorial* stut = CTX_wm_space_tutorial(C);
      ARegion *ar = CTX_wm_region(C);
    stut->color = 1;
    ED_area_tag_redraw(CTX_wm_area(C));
    return OPERATOR_FINISHED;
}
Listing 8-15

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.

From the draw function of our header region, we call draw_our_GUI_in_C(). This function’s partial implementation is shown in Listing 8-16.
static void draw_our_buttons_in_C(uiBlock* block) {
  struct wmOperatorType* ot = WM_operatortype_find("SPACE_TUTORIAL_OT_red_region", true);
  struct uiBut* but = uiDefBut(block, // uiBlock*
     UI_BTYPE_BUT_TOGGLE,  // int type
     1,                    // int retval
     "RED",                // const char *str
     100,                  // int x1
     2,                    // int y1
     30,                   // short x2
     19,                   // short y2
     NULL,                 // void *poin to char...
     0.0,                  // float min
     0.0,                  // float max
     0.0,                  // float a1
     0.0,                  // float a2
     "");                  // const char *tip
   but->optype = ot;
   ...
Listing 8-16

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

The entirety of additional files and changes can be found in the source code included with this book. Tables 8-1, 8-2, and 8-3 summarize the files added or affected.
Table 8-1

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

Table 8-2

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

Table 8-3

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

The user interface API implementation files (UI_*) are located in source/blender/editors/interface/. The UI_* API prototypes, however, are located in source/blender/editors/include/. The dependency graph for this directory is shown in Figure 8-3.
../images/495914_1_En_8_Chapter/495914_1_En_8_Fig3_HTML.jpg
Figure 8-3

The source/blender/editors/include/ dependency graph. Since access to structs such as View2D is provided from UI_view2d.h, and other files in this directory, there are many connections with the makesdna module

There are five files that provide the UI_* API’s prototypes to be used externally7—that is, for client code outside of source/blender/editors/interface/. The UI_* API’s headers are
  • 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

The UI_interface.h file declares a number of API functions and enumerations for use in the codebase. A partial listing is shown in Listing 8-17. These functions are ones for creating (i.e., drawing at a high level) GUI elements and creating layouts. It is the longest and most extensive of the headers files.
...
enum {
  UI_ID_RENAME = 1 << 0,
  UI_ID_BROWSE = 1 << 1,
  ...
};
...
uiBut *uiDefPulldownBut(...);
uiBut *uiDefMenuBut(...); ...
uiBut *uiDefBlockBut(...);
uiBut *uiDefBlockButN(...);
...
Listing 8-17

Snippet of enums and prototypes from UI_interface.h

UI_view2d.h

This header gives access to the View2D struct, which is used for drawing on two-dimensional surfaces in editor regions. We saw an example of its use in our tutorial editor. Some of its function prototypes are shown in Listing 8-18.
...
void UI_view2d_draw_lines_y__values(const struct View2D *v2d);
void UI_view2d_draw_lines_x__values(const struct View2D *v2d);
void UI_view2d_draw_lines_x__discrete_values(const struct View2D *v2d ...);
...
Listing 8-18

Example prototypes from UI_View2D.h

UI_resources.h

“UI resources” are collectively related to theming and color schemes in the Blender UI. As such, enums for color and theme identification are defined in UI_resources.h , as are prototyped functions for getting and setting “UI resources.” Listing 8-19 provides an example from this header file.
...
void UI_GetThemeColorShade3fv(...);
void UI_GetThemeColorShade3ubv(...);
void UI_GetThemeColorShade4ubv(...);
...
Listing 8-19

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

Let us look at the directory layout in source/blender/editors/interface/, where the Blender UI_* API implementation resides (see Figure 8-4). There, we can see a number of files.
../images/495914_1_En_8_Chapter/495914_1_En_8_Fig4_HTML.jpg
Figure 8-4

The interface UI_* API from the editors module, located at source/blender/editors/interface/

These files are separated into ones that deal either with a particular category of UI elements, their operators, their handlers (callbacks from the operators), UI structs and enum definitions, or View2D struct functionality. The gpu module’s API is called extensively by the UI interface code from this directory. This can be seen graphically in Figure 8-5.
../images/495914_1_En_8_Chapter/495914_1_En_8_Fig5_HTML.jpg
Figure 8-5

Directory dependency graph for source/blender/editors/interface/. Note the large number of references (total of thirty-three) to the gpu module

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.