In this post I am going to implement a trivial modifier to show how to extend Blender by adding custom modifiers.

My implementation is based on the tagged version v2.75a. To try it out, you can checkout that tag, branch from that, and apply my patch. You are also encouraged to follow the patch file commit by commit to see how each step is done.

Overview

There has been already a good tutorial on the Blender’s wiki. It covers almost all about Blender modifier development. But it does not have many words on why a particular step is done.

In this post I try to supply more explanations on the mechanism behind every step we will take, and organize the whole process in a more structured way. Besides, I will provide some topics which it does not cover, such as modifier parameter.

Task statements

This trivial modifier will generate a tetrahedron at the location of one object. And we are able to set a scale parameter through modifier’s panel to control the size of the tetrahedron.

Let us call this modifier “Tetrahedron”.

Add skeleton code

We first define the data structure which is going to be passed to the modifier. This is done by adding following to the file source/blender/makesdna/DNA_modifier_types.h:

/* Tetrahedron modifier */
typedef struct TetrahedronModifierData {
    ModifierData modifier;
} TetrahedronModifierData;

In the above code we make a ‘derived’ class of ModifierData. This structure will be parsed by Blender’s reflection system(a.k.a the DNA and RNA infrastructure) later.

Then we define the ModifierTypeInfo for the Tetrahedron modifier in a new file MOD_tetrahedron.c. In this step I just make a skeleton of this modifier. The blanks will be filled later.

Finally, we include the new modifier into the build system by registering MOD_tetrahedron.c into the build script source/blender/modifiers/CMakeLists.txt.

You may refer to part 2 of these series for discussion of ModifierData and ModifierTypeInfo.

Register the new modifier to system

Now we move on to register our new modifier so the Blender modifier system can find it.

At the starting of Blender, it initializes mappings from ModifierType to ModifierTypeInfo1. Whenever a modifier is requested by user, Blender uses the ModifierType as a key to retrieve the desired ModifierTypeInfo2.

We first create a new entry in ModifierType enum by appending eModifierType_Tetrahedron = 52, into the enum ModifierType. As you already noticed, new modifier type must be appended to ModifierType. This is because only the integer value of ModifierType is serialized, any change in ModifierType enum that failed to preserve its original integer value will cause an incorrect deserialization of modifiers in previously saved Blender files.

After that we construct a new mapping from ModifierType to ModifierTypeInfo in function modifier_type_init:

INIT_TYPE(Tetrahedron);

Finally we provide declaration of Tetrahedron’s ModifierTypeInfo in file source/blender/modifiers/MOD_modifiertypes.h so the initializer can see it:

extern ModifierTypeInfo modifierType_Tetrahedron;

Implement the modifier

Now we go on to fill the functions in MOD_tetrahedron.c with actual implementation.

As we discussed in part 2 of these series, we construct a DerivedMesh and pass it through the modifiers stack. Please look at the source code for details.

Expose modifier to user interface

Now our Tetrahedron modifier is almost finished. We still have to expose our modifier to the user interface so one user can add a modifier through the modifier panel or by executing bpy.ops.object.modifier_add.

This task can be done through Blender’s RNA system.

Given a ModifierType, the RNA system should be able to “dynamic cast” a ModifierType to the “derived” one. We add following code in the function rna_Modifier_refine3:

case eModifierType_Tetrahedron:
    return &RNA_TetrahedronModifier;

An icon and description used for “Add Modifier” dropdown menu can be put in the modifier_type_items array3:

{eModifierType_Tetrahedron, "TETRAHEDRON", ICON_MOD_BUILD, "Tetrahedron", ""},

And we also provide similar informations for the modifier panel:

static void rna_def_modifier_tetrahedron(BlenderRNA *brna)
{
    StructRNA *srna;
    PropertyRNA *prop;

    srna = RNA_def_struct(brna, "TetrahedronModifier", "Modifier");
    RNA_def_struct_ui_text(srna, "Tetrahedron Modifier", "Generate Tetrahedron");
    RNA_def_struct_sdna(srna, "TetrahedronModifierData");
    RNA_def_struct_ui_icon(srna, ICON_MOD_BUILD);
}

For simplicity I use ICON_MOD_BUILD here.

Remember to add above function to RNA_def_modifier3 in order to execute it.

Currently we do not have parameters in the Tetrahedron modifier. So we just provide a dummy panel in the Python function DATA_PT_modifiers.draw4:

def TETRAHEDRON(self, layout, ob, md):
    layout.label(text="Comming soon.")

Optionally, you can also customize your modifier icon in the outline view5:

case eModifierType_Tetrahedron:
    UI_icon_draw(x, y, ICON_MOD_BUILD); break;

Add parameters to modifier(optional)

Now you can build your costomized Blender and try the Tetrahedron modifier.

Sometimes we also want to pass parameters through modifier panel.

For example, we can put a scale parameter in TetrahedronModifierdata:

/* Tetrahedron modifier */
typedef struct TetrahedronModifierData {
    ModifierData modifier;
    
    float scale;

    int pad;
} TetrahedronModifierData;

While you are implementing the modifier, you can read this parameter as below:

TetrahedronModifierData *tmd = (TetrahedronModifierData *)md;
float scale = tmd->scale;

Note that we should keep the structure size be multiples of 8 bytes according to this wiki page.

It is also required that this parameter definition should be exported to interface using RNA. We revise the previous RNA property/struct definition as below:

static void rna_def_modifier_tetrahedron(BlenderRNA *brna)
{
    StructRNA *srna;
    PropertyRNA *prop;

    srna = RNA_def_struct(brna, "TetrahedronModifier", "Modifier");
    RNA_def_struct_ui_text(srna, "Tetrahedron Modifier", "Generate Tetrahedron");
    RNA_def_struct_sdna(srna, "TetrahedronModifierData");
    RNA_def_struct_ui_icon(srna, ICON_MOD_BUILD);

    prop = RNA_def_property(srna, "scale", PROP_FLOAT, PROP_FACTOR);
    RNA_def_property_float_sdna(prop, NULL, "scale");
    RNA_def_property_range(prop, 0.0f, 100.0f);
    RNA_def_property_ui_range(prop, 0.0f, 100.0f, 1.0f, 2);
    RNA_def_property_ui_text(prop, "Scale", "Scale of the tetrahedron");
    RNA_def_property_update(prop, 0, "rna_Modifier_update");
}

Note that we provide the same string "scale" for both RNA_def_property and RNA_def_property_float_sdna. But they have different meanings.

The first "scale" for RNA_def_property is the field name to be exported in Python. The second is the name of corresponding struct member in C++.

Finally we go on to revise the panel appearance in Python:

def TETRAHEDRON(self, layout, ob, md):
    layout.prop(md, "scale")

Now the Tetrahedron modifier is finished. And it also works with other modifiers correctly.

Tetrahedron and Array

Footnotes

  1. This happens in the function modifier_type_init(file source/blender/modifiers/intern/MOD_util.c).

  2. This happens in the function modifierType_getInfo(file source/blender/blenkernel/intern/modifier.c).

  3. This is in the file source/blender/makesrna/intern/rna_modifier.c. 2 3

  4. This is in the file release/scripts/startup/bl_ui/properties_data_modifier.py.

  5. This is in the file source/blender/editors/space_outliner/outliner_draw.c.