ClothesService

February 7, 2026 · View on GitHub

Overview

ClothesService provides static methods for managing and manipulating clothes objects in relation to a MakeHuman basemesh. Its primary responsibility is fitting clothes to a character by translating and scaling each clothes vertex according to a mapping defined in an MHCLO file. Each clothes vertex is mapped to three basemesh vertices with barycentric weights and an offset vector, allowing clothes to deform as the character's shape changes.

A second major responsibility is geometry hiding. When a piece of clothing covers part of the body, the underlying basemesh faces can be hidden via "delete groups" — vertex groups combined with MASK modifiers. ClothesService creates and updates these groups, applying a conservative mask algorithm that removes outlier vertices to avoid visual artefacts at group edges.

The service also handles weight interpolation for rigging: when a rig is present, bone weights are transferred from the basemesh to the clothes mesh using the MHCLO vertex mapping. Custom weight overrides can be loaded from sidecar files. For the MakeClothes workflow, the service can validate whether a mesh is suitable for use as clothes and can generate MHCLO data from scratch by matching a clothes mesh to the basemesh. All methods are static; the class should never be instantiated.

Source

src/mpfb/services/clothesservice.py

Dependencies

DependencyUsage
LogServiceLogging via LogService.get_logger("services.clothesservice")
ObjectServiceBlender object operations, vertex group queries, face/vertex tables
MeshServiceFinding vertices belonging to a vertex group
LocationServiceResolving user cache paths
AssetServiceLocating asset files by path fragment
RigServiceRig identification, weight loading, armature modifier setup
MhcloMHCLO entity for loading and representing clothes mapping data
RigRig entity for sub-rig creation
VertexMatchVertex matching algorithm used during MHCLO creation
MeshCrossRefCross-reference data structure for mesh vertices, faces, and groups
GeneralObjectPropertiesReading/writing custom object properties (asset source, scale factor, UUID)

Constants

CLOTHES_REFERENCE_SCALE

A module-level dictionary mapping body part names to vertex index pairs used for computing reference scales. Each entry defines six vertex indices (xmin, xmax, ymin, ymax, zmin, zmax) that bound the body part in world space. The distance between these vertices provides a baseline for scaling clothes offsets during the MakeClothes matching process.

Body parts covered: Body, Head, Teeth, Torso, Arm, Hand, Leg, Foot, Eye, Genital.

Public API

Clothes Fitting

fit_clothes_to_human(clothes, basemesh, mhclo=None, set_parent=True)

Move clothes vertices so they fit the current shape of the basemesh. The method creates a temporary "from mix" shape key on the basemesh to capture the combined effect of all current shape keys, then repositions each clothes vertex using the MHCLO mapping data (three basemesh vertex references, barycentric weights, and a scaled offset). If the clothes mesh has shape keys, the Basis shape key is updated via edit mode. When mhclo is not provided, it is loaded automatically from the asset source stored in the clothes object's custom properties.

ArgumentTypeDefaultDescription
clothesbpy.types.ObjectThe clothes mesh object to refit
basemeshbpy.types.ObjectThe basemesh to fit clothes to
mhcloMhcloNonePre-loaded MHCLO data; auto-loaded from object properties if None
set_parentboolTrueWhether to parent the clothes to the basemesh or its rig

Returns: None

Raises: ValueError if basemesh or clothes is None, basemesh is not a valid basemesh, MHCLO vertex info is missing, or asset source metadata is insufficient. IOError if the MHCLO file cannot be found on disk.


get_reference_scale(basemesh, body_part_reference="Torso")

Compute a reference scale dictionary from a basemesh, measuring the distances between specific vertex pairs defined in CLOTHES_REFERENCE_SCALE. The basemesh is temporarily duplicated with MASK and SUBSURF modifiers removed, then evaluated to get post-modifier vertex positions. The resulting scale values are normalized by the basemesh's scale factor.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh to measure
body_part_referencestr"Torso"Key in CLOTHES_REFERENCE_SCALE to use for measurements

Returns: dict — A dictionary with keys x_scale, y_scale, z_scale (float distances) plus the vertex indices from the selected body part reference.


Delete Groups

update_delete_group(mhclo, basemesh, replace_delete_group=False, delete_group_name=None, add_modifier=True, skip_if_empty_delete_group=True)

Create or update a "delete" vertex group on the basemesh to hide geometry underneath clothes. The group is populated with vertex indices from the MHCLO's delete vertex list, filtered through a conservative mask algorithm that excludes vertices on partially-affected face boundaries. A MASK modifier is added to the basemesh if one does not already exist for this group.

ArgumentTypeDefaultDescription
mhcloMhcloThe MHCLO object containing delete vertex data
basemeshbpy.types.ObjectThe basemesh to add the delete group to
replace_delete_groupboolFalseRemove any existing group with the same name before creating
delete_group_namestrNoneCustom name for the group; uses MHCLO's delete_group or "Delete" if None
add_modifierboolTrueWhether to add a MASK modifier for the group
skip_if_empty_delete_groupboolTrueReturn immediately if the MHCLO has no delete vertices

Returns: None


create_new_delete_group(basemesh, clothes, mhclo, group_name="Delete")

Create a new delete group on the basemesh based on which vertices are covered by the clothes mesh. Unlike update_delete_group which reads pre-defined delete vertices from the MHCLO, this method derives the covered area from the MHCLO vertex matchings: it collects all basemesh vertices referenced by the matchings, then extends the set to include all vertices belonging to any face touched by those vertices.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh object
clothesbpy.types.ObjectThe clothes object matching the MHCLO
mhcloMhcloA populated MHCLO with vertex matchings
group_namestr"Delete"Name for the new vertex group

Returns: bpy.types.VertexGroup — The newly created delete group.

Raises: ValueError if any argument is None, objects are not meshes, or the MHCLO has no matchings or mismatched vertex counts.


Rigging and Weights

interpolate_weights(basemesh, clothes, rig, mhclo)

Copy rigging weights from the basemesh to the clothes mesh by interpolating through the MHCLO vertex mapping. For each clothes vertex, the method looks up the three mapped basemesh vertices, multiplies each basemesh vertex's bone group weights by the MHCLO barycentric weight, and accumulates the result. Interpolated weights below 0.001 are discarded for performance. The method also carries over DEF- (Rigify deform) and mhmask- vertex groups.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh with source vertex weights
clothesbpy.types.ObjectThe clothes mesh to receive interpolated weights
rigbpy.types.ObjectThe armature whose bone names define the vertex groups
mhcloMhcloMHCLO mapping data linking clothes vertices to basemesh vertices

Returns: None


set_up_rigging(basemesh, clothes, rig, mhclo, *, interpolate_weights=True, import_subrig=True, import_weights=True)

Set up the full rigging pipeline for a clothes object: optionally load a custom sub-rig from an .mpfbskel sidecar file, parent the clothes to the rig (or sub-rig), interpolate weights, load custom weight overrides, and ensure the armature modifier is in place.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh object
clothesbpy.types.ObjectThe clothes mesh to rig
rigbpy.types.ObjectThe main armature object
mhcloMhcloMHCLO mapping data
interpolate_weightsboolTrueWhether to interpolate weights from the basemesh
import_subrigboolTrueWhether to import a custom sub-rig if available
import_weightsboolTrueWhether to load custom weight files

Returns: None


load_custom_weights(clothes, armature_object, subrig, mhclo)

Load custom weight files for the given clothes and rig. The method tries loading weights from several sidecar files: the base weights file (no suffix), a "force" file for non-standard mask groups, and a rig-type-specific file determined by RigService.identify_rig. For Rigify generated rigs, the rig type is mapped back to the base Rigify type.

ArgumentTypeDefaultDescription
clothesbpy.types.ObjectThe clothes mesh to load weights onto
armature_objectbpy.types.ObjectThe main armature object
subrigbpy.types.Object or NoneThe sub-rig armature, or None if not applicable
mhcloMhcloMHCLO object used to derive weight file paths

Returns: None


interpolate_vertex_group_from_basemesh_to_clothes(basemesh, clothes_object, vertex_group_name, match_cutoff=0.3, mhclo_full_path=None)

Interpolate a single vertex group from the basemesh to the clothes object. The method loads the MHCLO file, finds which clothes vertices are mapped to basemesh vertices that belong to the specified vertex group, and creates a corresponding vertex group on the clothes with those vertices at weight 1.0.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh with the source vertex group
clothes_objectbpy.types.ObjectThe clothes object to add the group to
vertex_group_namestrName of the vertex group to interpolate
match_cutofffloat0.3Cutoff value for matching vertices
mhclo_full_pathstrNonePath to the MHCLO file; auto-resolved if None

Returns: bpy.types.VertexGroup — The newly created vertex group on the clothes.


Asset Management

find_clothes_absolute_path(clothes_object)

Find the absolute file path to the MHCLO asset of a clothes object by reading the asset_source and object_type custom properties and resolving them via AssetService.

ArgumentTypeDefaultDescription
clothes_objectbpy.types.ObjectThe clothes object to look up

Returns: str or None — The absolute path to the MHCLO file, or None if the properties are missing.


set_makeclothes_object_properties_from_mhclo(clothes_object, mhclo, delete_group_name=None)

Update a clothes object's MakeClothes custom properties (author, description, license, name, tag, z_depth, delete_group, homepage) from the metadata stored in an MHCLO object.

ArgumentTypeDefaultDescription
clothes_objectbpy.types.ObjectThe clothes object to update
mhcloMhcloThe MHCLO object containing metadata
delete_group_namestrNoneOverride value for the delete group property

Returns: None


MakeClothes

mesh_is_valid_as_clothes(mesh_object, basemesh)

Validate whether a mesh object can be used as clothes for the given basemesh. Performs a series of checks and returns a detailed report dictionary. The checks include: valid mesh type, has vertices, has vertex groups, each vertex in exactly one group, all vertices belong to faces, all clothes groups exist on the basemesh, and matching scale.

ArgumentTypeDefaultDescription
mesh_objectbpy.types.ObjectThe candidate clothes mesh
basemeshbpy.types.ObjectThe basemesh to validate against

Returns: dict — A validation report with keys: is_valid_object (bool), has_any_vertices (bool), has_any_vgroups (bool), all_verts_have_max_one_vgroup (bool), all_verts_have_min_one_vgroup (bool), all_verts_belong_to_faces (bool), clothes_groups_exist_on_basemesh (bool), objs_same_scale (bool), all_checks_ok (bool), and warnings (list of strings).


create_mhclo_from_clothes_matching(basemesh, clothes, properties_dict=None, delete_group=None)

Create an MHCLO object from scratch by matching clothes vertices to the basemesh. For each clothes vertex, a VertexMatch is computed that finds the three closest basemesh vertices, their barycentric weights, and the offset. The result is a fully populated MHCLO object that can be saved to a file. If a delete group name is specified and exists on the basemesh, the corresponding vertices are included in the MHCLO's delete list.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh to match against
clothesbpy.types.ObjectThe clothes mesh whose vertices will be matched
properties_dictdictNoneOptional dictionary of MHCLO metadata properties to set
delete_groupstrNoneName of a vertex group on the basemesh to use as the delete group

Returns: Mhclo — A populated MHCLO object with vertex matchings.


Examples

Fitting Clothes to a Character

from mpfb.services.clothesservice import ClothesService
from mpfb.entities.clothes.mhclo import Mhclo

# Auto-fit: the MHCLO is loaded from the clothes object's properties
ClothesService.fit_clothes_to_human(clothes_obj, basemesh)

# Manual fit: provide a pre-loaded MHCLO
mhclo = Mhclo()
mhclo.load("/path/to/shirt.mhclo")
mhclo.clothes = clothes_obj
ClothesService.fit_clothes_to_human(clothes_obj, basemesh, mhclo=mhclo)

Setting Up Delete Groups

from mpfb.services.clothesservice import ClothesService

# Update a delete group from MHCLO data
ClothesService.update_delete_group(mhclo, basemesh, delete_group_name="Delete.shirt")

# Create a new delete group derived from clothes coverage
delete_group = ClothesService.create_new_delete_group(basemesh, clothes_obj, mhclo, group_name="Delete.pants")

Rigging Clothes

from mpfb.services.clothesservice import ClothesService

# Full rigging pipeline: weights, sub-rig, and custom weights
ClothesService.set_up_rigging(basemesh, clothes_obj, rig, mhclo)

# Interpolate weights only, skip sub-rig and custom weights
ClothesService.set_up_rigging(basemesh, clothes_obj, rig, mhclo,
                              import_subrig=False, import_weights=False)

# Interpolate a single vertex group (e.g. for proxy meshes)
ClothesService.interpolate_vertex_group_from_basemesh_to_clothes(
    basemesh, proxy_obj, "Delete.shirt"
)

MakeClothes Workflow

from mpfb.services.clothesservice import ClothesService

# Validate a mesh before creating MHCLO data
report = ClothesService.mesh_is_valid_as_clothes(my_clothes_mesh, basemesh)
if report["all_checks_ok"]:
    mhclo = ClothesService.create_mhclo_from_clothes_matching(
        basemesh, my_clothes_mesh,
        properties_dict={"name": "My Shirt", "author": "Me"},
        delete_group="Delete"
    )
else:
    for warning in report["warnings"]:
        print(f"Warning: {warning}")