(The revision of Blender used for analysis is bacdfc70e2109711eeecef122cf408e6803990ca)

Overview

I assume you have already used “cube adding” before (By pressing Shift + A, and choosing the Cube item in the Mesh sub-menu). And from previous posts we are already familiar with the operator registration process. So in this post, I will focus on the execution of this operator, i.e. the function add_primitive_cube_exec(file editmesh_add.c).

In the following sections, we will have a look at the operator from the view of MVC pattern first. Then we dive into the BMesh system to see how the actual mesh data is constructed.

TL;DR There is also a summary section in the end for you if you just want to see which functions are required for writing your own operators.

The MVC pattern in Blender

Speaking with the theory of MVC pattern, a successfully executed action usually experiences following steps:

  1. When a user triggers some events, the controller translates those raw events into a series of commands, which can be understood by the model.

  2. When the model receives the commands from controller, it will modify itself according to those commands.

  3. After the model finished the modification(or some progress has been made), it notifies the view for an (visual)update for user.

Now let us see how are these MVC concepts implemented in Blender.

The controller part is simple in this case. The clicking on menu item can be directly translated to the commands for models. I will discuss the controller later in more depth for those complicated cases(i.e. the modal operator).

The view part of Blender involves with how to represent models in graphic API draw calls. I do not want to spend much time on this for now. Actually we rarely need to touch this layer for writing operators.

The model part is today’s main topic. In Blender, 3D objects, such as meshes, lights, cameras, are managed under a scene object. For every object in the scene, there is data with specific type stored in it. In our case, the cube we are going to create is an object containing mesh data. Now the whole process is clear: we create an object first, then link it to the scene, and finally fill mesh data into it.

The object creation takes place in function ED_object_add_type(file editmesh_add.c). And the function BKE_object_add links the newly created object to the scene. The trace of object linking is shown below(the latest function invocation comes first):

ED_object_add_type(bContext * C, int type, const float * loc, const float * rot, bool enter_editmode, unsigned int layer) Line 409
make_prim_init(bContext * C, const unsigned char * idname, float * dia, float[4] * mat, bool * was_editmode, const float * loc, const float * rot, const unsigned int layer) Line 65
add_primitive_cube_exec(bContext * C, wmOperator * op) Line 155
wm_operator_invoke(bContext * C, wmOperatorType * ot, wmEvent * event, PointerRNA * properties, ReportList * reports, const bool poll_only) Line 1048

While the object with mesh data is being created, the model send notifications to the view using function WM_event_add_notifier(defined in file wm_event_system.c). There will be multiple notifications for different objects during the operator execution. One of them is for the scene, and that notification is sent from make_prim_init:

WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);

The abbreviation NC in NC_SCENE means “notification category”, and the ND in ND_LAYER_CONTENT stands for “notification data”. All of these enumerator definitions can be found in the file WM_types.h.

Another notification is for the newly created object, and that is sent from make_prim_finish:

WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obedit);

I list the call stack of this notification from make_prim_finish below:

WM_event_add_notifier(const bContext * C, unsigned int type, void * reference) Line 164
make_prim_finish(bContext * C, Object * obedit, bool was_editmode, int enter_editmode) Line 96
add_primitive_cube_exec(bContext * C, wmOperator * op) Line 167
wm_operator_invoke(bContext * C, wmOperatorType * ot, wmEvent * event, PointerRNA * properties, ReportList * reports, const bool poll_only) Line 1048

This then finishes the execution of cube adding operator.

Let me remind you that there are also many other interesting things in add_primitive_cube_exec but I did not mention them. To name a few of them:

  • Operator properties access in ED_object_add_generic_get_opts.

  • Editing mode toggling. The ED_object_editmode_enter(called in make_prim_init) and ED_object_editmode_exit(called in make_prim_finish) are used.

  • Dependency graph. In ED_object_add_type I saw some functions with prefix DAG_. They are for the dependency graph. The dependency graph is an important feature emerged from recent Blender development. I will cover this topic in later posts.

Every topic from above worths an in-depth exploration.

Until now we have discussed all but the mesh data filling. I leave this to the next section.

The BMesh system

In history there was a huge refactoring on mesh implementation. The original mesh sub-system was replaced by BMesh.

The BMesh sub-system is too complicated for such a simple operator as cube adding. But I think it is a good chance for us to learn the mechanism behind the BMesh from this simple case.

After a search on the web I find this design document for BMesh. I suggest to read it later for a more complete understanding of BMesh system, especially the algorithm part if you want to do some extension for it.

Another excellent material on BMesh is in the header file bmesh.h. It covers some practical concepts, such as flags, slots, etc. I think it can be used as a manual on how to use BMesh.

Now let us start with some basic concepts.

BMesh system contains a collection of operators(they are BMesh operators which start with prefix of bmo_, not the Blender operators we saw before), which can be composed to build desired functionality.

The data which these BMesh operators manipulate, is called slots.

A slot(BMOpSlot, file bmesh_operator_api.h) is associated with a slot type(e.g. booleans, integers, or floats). This type directs a BMesh operator how to read the value from slots.

A BMesh operator(BMOperator, file bmesh_operator_api.h) owns two kinds of slots: one is for input, and the other is for output. There is a limit on the slots capacity, which is defined by BMO_OP_MAX_SLOTS.

Before invocation, every BMesh operator is initialized by some meta information, such as “how many slots will be used?” and “which types are these slots?”. This meta information is stored in BMOpDefine structure. And all BMOpDefines are arranged in an array called bmo_opdefines(file bmesh_opdefines.c). Note that every BMOpDefine also has a name. You can use this name as a key to search for the meta information about one BMesh operator in bmo_opdefines array.

Let us now examine how this is implemented in Blender.

We start from the function EDBM_op_call_and_selectf, which is the entry point to the BMesh system:

if (!EDBM_op_call_and_selectf(
        em, op, "verts.out", false,
        "create_cube matrix=%m4 size=%f",
        mat, RNA_float_get(op->ptr, "radius") * 2.0f))
{
    return OPERATOR_CANCELLED;
}

The third argument "verts.out" is the name of the output slot; the argument in C-style format string(the fifth argument) gives the name of BMesh operator which is going to be executed and indicates the input format for the input slots. The slots names, verts.out, matrix, and size are the ones you specified in BMesh operator definitions(bmo_opdefines array).

Now we step inside the function EDBM_op_call_and_selectf.

The first function we encounter is BMO_op_vinitf. This function is a parser of the C-style format string. It gets the name of BMesh operator, and reads the variadic arguments into input slots. In this cube adding case, the operator name is "create_cube". Then a 4x4 matrix and a single float are read into the input slots. When this function returns, the BMesh operator is successfully constructed.

The next function BMO_op_exec executes the BMesh operator. In the function bmo_create_cube_exec you can see the vertices get filled, and faces get constructed. The output slots are also allocated and filled here inside the function BMO_slot_buffer_from_enabled_flag.

I list the call stack below:

blender-app.exe!bmo_create_cube_exec(BMesh * bm, BMOperator * op) Line 667
blender-app.exe!BMO_op_exec(BMesh * bm, BMOperator * op) Line 219
blender-app.exe!EDBM_op_call_and_selectf(BMEditMesh * em, wmOperator * op, const unsigned char * select_slot_out, const bool select_extend, const unsigned char * fmt, ...) Line 294
blender-app.exe!add_primitive_cube_exec(bContext * C, wmOperator * op) Line 161
blender-app.exe!wm_operator_invoke(bContext * C, wmOperatorType * ot, wmEvent * event, PointerRNA * properties, ReportList * reports, const bool poll_only) Line 1048

Inside the function BMO_slot_buffer_from_enabled_flag, there are two concepts you have to understand. One is the header type, which are some properties about the BMesh elements themselves, such as “Is this a vertex, a face, a loop, or an edge?”. The other is the tool flags, which are some configurations about the BMesh operator being executed.

After the BMesh operator execution is finished, we return to the function EDBM_op_call_and_selectf. The BMO_slot_buffer_hflag_enable will use the output slots to mark the newly created vertices selected.

Finally we end the BMesh operator execution with EDBM_op_finish, which dereferences the mesh data we are working on.

Summary

Here is a short summary of functions which you need to call in your own mesh operator:

  • Use ED_object_add_type to create object.
  • Link your object to the scene with BKE_object_add.
  • Use WM_event_add_notifier to emit a specific notification. Refer to the file WM_types.h for notification categories and types.
  • Call specific BMesh operator to manipulate mesh data. You can find BMesh operator specifications(including operator name, input and output slots names, etc.) in bmo_opdefines array(file bmesh_opdefines.c).

Some random notes and remained questions

I think there are a lot of things and details I did not cover in this post. And there are also many questions for me to work on. I list some of them below.

The event system and notifications

I used to believe that in a GUI framework written in real time graphics API like OpenGL, the views(I mean the window renderer) refresh itself in a 60 FPS rate. Hence the models need not to send notifications manually to the view to trigger a refresh.

And I thought the only possible need for a notification is that there are some other models depending on the modified parts. So the manually triggered notification should be only for the dependency resolving.

But after reading the source code of Blender, I noticed the mandatory function call to WM_event_add_notifier in operators. It seems that I was wrong.

Reduce operator looking up by string interning

The BMesh operator is indexed by their names. While you are request one BMesh operator’s definition, you have to perform a linear search(in function BMO_opcode_from_opname) on these names.

This linear search happens twice in the function BMO_op_vinitf.

It would be better to pass index of the operator across functions to reduce the linear search.

More over, for a static string mapping (that is, a mapping defined in compile time and remains constant during runtime), this search can be reduced to constant time using string interning.

Do I have to add new operators to BMesh?

For my proposed project in previous post, I am going to construct mesh dynamically.

I briefly browsed the BMesh operators list and found the bmo_mesh_to_bmesh_def. So I can construct a mesh structure and invoke this operator to convert it to a BMesh.

But there is another possible solution: I can write a new BMesh operator that constructs the mesh directly.

I will try my ideas out in coming posts.