I start with adding a custom property editor panel to realize my plan stated in the previous post.

The patch file for a sample property editor can be found here.

Task statements

There are several predefined property editors in Blender’s properties view, such as Render, Render Layers, Scene, World, Object, etc.

Sometimes I want to add new features for an element in Blender and provide user interfaces for user to edit its properties.

One approach is to write Python scripts to create custom property panels. But these custom panels are confined in predefined property editors.

For consistency and clarity, we want to group logically related panels in a separate property editor.

We call this new property editor “Simulation”. Here is a screenshot of it:

Custom property editor panel

For simplicity I use ICON_ANIM as its icon.

Overview of our approach

Firstly we will define a new property editor named Simulation in Blender system. Then we register it to UI system in order to make its button appear on the header of properties view. Finally we draw the body of Simulation panel when it is activated.

Define a new panel

A new item of eSpaceButtons_Context1 should be defined:

typedef enum eSpaceButtons_Context {
    BCONTEXT_RENDER = 0,
    BCONTEXT_SCENE = 1,
    BCONTEXT_WORLD = 2,
    BCONTEXT_OBJECT = 3,
    BCONTEXT_DATA = 4,
    BCONTEXT_MATERIAL = 5,
    BCONTEXT_TEXTURE = 6,
    BCONTEXT_PARTICLE = 7,
    BCONTEXT_PHYSICS = 8,
    BCONTEXT_BONE = 9,
    BCONTEXT_MODIFIER = 10,
    BCONTEXT_CONSTRAINT = 11,
    BCONTEXT_BONE_CONSTRAINT = 12,
    BCONTEXT_RENDER_LAYER = 13,
    BCONTEXT_SIMULATION = 14,
    
    /* always as last... */
    BCONTEXT_TOT
} eSpaceButtons_Context;

Also some meta information(description text, icon, etc) should be provided in buttons_context_items2:

{BCONTEXT_SIMULATION, "SIMULATION", ICON_ANIM, "Simulation", "Simulation"}

Draw button on header

The source code for properties view are located in the folder source/blender/editors/space_buttons. After a brief search inside this folder we can locate the area(or region) definition function ED_spacetype_buttons in the file space_buttons.c:

void ED_spacetype_buttons(void)
{
    /* ... */
    
    /* regions: header */
    art = MEM_callocN(sizeof(ARegionType), "spacetype buttons region");
    art->regionid = RGN_TYPE_HEADER;
    art->prefsizey = HEADERY;
    art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D | ED_KEYMAP_FRAMES | ED_KEYMAP_HEADER;
    
    art->init = buttons_header_area_init;
    art->draw = buttons_header_area_draw;
    BLI_addhead(&st->regiontypes, art);

    BKE_spacetype_register(st);
}

For buttons rendering we focus on the header area. We can see the actual header rendering is registered as function buttons_header_area_draw:

static void buttons_header_area_draw(const bContext *C, ARegion *ar)
{
    SpaceButs *sbuts = CTX_wm_space_buts(C);

    /* Needed for RNA to get the good values! */
    buttons_context_compute(C, sbuts);

    ED_region_header(C, ar);
}

The ED_region_header function performs the UI rendering. But what does function buttons_context_compute do?

Understand SpaceButs

Let me distill the essential parts of function buttons_context_compute here:

void buttons_context_compute(const bContext *C, SpaceButs *sbuts)
{
    ButsContextPath *path;
    PointerRNA *ptr;
    int a, pflag = 0, flag = 0;

    /* ... */

    path = sbuts->path;

    /* ... */

    /* for each context, see if we can compute a valid path to it, if
     * this is the case, we know we have to display the button */
    for (a = 0; a < BCONTEXT_TOT; a++) {
        if (buttons_context_path(C, path, a, pflag)) {
            flag |= (1 << a);

            /* ... */
        }
    }

    /* always try to use the tab that was explicitly
     * set to the user, so that once that context comes
     * back, the tab is activated again */
    sbuts->mainb = sbuts->mainbuser;

    /* ... */

    sbuts->pathflag = flag;
}

The SpaceButs structure records the state of each property editors. In particular,

  • path contains the current available components(such as mesh data, particle systems, etc) of an object. It gets its name since a serial hierarchy from the root scene to the corresponding component will be rendered later. It looks like Path of components in properties view.

  • mainb is the index of current property editor(or context in the source code). It has the same integer value of corresponding item in eSpaceButtons_Context.

  • mainbuser is the index of desired property editor from user.

  • pathflag is a bit fields indicating which property editor is available for current object.

Now we can understand what buttons_context_compute does: it loops through all property editors and records available ones to the pathflag.

Render

We simply turn on the corresponding bit in pathflag to mark our new property editor available for header rendering:

/* Always include BCONTEXT_SIMULATION */
flag |= (1 << BCONTEXT_SIMULATION);

However you still cannot see our new button now.

This problem took me some time in my first try since the panel rendering process is rather complicated: it gets issued in C++ side, then goes into Python script, and finally calls back to C++ side for OpenGL rendering. It is really hard to trace the execution due to the discontinuous call stack.

But remember that the pathflag indicates which editor is available? I conjecture that any rendering code may refer to the variable pathflag for available editors. Through a search of pathflag I finally find the solution.

An enum item should be added for each available editor. So I should add this in function rna_SpaceProperties_context_itemf2:

if (sbuts->pathflag & (1 << BCONTEXT_SIMULATION)) {
    RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_SIMULATION);
}

Let us have a look at its stack trace. It is hard to trace through that Python part:

blender-app.exe!rna_SpaceProperties_context_itemf
blender-app.exe!RNA_property_enum_items
blender-app.exe!RNA_property_enum_items_gettexted
blender-app.exe!ui_item_enum_expand
blender-app.exe!uiItemFullR
blender-app.exe!rna_uiItemR
blender-app.exe!UILayout_prop_call
blender-app.exe!RNA_function_call
blender-app.exe!pyrna_func_call
python34_d.dll!56c7de81
[Frames below may be incorrect and/or missing, no symbols loaded for python34_d.dll]    
python34_d.dll!56d99f5c
python34_d.dll!56d999ed
python34_d.dll!56d9742d
python34_d.dll!56d92f31
python34_d.dll!56cc1dc5
python34_d.dll!56c7de81
blender-app.exe!bpy_class_call
blender-app.exe!header_draw
blender-app.exe!ED_region_header
blender-app.exe!buttons_header_area_draw
blender-app.exe!ED_region_do_draw
blender-app.exe!wm_method_draw_triple
blender-app.exe!wm_draw_update
blender-app.exe!ghost_event_proc
blender-app.exe!GHOST_CallbackEventConsumer::processEvent
blender-app.exe!GHOST_EventManager::dispatchEvent
blender-app.exe!GHOST_EventManager::dispatchEvent
blender-app.exe!GHOST_EventManager::dispatchEvents
blender-app.exe!GHOST_System::dispatchEvents
blender-app.exe!GHOST_DispatchEvents
blender-app.exe!wm_window_process_events
blender-app.exe!WM_main
blender-app.exe!main

Draw panels

Now the new property editor button appears on the header of property view. We move on to render panels inside it.

The panel user interface is defined in Python. You can refer to scripts in release/scripts/startup/bl_ui for examples.

Mine is here:

class SimulationButtonsPanel:
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "simulation"


class SIMULATION_PT_context_simulation(SimulationButtonsPanel, Panel):
    bl_label = ""
    bl_options = {'HIDE_HEADER'}

    def draw(self, context):
        print("SimulationPanel.draw")
        layout = self.layout
        layout.label(text="Coming soon.")

Note that I use "simulation" for bl_context. This tells Blender we want to put this panel under the Simulation context.

The C++ side of Blender also have to use this string to dispatch UI rendering to Python. Therefore we add following code in the function buttons_main_area_draw3:

else if (sbuts->mainb == BCONTEXT_SIMULATION)
    ED_region_panels(C, ar, vertical, "simulation", sbuts->mainb);

Some extra notes

In this post we just turn on a bit of pathflag to force our custom panel appear in the user interface. In a real life situation a data block should be defined and a valid path to that block reveals the corresponding bit of pathflag. I leave this for future works.

Footnotes

  1. It is in the file source/blender/makesdna/DNA_space_types.h.

  2. It is in the file source/blender/makesrna/intern/rna_space.c. 2

  3. It is in the file source/blender/editors/space_buttons/space_buttons.c.