TargetService

May 10, 2026 · View on GitHub

Overview

TargetService manages "targets" — serialized shape keys used to morph MPFB human base meshes. A target is fundamentally a list of vertex indices paired with XYZ offset vectors describing how each vertex should be displaced when the target is applied. In Blender, targets are stored as shape keys with a one-to-one mapping between a target file and a shape key.

The service handles the full target lifecycle: creating shape keys, loading targets from .target or .target.gz files, converting between shape keys and target string formats, and managing the macro target system. Macro targets provide high-level phenotype control (gender, age, muscle, weight, race, proportions, height, cupsize, firmness) by interpolating between combinations of underlying shape keys according to a configuration defined in macro.json.

Because Blender limits shape key names to 61 characters, TargetService includes a name encoding/decoding system that abbreviates common terms (e.g., macrodetail to $md, female to $fe). All methods are static; the class should never be instantiated.

Source

src/mpfb/services/targetservice.py

Dependencies

DependencyUsage
LogServiceLogging via LogService.get_logger("services.targetservice")
AssetServiceFinding target files across asset root directories
LocationServiceResolving paths to system targets, custom targets, and UV layer data
ObjectServiceObject type queries (for symmetrization validation)
GeneralObjectPropertiesReading scale factor from object custom properties
HumanObjectPropertiesReading macro attribute values (gender, age, etc.) from basemesh
PrimitiveProfilerPerformance profiling of target operations

Constants

Shape Key Name Encoding

Pairs used to shorten shape key names to fit within Blender's 61-character limit:

OriginalEncoded
macrodetail$md
female$fe
male$ma
caucasian$ca
asian$as
african$af
average$av
weight$wg
height$hg
muscle$mu
proportions$pr
firmness$fi
ideal$id
uncommon$un
young$yn
child$ch

Macro Attributes

The macro target system uses these attributes, each with a default value of 0.5:

gender, age, muscle, weight, proportions, height, cupsize, firmness

Plus a race sub-dictionary with asian, caucasian, and african (defaults: 0.33 each).

Public API

Shape Key Creation and Queries

create_shape_key(blender_object, shape_key_name, also_create_basis=True, create_from_mix=False)

Create a new shape key on a Blender object. If also_create_basis is True and no Basis shape key exists, one is created first. The new shape key's value is set to 1.0 and it becomes the active shape key.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe mesh object to add the shape key to
shape_key_namestrName for the new shape key
also_create_basisboolTrueCreate a Basis shape key if missing
create_from_mixboolFalseCreate from the current mix of existing shape keys

Returns: bpy.types.ShapeKey — The newly created shape key.


has_any_shapekey(blender_object)

Check if a Blender object has any shape keys at all.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe object to check

Returns: boolTrue if the object has at least one shape key.


has_target(blender_object, target_name, also_check_for_encoded=True)

Check if a Blender object has a specific shape key, optionally checking the encoded form of the name as well.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe object to check
target_namestrThe shape key name to look for
also_check_for_encodedboolTrueAlso check the encoded version of the name

Returns: boolTrue if the shape key exists.


get_target_value(blender_object, target_name)

Retrieve the current value of a specific shape key.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe object to query
target_namestrThe shape key name

Returns: float — The shape key value, or 0.0 if not found.


set_target_value(blender_object, target_name, value, delete_target_on_zero=False)

Set the value of a specific shape key. Optionally removes the shape key if the value is set to zero.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe object to modify
target_namestrThe shape key name
valuefloatThe value to set
delete_target_on_zeroboolFalseRemove the shape key if value is below 0.0001

Returns: None

Raises: ValueError if the object or target name is invalid, or the object has no shape keys.


get_target_stack(blender_object, exclude_starts_with=None, exclude_ends_with=None)

Retrieve all shape keys from an object as a list of name/value dictionaries. Automatically excludes the Basis shape key and any keys matching the exclusion filters.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe mesh object to query
exclude_starts_withstrNoneExclude shape keys starting with this string (case-insensitive)
exclude_ends_withstrNoneExclude shape keys ending with this string (case-insensitive)

Returns: list[dict] — List of {"target": name, "value": float} dictionaries.

Raises: ValueError if the object is not a valid mesh.


shapekey_is_target(shapekey_name)

Guess whether a shape key is a MakeHuman target based on its name. Checks for macro detail prefixes ($md) and known opposite-pair patterns (e.g., decr-incr, down-up, in-out). May not identify custom targets or unusual names.

ArgumentTypeDefaultDescription
shapekey_namestrThe shape key name to check

Returns: boolTrue if the name matches known target patterns.


Target Loading and Saving

load_target(blender_object, full_path, *, weight=0.0, name=None)

Load a shape key from a .target or .target.gz file and apply it to a Blender object. The shape key name is derived from the filename if not specified. Supports both plain text and gzip-compressed target files.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe mesh object to add the shape key to
full_pathstrAbsolute path to the target file
weightfloat0.0Initial value for the shape key
namestrNoneCustom name for the shape key (derived from filename if None)

Returns: bpy.types.ShapeKey — The created shape key.

Raises: ValueError if the object or path is invalid. IOError if the file doesn't exist.


bulk_load_targets(blender_object, target_stack, encode_target_names=False)

Load multiple targets from a target stack list. Each entry must have "target" (name) and "value" (weight) keys. Target files are resolved via target_full_path and loaded in batch. Targets that cannot be resolved to a file path are skipped with a warning.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe mesh object to load targets onto
target_stacklist[dict]List of {"target": name, "value": weight} dictionaries
encode_target_namesboolFalseWhether to encode target names

Returns: None


target_full_path(target_name)

Search for a target file by name across system targets, custom targets, and all target directories. Returns the first match found.

ArgumentTypeDefaultDescription
target_namestrThe target name to search for

Returns: str or None — The full file path, or None if not found.


bake_targets(basemesh)

Apply all current shape keys to the mesh geometry and remove them. Creates a temporary shape key from the current mix, removes all other shape keys, then removes the temporary one — leaving the mesh in its final deformed state with no shape keys.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe mesh object whose shape keys are to be baked

Returns: None


prune_shapekeys(blender_object, cutoff=0.0001)

Remove shape keys with weight below the cutoff value. Only removes shape keys identified as targets (via shapekey_is_target). The Basis shape key is always preserved.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe mesh object to prune
cutofffloat0.0001Weight threshold below which shape keys are removed

Returns: None


Target/Shape Key Conversion

get_shape_key_as_dict(blender_object, shape_key_name, *, smaller_than_counts_as_unmodified=0.0001, only_modified_verts=True)

Convert a shape key to a dictionary representation containing vertex offsets. Offsets are computed relative to the Basis shape key and divided by the object's scale factor.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe mesh object containing the shape key
shape_key_namestr or bpy.types.ShapeKeyThe shape key name or object
smaller_than_counts_as_unmodifiedfloat0.0001Offset magnitude threshold below which vertices are considered unmodified
only_modified_vertsboolTrueIf True, only include vertices with offsets above the threshold

Returns: dict{"name": str, "vertices": [(index, x, y, z), ...]}.

Raises: ValueError if the object has no shape keys or the named shape key doesn't exist.


shape_key_info_as_target_string(shape_key_info, include_header=True)

Convert a shape key info dictionary (as returned by get_shape_key_as_dict) to a .target file format string. The output uses MakeHuman's XZY coordinate order with negated Y.

ArgumentTypeDefaultDescription
shape_key_infodictShape key info dictionary
include_headerboolTrueWhether to include the standard MakeTarget header

Returns: str — Target file content string.


target_string_to_shape_key(target_string, shape_key_name, blender_object, *, reuse_existing=False)

Parse a target format string and apply it as a shape key on a Blender object. If reuse_existing is True and a shape key with the same name already exists, it is updated instead of creating a new one.

ArgumentTypeDefaultDescription
target_stringstrThe target file content string
shape_key_namestrName for the shape key
blender_objectbpy.types.ObjectThe mesh object to apply the shape key to
reuse_existingboolFalseReuse an existing shape key with the same name

Returns: bpy.types.ShapeKey — The created or updated shape key.


translate_mhm_target_line_to_target_fragment(mhm_line)

Parse a line from a MakeHuman Model (.mhm) save file and extract the target name and weight. Handles translation of opposite-pair terms (e.g., decr|incr) and directory prefixes.

ArgumentTypeDefaultDescription
mhm_linestrA line from an MHM file

Returns: dict{"target": target_name, "value": weight}.


Shape Key Name Encoding

encode_shapekey_name(original_name)

Encode a shape key name by replacing common substrings with short codes to fit within Blender's 61-character limit.

ArgumentTypeDefaultDescription
original_namestrThe original shape key name

Returns: str — The encoded name.


decode_shapekey_name(encoded_name)

Decode a previously encoded shape key name back to its original form.

ArgumentTypeDefaultDescription
encoded_namestrThe encoded shape key name

Returns: str — The decoded name.


filename_to_shapekey_name(filename, *, macrodetail=False, encode_name=None)

Convert a target filename to a shape key name by stripping the file extension (.target, .ptarget, .gz). If macrodetail is True or None (auto-detect from path), prepends macrodetail-. Encoding is applied automatically for macro details or names exceeding 60 characters.

ArgumentTypeDefaultDescription
filenamestrThe filename to convert
macrodetailbool or NoneFalseWhether the file is a macro detail. None for auto-detection
encode_namebool or NoneNoneWhether to encode the name. None for automatic

Returns: str — The shape key name.


macrodetail_filename_to_shapekey_name(filename, encode_name=False)

Convert a macro detail filename to a shape key name. Convenience wrapper around filename_to_shapekey_name with macrodetail=True.

ArgumentTypeDefaultDescription
filenamestrThe macro detail filename
encode_nameboolFalseWhether to encode the resulting name

Returns: str — The shape key name.


expression_name_to_shapekey_name(face_unit_name)

Translate a bare ARKit face unit name to its expression shape key name by prepending the !ex- prefix. Expression shape keys on the basemesh are tagged with this prefix so they can be told apart from modeling shape keys (which use the $md- prefix) and from visemes. The JSON representation of an expression always uses bare ARKit names; the prefix is a Blender-only detail.

ArgumentTypeDefaultDescription
face_unit_namestrThe bare ARKit face unit name, e.g. "browDownLeft"

Returns: str — The corresponding shape key name, e.g. "!ex-browDownLeft".


shapekey_name_to_expression_name(shapekey_name)

Translate an expression shape key name back to its bare ARKit face unit name. Returns None for shape keys that do not begin with !ex-.

ArgumentTypeDefaultDescription
shapekey_namestrA Blender shape key name

Returns: str | None — The bare ARKit name if the input starts with !ex-, otherwise None.


Macro Target System

get_default_macro_info_dict()

Return a dictionary with default values for all macro attributes: gender, age, muscle, weight, proportions, height, cupsize, and firmness at 0.5, with race values at 0.33 each.

Returns: dict — Default macro info dictionary.


get_macro_info_dict_from_basemesh(basemesh)

Read the current macro attribute values stored on a basemesh object's custom properties and return them as a macro info dictionary.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh to read values from

Returns: dict — Macro info dictionary with current values.


calculate_target_stack_from_macro_info_dict(macro_info, cutoff=0.01)

Calculate the complete target stack from a macro info dictionary. This is the core of the macro target system — it interpolates across all combinations of gender, age, muscle, weight, race, height, proportions, cupsize, and firmness to produce a weighted list of target files that should be applied.

ArgumentTypeDefaultDescription
macro_infodictMacro info dictionary (uses defaults if None)
cutofffloat0.01Minimum weight threshold for including a target

Returns: list[list] — List of [target_name, weight] pairs.


get_current_macro_targets(basemesh, decode_names=True)

Retrieve the names of all macro target shape keys currently on a basemesh (those starting with $md).

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh to query
decode_namesboolTrueWhether to decode encoded shape key names

Returns: list[str] — List of macro target names.


reapply_macro_details(basemesh, remove_zero_weight_targets=True)

Recalculate and reapply all macro detail targets to a basemesh. Sets existing macro targets to zero, calculates the required targets from the current macro info, loads any missing targets, and sets all target values. Optionally removes macro targets with zero weight.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh to update
remove_zero_weight_targetsboolTrueRemove macro targets with zero weight

Returns: None


reapply_all_details(basemesh, remove_zero_weight_targets=True)

Reapply both macro details and all micro/custom detail targets to a basemesh. Saves the current non-macro target stack, reapplies macro details, removes and re-loads the micro targets.

ArgumentTypeDefaultDescription
basemeshbpy.types.ObjectThe basemesh to update
remove_zero_weight_targetsboolTrueRemove macro targets with zero weight

Returns: None


Shape Key Manipulation

symmetrize_shape_key(blender_object, shape_key_name, copy_left_to_right=True)

Mirror the vertex coordinates of a shape key across the X axis using a predefined mirror table. Only works on Basemesh objects.

ArgumentTypeDefaultDescription
blender_objectbpy.types.ObjectThe basemesh object
shape_key_namestrThe shape key to symmetrize
copy_left_to_rightboolTrueIf True, copy left side to right; if False, right to left

Returns: None

Raises: ValueError if the object type is not "Basemesh".


Examples

Loading and Applying Targets

from mpfb.services.targetservice import TargetService

# Load a single target with a specific weight
TargetService.load_target(basemesh, "/path/to/nose-width.target", weight=0.5)

# Check if a target exists
if TargetService.has_target(basemesh, "nose-width-incr"):
    value = TargetService.get_target_value(basemesh, "nose-width-incr")
    print(f"Current value: {value}")

Working with the Macro Target System

from mpfb.services.targetservice import TargetService

# Get default macro info and modify it
macro_info = TargetService.get_default_macro_info_dict()
macro_info["gender"] = 1.0      # fully female
macro_info["age"] = 0.7         # older
macro_info["race"]["african"] = 1.0
macro_info["race"]["asian"] = 0.0
macro_info["race"]["caucasian"] = 0.0

# Calculate and examine the resulting target stack
target_stack = TargetService.calculate_target_stack_from_macro_info_dict(macro_info)
for name, weight in target_stack:
    print(f"{name}: {weight:.4f}")

# Or simply reapply macro details from the basemesh's current properties
TargetService.reapply_macro_details(basemesh)

Exporting a Shape Key as a Target File

from mpfb.services.targetservice import TargetService

# Convert a shape key to target format
shape_info = TargetService.get_shape_key_as_dict(basemesh, "my_custom_shape")
target_string = TargetService.shape_key_info_as_target_string(shape_info)

# Write to file
with open("/path/to/my_target.target", "w") as f:
    f.write(target_string)