This file describes the structure and principles of the code.
MPFB (MakeHuman Plugin For Blender) is a Blender 4.2+ addon for generating and editing human characters. It uses the Blender extension format (declared in blender_manifest.toml), not the legacy addon format. All Python code within src/mpfb/ uses relative imports.
mpfb2/
src/ Main source tree (the Blender addon)
test/ Test suite (pytest, run inside Blender or with blender in headless mode)
docs/ Technical documentation
.pylintrc Pylint configuration
CONTRIBUTING.md Contribution guidelines
LICENSE.CODE.md GPLv3 (code)
LICENSE.ASSETS.md CC0 (assets/data)
README.md Project readme
TODO.md Task list
src/mpfb/
__init__.py Entry point: register() / unregister()
_classmanager.py ClassManager singleton
_preferences.py Blender addon preferences
blender_manifest.toml Blender extension manifest
services/ Stateless singleton services (core logic)
entities/ Data models
ui/ Blender panels and operators
data/ Static assets
The four layers have a clear dependency direction: services depend on nothing outside themselves; entities use services; ui uses both; data is passive.
The register() function in src/mpfb/__init__.py runs in this order:
- Import and register
MpfbPreferencesfrom._preferences. - Import
LogServicefrom.services— this triggers the entire service import chain in dependency order. - Import
ClassManagerfrom._classmanagerand create its singleton. - Import
UI_DUMMY_VALUEfrom.ui— this triggers all UI module imports. Each UI module callsClassManager.add_class()to queue its panels and operators. - Call
ClassManager.register_classes(), which callsbpy.utils.register_class()on every queued class. - Post-registration housekeeping (version check, optional MakeHuman socket discovery).
Critical rule: Nothing is imported at module level in __init__.py outside of register(). Blender requires deferred imports because the module environment is not fully initialized at import time.
Location: src/mpfb/services/
Services are stateless singletons. They use one of two patterns:
Static-method-only classes (most services): The class has only @staticmethod methods and raises RuntimeError in __init__() to prevent instantiation.
Private-instance with static facade (LogService, SocketService, UiService): A private _ServiceName class is instantiated once at module level. A public ServiceName class exposes only @staticmethod methods that delegate to the private instance. LocationService is a variant where the module-level variable is directly the instance.
Services are imported in a specific order in services/__init__.py. The table below lists them roughly bottom-to-top:
| Service | Description |
|---|---|
LogService |
Logging and profiling. Creates per-channel log files. Log levels: CRASH(0) through DUMP(6). |
LocationService |
Resolves filesystem paths: user home, user data, config, cache, log directories, MPFB root. |
These are not services per se but are imported alongside them:
| Class | Description |
|---|---|
ConfigurationSet |
Abstract base class defining config get/set/serialize interface. |
BlenderConfigSet |
Stores config as bpy.props on Blender types. Supports boolean, string, int, float, vector, color, enum. |
SceneConfigSet |
Specialization of BlenderConfigSet targeting bpy.types.Scene. |
DynamicConfigSet |
Extends BlenderConfigSet for dynamic properties on bpy.types.Object. |
JsonCall |
Helper for JSON-RPC calls to the MakeHuman socket server. |
| Service | Description |
|---|---|
SystemService |
System utility functions, Blender version checks. |
ObjectService |
Creating, linking, selecting, activating Blender objects. Vertex group management. Loading/saving to JSON/OBJ. |
ModifierService |
Creating, finding, reordering Blender modifiers. |
NodeService |
Utility functions for shader nodes. Maps node types to socket classes. |
NodeTreeService |
Utility functions for Blender 4+ shader node trees. |
MeshService |
Mesh manipulation: vertex groups, weights, vertex operations. Uses numpy. |
SocketService |
TCP communication with MakeHuman (127.0.0.1:12345). |
UiService |
UI state management: panel visibility, categories, configuration. |
AssetService |
Scanning asset repositories and libraries. |
| Service | Description |
|---|---|
MaterialService |
Materials and node-based skin shaders. |
TargetService |
Morph targets and shape keys. A target is a serialized shape key: vertex indices with XYZ displacement vectors. |
RigService |
Rigs, bones, and weights. |
AnimationService |
Animations and poses. |
FaceService |
Facial animation targets: loading and interpolating viseme and ARKit face unit shape keys. |
ExportService |
Character copy creation and modifier baking. |
HairEditorService |
Convenience methods for the hair editor. |
| Service | Description |
|---|---|
ClothesService |
Loading, fitting, and vertex-matching clothes. |
HumanService |
High-level operations on human objects. Depends on nearly every other service. |
Location: src/mpfb/entities/
Data models for the various asset types MPFB works with:
| Path | Description |
|---|---|
clothes/mhclo.py |
Mhclo — parser and model for .mhclo clothing files. |
clothes/vertexmatch.py |
VertexMatch — vertex matching for clothing fitting. |
material/mhmaterial.py |
MhMaterial — parser and model for .mhmat material files. |
material/makeskinmaterial.py |
MakeSkin material model. |
material/enhancedskinmaterial.py |
Enhanced skin material model. |
material/mhmatkeys.py, mhmatkeytypes.py |
Material property key and type definitions. |
rig.py |
Rig entity. |
meshcrossref.py |
MeshCrossRef — cross-referencing mesh data. |
primitiveprofiler.py |
PrimitiveProfiler — performance profiling utility. |
nodemodel/v2/ |
Node tree wrappers: primitives (~100 shader node wrappers), composites (~50 node group wrappers), and materials. |
objectproperties/ |
JSON-based property definitions for general, human, and rig properties. |
rigging/ |
Rig helpers (arm, leg, eye, finger IK/FK) and Rigify integration. |
socketobject/ |
Objects received from MakeHuman socket: body, mesh, proxy. |
Many UI panels define their properties through JSON files in a directory. The pattern:
from ...services import SceneConfigSet
_PROPERTIES_DIR = os.path.join(os.path.dirname(__file__), "properties")
SOME_PROPERTIES = SceneConfigSet.from_definitions_in_json_directory(_PROPERTIES_DIR, prefix="some_feature")Each .json file defines one property with keys like name, type, description, default, min, max, items, and label. BlenderConfigSet.get_definitions_in_json_directory() reads all JSON files from a directory and returns them as property definition dicts.
Location: src/mpfb/ui/
The UI layer contains ~37 feature subdirectories plus base classes and top-level panel definitions.
Abstract_Panel (ui/abstractpanel.py): Base class for all MPFB panels. Inherits from bpy.types.Panel. Provides:
create_box(layout, box_text, icon)— creates a labeled box in the panel layout.get_basemesh(context)— finds the basemesh object from context.active_object_is_basemesh(context)— class method used inpoll()for panel visibility.
MpfbOperator (ui/mpfboperator.py): Base class for all MPFB operators. Inherits from bpy.types.Operator. Provides:
execute(context)— wrapshardened_execute()in try/except. On exception, generates a detailed error report (context, operator info, system info, stack trace) and writes it to a log file.hardened_execute(context)— abstract method subclasses must override. This is where the actual operator logic goes.
Files like createpanel.py, materialspanel.py, rigpanel.py, etc. define the sidebar panel categories visible in Blender's 3D viewport.
Each feature subdirectory typically contains:
somefeature/
__init__.py Imports operators and panels, calls ClassManager.add_class()
somefeaturepanel.py Panel class (inherits Abstract_Panel)
operators/
__init__.py
someoperator.py Operator class (inherits MpfbOperator)
properties/ Optional: JSON property definition files
someprop.json
Every UI module's __init__.py calls ClassManager.add_class(SomePanel) and ClassManager.add_class(SomeOperator) during import. The ClassManager (in src/mpfb/_classmanager.py) collects all classes into a list and registers them all at once during register().
When code checks are enabled, ClassManager.add_class() validates:
- Operators:
bl_idnamemust start with"mpfb.", class must inherit fromMpfbOperator. - Panels: must have
bl_label,bl_space_type,bl_region_type,bl_category; must inherit fromAbstract_Panel.
All operator bl_idname values must start with mpfb.. Example: bl_idname = "mpfb.load_clothes".
Location: src/mpfb/data/
Static assets bundled with the addon:
| Directory | Contents |
|---|---|
3dobjs/ |
Base mesh and body part OBJ files. |
hair/ |
Hair particle system data. |
mesh_metadata/ |
Metadata about mesh structures. |
node_trees/ |
Serialized shader node tree definitions. |
poses/ |
Pose data files. |
rigs/ |
Rig definition files. |
settings/ |
Configuration and setting files. |
targets/ |
Morph target files (.target format). |
textures/ |
Texture images. |
uv_layers/ |
UV mapping data. |
walkcycles/ |
Walk cycle animation data. |
Location: test/
Tests run inside Blender's embedded Python using pytest. Blender loads the addon, then pytest discovers and runs tests.
The blender instance used for running the tests need to be prepared. Generally, you need to do this:
- Create a new extension "repository", with a custom location set to the "src" dir in the mpfb source
- Enable the MPFB extension
- Install the makehuman system assets pack
- Enable rigify
- Open and execute the "test/run_this_to_install_pytest.py" in the script panel
Once this has been done, you can run tests in a headless manner from console prompt.
cd test
export BLENDER_EXE=/path/to/blender
./execute_tests_headless.bashTo run a specific test module:
cd test
export BLENDER_EXE=/path/to/blender
export TEST_MODULE=tests/bbb_services/objectservice_test.py
./execute_tests_headless.bashTest directories use alphabetical prefixes to control execution order:
| Prefix | Directory | Purpose |
|---|---|---|
aaa |
aaa_context_test.py |
Context and environment validation |
bbb |
bbb_services/ |
Service layer tests |
ccc |
ccc_data/ |
Data layer tests |
ddd |
ddd_entities/ |
Entity tests |
eee |
eee_ui/ |
UI integration tests |
Code coverage reports are generated to test/coverage/ when running headless.
Pylint is configured via .pylintrc. Key settings:
- Max line length: 160 characters
- Indent: 4 spaces
- Naming:
snake_casefor functions, variables, and modules;PascalCasefor classes - C extensions:
bpy,mathutils,addon_utilsare whitelisted - Several complexity checks are disabled (too-many-branches, too-many-arguments, etc.)
Run pylint:
pylint src/mpfb/