Mhclo

February 21, 2026 · View on GitHub

Overview

Mhclo parses and serialises MHCLO clothing files. See the MHCLO file format reference for a complete description of the format.

Vertex mapping

The core data held by an Mhclo instance is the vertex mapping stored in self.verts. Every entry describes how one clothes vertex is anchored to the base mesh:

  • Exact mapping (weights == (1, 0, 0)): the clothes vertex is co-located with a single base-mesh vertex. The MHCLO line contains only that vertex index.
  • Weighted mapping: the clothes vertex is expressed as a barycentric combination of three base-mesh vertices, plus an offset Vector. The MHCLO line contains the three vertex indices, three barycentric weights, and three offset components.

Coordinate system

The MHCLO file format stores offsets in MakeHuman's coordinate system (Y-up, Z-depth). Blender uses Z-up. load and write_mhclo handle the swap transparently: on read, the raw (d0, d1, d2) offsets become Vector((d0, -d2, d1)); on write, Y and Z scale references are exchanged accordingly.

Notes

  • set_scalings is a stub; the body-part detection logic is marked TODO and has no effect.
  • clothes (the loaded Blender mesh object) is not set by __init__; it is set by load_mesh.

Source

src/mpfb/entities/clothes/mhclo.py

Dependencies

DependencyUsage
mathutils.VectorOffset vectors in self.verts
ObjectServiceLoading and saving wavefront OBJ files
LogServiceLogging via LogService.get_logger("entities.mhclo")
LocationServiceLocating the hm08_config.json mesh metadata file

Attributes

AttributeTypeDefaultDescription
obj_filestr or NoneNoneAbsolute path to the OBJ mesh referenced in the .mhclo file
x_scaletuple or NoneNone(vmin, vmax, scale) for the X axis reference scale
y_scaletuple or NoneNone(vmin, vmax, scale) for the Y axis reference scale
z_scaletuple or NoneNone(vmin, vmax, scale) for the Z axis reference scale
authorstr"unknown"Author metadata from the MHCLO header comment
licensestr"CC0"License metadata from the MHCLO header comment
namestr"imported_cloth"Name key from the MHCLO body
descriptionstr"no description"Description metadata from the MHCLO header comment
basenamestr or NoneNoneAbsolute file path minus the .mhclo extension; set by load
weights_filestr or NoneNoneAbsolute path to the vertex bone weights file (.mhw) if declared
materialstr or NoneNoneAbsolute path to the linked .mhmat material file
tagsstr""Comma-separated tag string accumulated from tag lines
zdepthint50Z-depth rendering order hint
firstint0Starting clothes vertex index (always 0; reserved field from the format)
vertsdict[int, dict]{}Vertex mapping: key is clothes vertex index; value has "verts" (3-tuple of int), "weights" (3-tuple of float), "offsets" (Vector)
delvertslist[int][]Base-mesh vertex indices to hide when clothes are applied
deleteboolFalseWhether any delete_verts section was found in the file
delete_groupstr"Delete"Name of the deletion vertex group
uuidstr or NoneNoneUUID from the MHCLO file
max_poleint or NoneNoneMaximum pole count, if declared
clothesbpy.types.Object(not set by __init__)The loaded Blender mesh object; set by load_mesh

Public API

__init__()

Initialise an empty Mhclo object with all fields set to their defaults. No file is read.

Returns: None.


load(mhclo_filename, *, only_metadata=False)

Parse an MHCLO file from disk and populate the object's attributes.

Metadata fields (author, license, description) are extracted from comment lines. Vertex mapping (verts, delverts) is parsed from the body. If only_metadata=True, parsing stops after the metadata/header fields; the vertex sections are skipped.

The Y/Z coordinate swap is applied to offset vectors on read: raw (d0, d1, d2) → Vector((d0, -d2, d1)).

ArgumentTypeDefaultDescription
mhclo_filenamestr—Absolute path to the .mhclo file to parse
only_metadataboolFalseIf True, skip vertex mapping data after reading metadata

Returns: None.

Raises: ValueError if mhclo_filename is empty or falsy; IOError if the file does not exist.


load_mesh(context)

Import the OBJ mesh referenced in self.obj_file into Blender and store the resulting object in self.clothes.

ArgumentTypeDefaultDescription
contextbpy.types.Context—Current Blender context

Returns: The newly created bpy.types.Object.

Raises: ValueError if obj_file is not set; IOError if the OBJ import fails.


get_weights_filename(suffix=None)

Return the path of the vertex bone weights file derived from the MHCLO basename.

ArgumentTypeDefaultDescription
suffixstr or NoneNoneOptional suffix inserted between the basename and the .mhw extension

Returns: A str of the form <basename>[.<suffix>].mhw.


set_scalings(context, human)

Determine the body part this clothing item was designed for by comparing self.x_scale against known body-part dimension data from hm08_config.json.

ArgumentTypeDefaultDescription
contextbpy.types.Context—Current Blender context
humanbpy.types.Object—The human base-mesh object

Returns: None.

Note: The body-part detection is incomplete (stub). The matched bodypart variable is identified but never stored or acted upon.


write_mhclo(filename, also_export_mhmat=False, also_export_obj=True, reference_scale=None)

Serialise the current state of this object to an MHCLO file on disk.

The vertex mapping is written with exact matches as single-index lines and weighted matches as 9-value lines. Delete-vertex ranges are run-length encoded with - separators. Y and Z scale references are exchanged to match the MakeHuman file convention.

ArgumentTypeDefaultDescription
filenamestr—Output path for the .mhclo file
also_export_mhmatboolFalseIf True, also write a .mhmat file alongside the MHCLO
also_export_objboolTrueIf True, export self.clothes as a .obj file using ObjectService
reference_scaledict or NoneNoneDict with keys xmin, xmax, x_scale, ymin, ymax, y_scale, zmin, zmax, z_scale; written as x_scale/y_scale/z_scale lines

Returns: None.

Raises: ValueError if filename is None, or if also_export_mhmat or also_export_obj is True and self.clothes has not been set.


Examples

Load metadata only

from mpfb.entities.clothes.mhclo import Mhclo

mhclo = Mhclo()
mhclo.load("/path/to/shirt.mhclo", only_metadata=True)
print(mhclo.name, mhclo.author, mhclo.uuid)

Full load and inspect vertex mapping

from mpfb.entities.clothes.mhclo import Mhclo

mhclo = Mhclo()
mhclo.load("/path/to/shirt.mhclo")

for vert_idx, mapping in mhclo.verts.items():
    verts   = mapping["verts"]    # 3-tuple of base-mesh vertex indices
    weights = mapping["weights"]  # 3-tuple of barycentric weights
    offsets = mapping["offsets"]  # mathutils.Vector offset
    print(vert_idx, verts, weights, offsets)