© 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_5

5. Blender’s Embedded Python

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

CPython is a C-based implementation of Python. It even contains an API. This allows CPython to be used as an external library and linked with a separate application written in C. Functions defined by an “embedding” program may be called via Python script, running on the embedded interpreter. As such, Blender itself embeds a Python interpreter, providing access to some of its own functions. Python-callable functions must be registered using the Python API. This is done by Blender’s python module.

The Blender python Module

Blender makes use of Python in multiple ways. One focus in this chapter is the python module and how its code calls the interpreter. We also look at function registration for the mathutils Python API module.

Because Python is object-oriented, Blender creates “built-in” Python modules and classes, some of whose methods map to Blender’s internal C structs (as in the case of struct bContext) and internal module APIs, for example, the bmesh module. mathutils’s mathematical objects, such as vectors and matrices, are assigned a Python class. Math utility functions from blenlib become methods for these same Python classes.

Source Files and Directories

Top-level directory contents for the python module are shown in Figure 5-1. We see in Figure 5-2 the internal and external dependencies for subdirectories in the python module.
../images/495914_1_En_5_Chapter/495914_1_En_5_Fig1_HTML.jpg
Figure 5-1

Top-level directory in the python module. rna_dump.py and simple_enum_gen.py generate C source code. In simple_enum_gen.py, you can see calls that generate PyModule_AddObject(), a Python API function. BPY_extern.h supplies prototypes for the python module API. BPY_extern_clog.h is used for logging

../images/495914_1_En_5_Chapter/495914_1_En_5_Fig2_HTML.jpg
Figure 5-2

Partial dependency graph for the subdirectories of the python module. Arrows point in the direction of the dependency

The python module provides a Python API for parts of the codebase. There are internal dependencies between subdirectories as well. One salient example is source/blender/python/intern’s dependency on source/blender/python/generic/.

The python module API

The python module API prototypes are provided by source/blender/python/BPY_extern.h. As with other modules, the API is called either inside or outside of the module. A sample of the BPY_* functions is shown in Listing 5-1.

Both BPY_python_start() and BPY_python_end() are called from windowmanager, during both initialization and shutdown. Other prominent API functions execute Python script, usually as the result of an operator’s callback function. BPY_execute_filepath(), BPY_execute_text(), and BPY_execute_string_as_number() each invoke the interpreter.
...
void BPY_python_start(int argc, const char **argv);
void BPY_python_end(void);
void BPY_python_reset(struct bContext *C);
void BPY_python_use_system_env(void);
...
bool BPY_execute_filepath(struct bContext *C, const char *filepath, struct ReportList *reports);
bool BPY_execute_text(struct bContext *C,
                      struct Text *text,
                      struct ReportList *reports,
                      const bool do_jump);
bool BPY_execute_string_as_number(struct bContext *C,
                                  const char *imports[],
                                  const char *expr,
                                  const bool verbose,
                                  double *r_value);
...
Listing 5-1

Snippet from BPY_extern.h, representing part of the module’s API. Function names are in boldface

Extending and Embedding Python

Before we go into the mechanics of the python module, we need to understand how to run the Python interpreter using the CPython API. Python’s primary documentation related to this is “Embedding Python in Another Application.”1 Most of this information can be distilled to just a number of steps.

Note that CPython API prototypes are in Python.h. A program using the CPython API must contain the preprocessor directive #include <Python.h>, assuming Python is in the system path.

When building Blender, there is a CMake variable that can be set to use the Python installation or to download binaries to be placed in the build directory under [build directory]/bin/share/blender/[Blender version]/python/.

Adding Python Extensions

Let us take a look at the CPython API and a few related structs. These also happen to be the CPython API functions Blender’s codebase uses most, although there are variants in some cases. First, we present the CPython API for creating extensions:
  • PyModule_New()

  • PyModule_Create()

  • PyModule_AddObject()

  • PyImport_ExtendInittab()

PyModule_New() and PyModule_Create() both are used to add an extension module. For instance, Blender’s Python mathutils module is added by PyModule_Create(), while its bpy module is via PyModule_New(). PyModule_New() was introduced in version 3.3 of the CPython, after PyModule_Create(). You will see both functions used for creating new modules in Blender.

PyModule_AddObject() adds a class to a module. For example, the mathutils.Vector is added as a built-in Python class using PyModule_AddObject(). PyImport_ExtendInittab() registers modules to be included in Python’s initialization table (i.e., Inittab from the function name). This table contains extension modules to be registered when Python is initialized.

The C structs in the CPython API related to extensions are
  • PyObject

  • PyTypeObject

  • PyMethodDef

  • PyModuleDef

  • PyCFunction

All Python objects are “derived” from PyObject. PyObject structs are “extended,” by adding PyObject_HEAD to a user-defined struct.

Note

In C, there is no explicit language mechanism for inheritance. By adding common fields to the beginning of a user-defined struct, we can treat these structs as having inherited those fields. In CPython, user-defined structs starting with PyObject_HEAD are treated as objects with the base type PyObject. We saw an example of this in Chapter 2, for “DNA” types. There, the ID struct is placed at the beginning of each “DNA” type. This method of “inheritance” is used extensively in Blender’s codebase.

PyObject_HEAD is expanded by the preprocessor into two fields. One field points to an object of PyTypeObject, the other a count of the object’s references.2

PyTypeObject contains, in addition to function pointers for an object’s type, a PyVarObject_HEAD_INIT macro similar to PyObject_HEAD. PyTypeObject also has a field for a PyObject’s size.

Some Python methods, for example, __init__(), are added as fields to a PyTypeObject, the one using it as its type definition. Other types of methods must be added to a PyMethodDef object. The PyTypeObject.tb_methods field points to a methods table (an object of type PyMethodDef). Note that there is a separate PyGetSetDef methods table, used extensively in the implementation of the mathutils.Vector class.

PyModuleDef is used when defining a Python extension module. mathutils’s submodules contain functions. Its function registration uses PyModuleDef. However, registration for functions still looks quite similar to the use of PyMethodDef. PyModuleDef and PyMethodDef are shown in Listing 5-2.
static struct PyMethodDef M_Mathutils_methods[] = {
    {NULL, NULL, 0, NULL},
};
static struct PyModuleDef M_Mathutils_module_def = {
    PyModuleDef_HEAD_INIT,
    "mathutils",         /* m_name */
    M_Mathutils_doc,     /* m_doc */
    0,                   /* m_size */
    M_Mathutils_methods, /* m_methods */
    NULL,                /* m_reload */
    NULL,                /* m_traverse */
    NULL,                /* m_clear */
    NULL,                /* m_free */
};
Listing 5-2

The PyMethodDef and PyModuleDef objects of the mathutils extension module (source/blender/python/mathutils/mathutils.c). mathutils does not contain functions defined in the mathutils namespace alone. All functionality is attached to its various classes or functions in its submodules (geometry, bvhtree, etc.)

The CPython API functions for embedding and running Python are
  • Py_Initialize()

  • Py_Finalize()

  • PyEval_EvalCode()

Py_Intialize() is called to initialize the Python interpreter before any module registration. This is done after a call to PyImport_ExtendInittab(), where the list of built-in modules are added (see Listing 5-3). Py_Finalize() is called from BPY_python_end(), which is invoked outside of the python module, during shutdown. PyEval_EvalCode() is called when an entire script (text file) is loaded and run, or as text from the Blender script editor. Python code executed from the interactive console editor runs by a call generated by the Python library itself.
static struct _inittab bpy_internal_modules[] = {
    {"mathutils", PyInit_mathutils},
#if 0
    {"mathutils.geometry", PyInit_mathutils_geometry},
    {"mathutils.noise", PyInit_mathutils_noise},
    {"mathutils.kdtree", PyInit_mathutils_kdtree},
#endif
    {"_bpy_path", BPyInit__bpy_path},
    {"bgl", BPyInit_bgl},
    {"blf", BPyInit_blf},
    {"imbuf", BPyInit_imbuf},
    {"bmesh", BPyInit_bmesh},
...
};
Listing 5-3

Abbreviated initialization table in source/blender/python/intern/bpy_interface.c. This table is used to add extensions to the list of “built-in” modules, before a call to Py_Initialize(). Each entry in bpy_internal_modules is structs, with an identifier for the module and a function pointer to the initialization function PyMODINIT_FUNC. Each of these modules is then initialized with member classes, functions, etc

BPY_execute_filepath() and BPY_execute_text() both call PyEval_EvalCode(), which in turn calls python_script_exec(). python_script_exec() is defined in source/blender/python/intern/bpy_interface.c. If the script has not already been compiled, then Py_CompileStringObject() is first executed. The resulting Python bytecode is placed in Text’s compiled field. source/blender/python/bpy.c and source/blender/python/bpy_interface.c (among others) make extensive use of the CPython API.

The mathutils Extension Module

As mentioned, one of the Blender Python extension modules is mathutils . It has fewer dependencies than other extensions. Additionally, it does not involve Blender “RNA,” as can be seen in its dependency graph of Figure 5-3.
../images/495914_1_En_5_Chapter/495914_1_En_5_Fig3_HTML.jpg
Figure 5-3

Dependencies of source/blender/python/mathutils/. There are separate bmesh nodes in the graph. The red bmesh node represents the external dependency source/blender/bmesh/. The black bmesh node represents source/blender/python/bmesh/, a dependency within the python module

Blender “RNA” is covered in Chapter 6. Figure 5-4 shows the file contents of source/blender/python/mathutils/.
../images/495914_1_En_5_Chapter/495914_1_En_5_Fig4_HTML.jpg
Figure 5-4

The mathutils directory from the python module. The directory contains the implementation of the mathutils Python module. mathutils features math utilities representing objects such as vectors, matrices, and quaternions

Implementation of the mathutils.Vector Class

In this section, we go over the implementation of the mathutils.Vector Python class. It is a good example to illustrate how Blender implements an extension module’s class.

VectorObject

As mentioned, newly “built-in” classes are derived from the PyObject struct. This is done by including a header at the start of the struct definition. For the mathutils module, Blender uses BASE_MATH_MEMBERS (see Listing 5-4), which contains the PyObject_HEAD macro from the CPython API.
#define BASE_MATH_MEMBERS(_data) \
  /** Array of data (alias), wrapped status depends on wrapped status. */ \
  PyObject_VAR_HEAD float *_data; \
  /** If this vector references another object, otherwise NULL, *Note* this owns its reference */ \
  PyObject *cb_user; \
  /** Which user funcs do we adhere to, RNA, etc */ \
  unsigned char cb_type; \
  /** Subtype: location, rotation... \
   * to avoid defining many new functions for every attribute of the same type */ \
  unsigned char cb_subtype; \
  /** Wrapped data type. */ \
  unsigned char flag
Listing 5-4

The BASE_MATH_MEMBERS macro (defined in source/blender/python/mathutils/mathutils.h). PyObject_VAR_HEAD is included in BASE_MATH_MEMBERS, and shown in boldface

mathutils.Vector is represented by VectorObject, shown in Listing 5-5. It uses BASE_MATH_MEMBERS as described. Notice also that VectorObject contains an instance variable size.
typedef struct {
  BASE_MATH_MEMBERS(vec);
  int size; /* vec size 2 or more */
} VectorObject;
Listing 5-5

The VectorObject struct. This is the “derived” PyObject for mathutils.Vector. BASE_MATH_MEMBERS is shown in boldface

Adding Methods to vector_Type

For mathutils.Vector, the methods are referenced by the tp_methods field. The PyTypeObject for mathutils.Vector is vector_Type (Listing 5-6). Instead, vector_Type uses PyGetSetDef, defined in source/blender/python/mathutils/mathutils_Vector.c. It assigns tp_getset an array of PyGetSetDef objects, one for each getter and setter method pair.
PyTypeObject vector_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    /*  For printing, in format "<module>.<name>" */
    "Vector",             /* char *tp_name; */
...
    /*** Attribute descriptor and subclassing stuff ***/
    Vector_methods,   /* struct PyMethodDef *tp_methods; */
    NULL,             /* struct PyMemberDef *tp_members; */
    Vector_getseters, /* struct PyGetSetDef *tp_getset; */
...
    NULL,             /* allocfunc tp_alloc; */
    Vector_new,       /* newfunc tp_new; */
...
};
Listing 5-6

The vector_Type definition for mathutils.Vector. tp_alloc is NULL. This is significant because Vector_new() must allocate a VectorObject. Since tp_alloc is NULL, a call will be made to _PyObject_GC_New()—a CPython API function—using the BASE_MATH_NEW macro

This implementation allows a getter method to return a value representing the length of the vector, instead of providing direct access to size. mathutils.Vector.length() is the associated getter and is registered in the PyGetSetDef object definition as follows:
{"length", (getter)Vector_length_get, (setter)Vector_length_set, Vector_length_doc, NULL}

The C functions that implement the getter and setter are Vector_length_get and Vector_length_set, respectively. The struct also contains the Python docstring, displayed when the Python’s built-in help() is called in a script. The last field of the PyGetSetDef struct is left NULL, as it is the optional closure slot.

Vector_new()’s Implementation

It will aid in our understanding of mathutils.Vector, to see how instances of this Python class are allocated. From Listing 5-6, we saw that the tp_new slot of vector_Type points to Vector_new(). Vector_new() is responsible for the top-level allocation of an instance of mathutils.Vector. A snippet of Vector_new() C function is shown in Listing 5-7.
static PyObject *Vector_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  float *vec = NULL;
  int size = 3; /* default to a 3D vector */
...
  switch (PyTuple_GET_SIZE(args)) {
    case 0:
      vec = PyMem_Malloc(size * sizeof(float));
...
      copy_vn_fl(vec, size, 0.0f);
      break;
...
  }
  return Vector_CreatePyObject_alloc(vec, size, type);
}
Listing 5-7

Abbreviated Vector_new() from python/mathutils/mathutils_Vector.c. Vector_new() creates VectorObjects. Notice that it calls blenlib’s math utility copy_vn_fl() , shown in boldface

Once called, Vector_new() assigns its local variable vec, to a dynamically allocated array of floats via PyMem_Malloc(). The allocation uses the CPython API, not Blender’s intern/guardedalloc library (see Chapter 1).

From there, Vector_new() calls copy_vn_fl(). The copy_vn_fl() function initializes a “zero” memory copy to vec. See Listing 5-8 for the copy_vn_fl() source. It is important to realize that not all Blender Python extensions use blenlib. In fact, Blender Python extension modules have dependencies on Blender “RNA” and the associated Data API.
void copy_vn_fl(float *array_tar, const int size, const float val)
{
  float *tar = array_tar + (size - 1);
  int i = size;
  while (i--) {
    *(tar--) = val;
  }
}
Listing 5-8

copy_vn_fl() from source/blender/blenlib/intern/math_vector.c. The function starts at the last component of the array and decrements the tar pointer down to the first entry of vec, assigning each entry with the parameter val

After copy_vn_fl() initializes vec, the data portion of a mathutils.Vector object, Vector_new() calls Vector_CreatePyObject_alloc(). Looking at Listing 5-9, we see that there is more than one function for creating a VectorObject.
/*prototypes*/
PyObject *Vector_CreatePyObject(const float *vec,
                                const int size,
                                PyTypeObject *base_type) ATTR_WARN_UNUSED_RESULT;
PyObject *Vector_CreatePyObject_wrap(float *vec,
                                     const int size,
                                     PyTypeObject *base_type) ATTR_WARN_UNUSED_RESULT
    ATTR_NONNULL(1);
PyObject *Vector_CreatePyObject_cb(PyObject *user,
                                   int size,
                                   unsigned char cb_type,
                                   unsigned char subtype) ATTR_WARN_UNUSED_RESULT;
PyObject *Vector_CreatePyObject_alloc(float *vec,
                                      const int size,
                                      PyTypeObject *base_type) ATTR_WARN_UNUSED_RESULT
    ATTR_NONNULL(1);
Listing 5-9

source/blender/python/mathutils/mathutils_Vector.h’s prototypes for instantiating VectorObjects . These are used for a copy constructor, swizzling operators, etc. source/blender/python/mathutils/mathutils_Vector.c contains the function definitions

Since we start with a float array in Vector_new(), Vector_CreatePyObject_alloc()’s responsibility is to use the float array data and wrap it in a PyObject, which in this case is the derived PyObject of type VectorObject. A PyObject representation of a Python class instance is its C side equivalent. As such, a copy of VectorObject, pointing to its associated data (i.e., its float array) is required. Py_CreatePyObject_alloc() calls Vector_CreatePyObject_wrap(), as shown in Listing 5-10.
PyObject *Vector_CreatePyObject_alloc(float *vec, const int size, PyTypeObject *base_type)
{
  VectorObject *self;
  self = (VectorObject *)Vector_CreatePyObject_wrap(vec, size, base_type);
  if (self) {
    self->flag &= ~BASE_MATH_FLAG_IS_WRAP;
  }
  return (PyObject *)self;
}
PyObject *Vector_CreatePyObject_wrap(float *vec, const int size, PyTypeObject *base_type)
{
  VectorObject *self;
...
  self = BASE_MATH_NEW(VectorObject, vector_Type, base_type);
...
  return (PyObject *)self;
}
Listing 5-10

Vector_CreatePyObject_alloc() and an abbreviated Vector_CreatePyObject_wrap(). Subsequent calls for allocation are in boldface

Vector_CreatePyObject_wrap() uses BASE_MATH_NEW (see Listing 5-11), due to the absence of a tp_alloc method in the VectorTypeObject for mathtutils.Vector. Vector_CreatePyObject_wrap() must call a CPython memory allocator for the VectorObject itself.
#define BASE_MATH_NEW(struct_name, root_type, base_type) \
  ((struct_name *)((base_type ? (base_type)->tp_alloc(base_type, 0) : \
                                _PyObject_GC_New(&(root_type)))))
Listing 5-11

The BASE_MATH_NEW macro defined in source/blender/python/mathutils/mathutils.h. BASE_MATH_NEW issues a call to either the tp_alloc method or _PyObject_GC_New(), a CPython function

Exploration of other extension classes and __new__() method implementations reveal differences with Vector_new(). mathutils.Vector is not tied to a Blender “DNA” type. Extension classes that have corresponding “DNA” are likely to use Blender “RNA.”

Vector_normalize()’s Implementation

We now have an example of a “non-magic” method from the mathtutils.Vector class, mathutils.Vector.normalize(). mathutils.Vector.normalize()’s implementation is the definition for Vector_normalize() , seen in Listing 5-12.
static struct PyMethodDef Vector_methods[] = {
    /* Class Methods */
    {"Fill", (PyCFunction)C_Vector_Fill, METH_VARARGS | METH_CLASS, C_Vector_Fill_doc},
...
    /* operate on original or copy */
    {"normalize", (PyCFunction)Vector_normalize, METH_NOARGS, Vector_normalize_doc},
...
    {NULL, NULL, 0, NULL},
};
Listing 5-12

The PyMethodDef for Vector.normalize()

Vector_normalize() is called by the interpreter, whenever an instance of the mathutils.Vector class invokes its normalize() method. It takes a pointer to a VectorObject containing vector entries (a float array). Vector_normalize() must call blenlib’s normalize_vn() to do the raw mathematical work of normalizing a vector (see Listing 5-13).3
static PyObject *Vector_normalize(VectorObject *self)
{
  int size = (self->size == 4 ? 3 : self->size);
  if (BaseMath_ReadCallback_ForWrite(self) == -1) {
    return NULL;
  }
  normalize_vn(self->vec, size);
  (void)BaseMath_WriteCallback(self);
  Py_RETURN_NONE;
}
Listing 5-13

Vector_normalize(), which defines the Python extension method Vector.normalize(). The blendlib function normalize_vn() is in boldface

Other mathutils.Vector Methods

There are many methods from mathutils.Vector in addition to the ones described. A good place to start is with the PyMethodDef array from source/blender/python/mathutils/mathutils_Vector.c (Listing 5-14). The PyCFunction type cast designates a C function. This function is called by the method name, in the first field of PyMethodDef. For instance, mathutils.Vector.Fill() is implemented by C_Vector_Fill(), as shown in Vector_methods[].4 The termination of a methods table is done with a “sentinel,” a NULL-data containing entry. The sentinel here is {NULL, NULL, 0, NULL}, a “nulled out” PyMethodDef struct .
static struct PyMethodDef Vector_methods[] = {
    /* Class Methods */
    {"Fill", (PyCFunction)C_Vector_Fill, METH_VARARGS | METH_CLASS, C_Vector_Fill_doc},
    {"Range", (PyCFunction)C_Vector_Range,
...
    /* operate on original or copy */
    {"normalize", (PyCFunction)Vector_normalize, METH_NOARGS, Vector_normalize_doc},
    {"normalized", (PyCFunction)Vector_normalized, METH_NOARGS, Vector_normalized_doc},
...
    /* operation between 2 or more types  */
    {"reflect", (PyCFunction)Vector_reflect, METH_O, Vector_reflect_doc},
    {"cross", (PyCFunction)Vector_cross, METH_O,
...
    /* base-math methods */
    {"freeze", (PyCFunction)BaseMathObject_freeze, METH_NOARGS, BaseMathObject_freeze_doc},
...
     {NULL, NULL, 0, NULL},
};
Listing 5-14

Abbreviated Vector_methods[] from mathutils_Vector.c. The sentinel and the “Fill” method registration are both shown in boldface

Submodules in mathutils

In Listing 5-15, we can see other classes defined in the Blender python module. Some of these include mathutils.Quanterion, mathutils.Matrix, and mathutils.Color.
static PyMethodDef M_Noise_methods[] = {
    {"seed_set", (PyCFunction)M_Noise_seed_set, METH_VARARGS, M_Noise_seed_set_doc},
    {"random", (PyCFunction)M_Noise_random,
...
    {"cell", (PyCFunction)M_Noise_cell, METH_VARARGS, M_Noise_cell_doc},
    {"cell_vector", (PyCFunction)M_Noise_cell_vector, METH_VARARGS, M_Noise_cell_vector_doc},
    {NULL, NULL, 0, NULL},
};
static struct PyModuleDef M_Noise_module_def = {
    PyModuleDef_HEAD_INIT,
    "mathutils.noise", /* m_name */
    M_Noise_doc,       /* m_doc */
    0,                 /* m_size */
    M_Noise_methods,   /* m_methods */
    NULL,              /* m_reload */
    NULL,              /* m_traverse */
    NULL,              /* m_clear */
    NULL,              /* m_free */
};
/*----------------------------MODULE INIT-------------------------*/
PyMODINIT_FUNC PyInit_mathutils_noise(void)
{
  PyObject *submodule = PyModule_Create(&M_Noise_module_def);
  /* use current time as seed for random number generator by default */
  setRndSeed(0);
  return submodule;
}
Listing 5-15

Example of the mathutils.noise submodule initialization and function registration (from source/blender/python/mathutils/mathutils_noise.c). The submodule initialization function is PyInit_mathutils_noise(), shown in boldface. M_Noise_methods[] is abbreviated for brevity

Files such as mathutils_geometry.c and mathutils_noise.c implement submodule functions (not methods). Without an associated class, their implementation requires less code. An example of this can be seen in source/blender/python/mathutils/mathutils_noise.c. M_Noise_module_def, a struct PyModuleDef, and its corresponding PyInit_mathutils_noise() function are added to bpy_internal_modules[] (Listing 5-16). We can see from bpy_internal_modules[] that creating submodules is no different from top-level modules. BPy_init_modules() is implemented in source/blender/python/intern/bpy.c. It is called after manual loading of all modules. This is where calls are made to PyModule_AddObject().
static struct _inittab bpy_internal_modules[] = {
    {"mathutils", PyInit_mathutils},
#if 0
    {"mathutils.geometry", PyInit_mathutils_geometry},
    {"mathutils.noise", PyInit_mathutils_noise},
    {"mathutils.kdtree", PyInit_mathutils_kdtree},
#endif
    {"_bpy_path", BPyInit__bpy_path},
    {"bgl", BPyInit_bgl},
    {"blf", BPyInit_blf},
    {"imbuf", BPyInit_imbuf},
    {"bmesh", BPyInit_bmesh},
#if 0
    {"bmesh.types", BPyInit_bmesh_types},
    {"bmesh.utils", BPyInit_bmesh_utils},
    {"bmesh.utils", BPyInit_bmesh_geometry},
#endif
...
    {NULL, NULL},
};
/* call BPY_context_set first */
void BPY_python_start(int argc, const char **argv)
{
...
  /* must run before python initializes */
  PyImport_ExtendInittab(bpy_internal_modules);
...
  Py_Initialize();
...
#ifdef WITH_PYTHON_MODULE
  {
    /* Manually load all modules */
    struct _inittab *inittab_item;
    PyObject *sys_modules = PyImport_GetModuleDict();
    for (inittab_item = bpy_internal_modules; inittab_item->name; inittab_item++) {
      PyObject *mod = inittab_item->initfunc();
      if (mod) {
        PyDict_SetItemString(sys_modules, inittab_item->name, mod);
      }
...
  }
#endif
  /* bpy.* and lets us import it */
  BPy_init_modules();
...
}
Listing 5-16

Abbreviated bpy_internal_modules[] and BPY_python_start() from source/blender/python/intern/bpy_interface.c

Summary

In this chapter, we discussed many of the fundamentals of Python extension, as handled in the python module. However, we did not discuss issues related to multithreading, while running the Python interpreter. A mutual exclusion lock (mutex) is necessary, so that the CPython library obtains the “global interpreter lock” (GIL), when working with shared PyObjects. This ensures that PyObjects are not updated by more than one running interpreter. The GIL is managed by various functions of the CPython API.

The python module also uses the CPython API to provide extensions to the Python interpreter. These extensions can be called from a Python script executed by Blender. We saw key parts of mathutils.Vector’s implementation in Blender’s codebase and how C functions are called when a script invokes mathutils.Vector’s methods. This process is similar for all Blender Python extension classes.