(The revision of Blender used for analysis is
I assume you have already used “cube adding” before
Shift + A, and choosing the
Cube item in the
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
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:
When a user triggers some events, the controller translates those raw events into a series of commands, which can be understood by the model.
When the model receives the commands from controller, it will modify itself according to those commands.
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
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 * 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
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
NC_SCENE means “notification category”,
ND_LAYER_CONTENT stands for “notification data”.
All of these enumerator definitions can be found in the file
Another notification is for the newly created object, and that is sent from
I list the call stack of this notification from
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
add_primitive_cube_exec but I did not mention them.
To name a few of them:
Operator properties access in
Editing mode toggling. The
make_prim_finish) are used.
Dependency graph. In
ED_object_add_typeI 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.
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(
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
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
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
Let us now examine how this is implemented in Blender.
We start from the function
which is the entry point to the BMesh system:
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,
are the ones you specified in BMesh operator definitions(
Now we step inside the function
The first function we encounter is
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
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
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
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
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.
Here is a short summary of functions which you need to call in your own mesh operator:
ED_object_add_typeto create object.
- Link your object to the scene with
WM_event_add_notifierto emit a specific notification. Refer to the file
WM_types.hfor 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.)
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
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
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.