Skip to content

Latest commit

 

History

History
772 lines (466 loc) · 22.7 KB

File metadata and controls

772 lines (466 loc) · 22.7 KB

ObjectService

Overview

ObjectService provides a comprehensive collection of static utility methods for managing and manipulating Blender objects within MPFB. It is one of the most heavily used services in the addon, handling everything from basic object creation to complex relationship queries.

The service's responsibilities fall into several categories: object lifecycle management (creation, linking, deletion), selection and activation (making objects active, deselecting), type identification (determining if an object is a basemesh, skeleton, clothes, etc.), relationship queries (finding parents, children, and siblings), and file operations (loading and saving Wavefront OBJ files).

ObjectService introduces the concept of MPFB object types, stored as custom properties on objects. These types include Basemesh, Skeleton, Subrig, Proxymesh, Eyes, Clothes, Hair, and others. Many methods use these types to identify and filter objects, making it easy to find all clothes attached to a character or locate the skeleton associated with a mesh.

The service also provides specialized methods for working with Rigify rigs, detecting whether an armature is a Rigify metarig or generated rig, and finding the relationship between them.

All methods are static; the class should never be instantiated.

Source

src/mpfb/services/objectservice.py

Dependencies

Dependency Usage
LogService Logging via LogService.get_logger("services.objectservice")
LocationService Resolving paths to base mesh OBJ and vertex group definitions
GeneralObjectProperties Accessing MPFB custom properties on objects
TargetService Shape key operations (imported at call time to avoid circular imports)
MaterialService Material deletion (imported at call time to avoid circular imports)

Object Type Constants

MPFB uses custom properties to identify object types:

Type Description
Basemesh The main human body mesh
Skeleton The primary armature/rig
Subrig Secondary armatures for clothes or accessories
Proxymesh / Proxymeshes Body proxy meshes
Eyes Eye meshes
Eyelashes Eyelash meshes
Eyebrows Eyebrow meshes
Tongue Tongue mesh
Teeth Teeth mesh
Hair Hair mesh or particle system holder
Clothes Clothing items

Public API

Object Creation

random_name()

Generate a random string of 15 lowercase ASCII characters.

Returns: str — A random string suitable for use as a temporary object name.


create_blender_object_with_mesh(name="NewObject", parent=None, skip_linking=False)

Create a new mesh object with an empty mesh data block.

Argument Type Default Description
name str "NewObject" The name for the new object
parent bpy.types.Object None Optional parent object
skip_linking bool False If True, don't link to the current collection

Returns: bpy.types.Object — The newly created mesh object.

The mesh data block is named <name>Mesh.


create_blender_object_with_armature(name="NewObject", parent=None)

Create a new armature object with an armature data block.

Argument Type Default Description
name str "NewObject" The name for the new object
parent bpy.types.Object None Optional parent object

Returns: bpy.types.Object — The newly created armature object.

The armature data block is named <name>Armature.


create_empty(name, empty_type="SPHERE", parent=None)

Create a new empty object.

Argument Type Default Description
name str The name for the empty object
empty_type str "SPHERE" The empty display type (e.g., "PLAIN_AXES", "ARROWS", "SPHERE")
parent bpy.types.Object None Optional parent object

Returns: bpy.types.Object — The newly created empty object.


link_blender_object(object_to_link, collection=None, parent=None)

Link an object to a collection and optionally set its parent.

Argument Type Default Description
object_to_link bpy.types.Object The object to link
collection bpy.types.Collection None Target collection (defaults to current context collection)
parent bpy.types.Object None Optional parent object

Returns: None


Object Deletion

delete_object(object_to_delete)

Safely delete a Blender object.

Argument Type Default Description
object_to_delete bpy.types.Object | None The object to delete

Returns: None

Does nothing if the object is None.


delete_object_by_name(name)

Safely delete an object by its name.

Argument Type Default Description
name str | None The name of the object to delete

Returns: None

Does nothing if the name is empty or no object with that name exists.


Selection and Activation

activate_blender_object(object_to_make_active, *, context=None, deselect_all=False)

Make an object selected and active.

Argument Type Default Description
object_to_make_active bpy.types.Object The object to activate
context bpy.types.Context None Blender context (defaults to bpy.context)
deselect_all bool False If True, deselect all other objects first

Returns: None


select_object(obj)

Select an object and make it active, deselecting all others.

Argument Type Default Description
obj bpy.types.Object The object to select

Returns: None

This is a convenience wrapper around activate_blender_object with deselect_all=True.


deselect_and_deactivate_all()

Ensure no object is selected or active.

Returns: None

Switches to Object mode if necessary and clears all selection state.


Object Queries

object_name_exists(name)

Check if an object with the given name exists.

Argument Type Default Description
name str | None The name to check

Returns: boolTrue if an object with that name exists.


ensure_unique_name(desired_name)

Generate a unique object name by appending incrementing numbers if needed.

Argument Type Default Description
desired_name str The preferred name

Returns: str — The original name if unique, otherwise <name>.001, <name>.002, etc.


find_by_data(id_data)

Find the object that uses a specific data block.

Argument Type Default Description
id_data bpy.types.ID The data block to search for (e.g., mesh, armature)

Returns: bpy.types.Object or None — The object using that data block.


get_list_of_children(parent_object)

Get all direct children of an object.

Argument Type Default Description
parent_object bpy.types.Object The parent object

Returns: list[bpy.types.Object] — List of child objects.


Selection Filtering

get_selected_objects(exclude_non_mh_objects=False, exclude_mesh_objects=False, exclude_armature_objects=False, exclude_meta_objects=True)

Get selected objects with optional filtering.

Argument Type Default Description
exclude_non_mh_objects bool False Exclude objects without an MPFB object_type
exclude_mesh_objects bool False Exclude mesh objects
exclude_armature_objects bool False Exclude armature objects
exclude_meta_objects bool True Exclude objects that are neither mesh nor armature

Returns: list[bpy.types.Object] — Filtered list of selected objects.


get_selected_armature_objects()

Get all selected armature objects.

Returns: list[bpy.types.Object] — List of selected armature objects.


get_selected_mesh_objects()

Get all selected mesh objects.

Returns: list[bpy.types.Object] — List of selected mesh objects.


Object Type Identification

get_object_type(blender_object)

Get the MPFB object type of an object.

Argument Type Default Description
blender_object bpy.types.Object | None The object to query

Returns: str — The object type (e.g., "Basemesh", "Skeleton") or empty string if not set.


object_is(blender_object, mpfb_type_name)

Check if an object matches one or more MPFB types.

Argument Type Default Description
blender_object bpy.types.Object | None The object to check
mpfb_type_name str or Sequence[str] Type name or list of acceptable type names

Returns: boolTrue if the object matches any of the specified types.

Type matching is case-insensitive and uses substring matching.


object_is_basemesh(blender_object)

Check if an object is a basemesh.

Returns: boolTrue if object_type == "Basemesh".


object_is_skeleton(blender_object)

Check if an object is a skeleton.

Returns: boolTrue if object_type == "Skeleton".


object_is_subrig(blender_object)

Check if an object is a subrig.

Returns: boolTrue if object_type == "Subrig".


object_is_any_skeleton(blender_object)

Check if an object is any skeleton type.

Returns: boolTrue if object_type is "Skeleton" or "Subrig".


object_is_body_proxy(blender_object)

Check if an object is a body proxy.

Returns: boolTrue if object_type is "Proxymesh" or "Proxymeshes".


object_is_eyes(blender_object)

Check if an object is eyes.

Returns: boolTrue if object_type == "Eyes".


object_is_basemesh_or_body_proxy(blender_object)

Check if an object is a basemesh or body proxy.

Returns: boolTrue if the object is a basemesh or proxy.


object_is_any_mesh(blender_object)

Check if an object is a mesh (Blender type).

Returns: bool | None — Truthy if the object exists and has type MESH; None when the object is None.


object_is_any_makehuman_mesh(blender_object)

Check if an object is a mesh with an MPFB object type.

Returns: str | bool | None — The object_type string when the object is a mesh with a valid type; otherwise a falsy False/None.


object_is_any_mesh_asset(blender_object)

Check if an object is a mesh asset (clothes, eyes, hair, etc.).

Returns: bool | None — Truthy if the object is a mesh with a mesh asset type; None when the object is None.


object_is_any_makehuman_object(blender_object)

Check if an object has any MPFB object type set.

Returns: str | None — The object_type string when set; None when the object is None.


Relationship Queries

find_object_of_type_amongst_nearest_relatives(blender_object, mpfb_type_name="Basemesh", *, only_parents=False, strict_parent=False, only_children=False)

Find a single object of the specified type among relatives.

Argument Type Default Description
blender_object bpy.types.Object | None The starting object
mpfb_type_name str or Sequence[str] "Basemesh" Type(s) to search for
only_parents bool False Only search up the parent chain
strict_parent bool False Stop at non-MakeHuman parents
only_children bool False Only search children

Returns: bpy.types.Object or None — The first matching object found.

Searches the object itself, its children, parents, and siblings.


find_all_objects_of_type_amongst_nearest_relatives(blender_object, mpfb_type_name="Basemesh", *, only_parents=False, strict_parent=False, only_children=False)

Find all objects of the specified type among relatives.

Argument Type Default Description
blender_object bpy.types.Object | None The starting object
mpfb_type_name str or Sequence[str] "Basemesh" Type(s) to search for
only_parents bool False Only search up the parent chain
strict_parent bool False Stop at non-MakeHuman parents
only_children bool False Only search children

Returns: Generator[bpy.types.Object] — Generator yielding matching objects.


find_related_objects(blender_object, **kwargs)

Find all related MPFB objects.

Returns: Generator[bpy.types.Object] — Generator yielding all related MPFB objects.


find_related_skeletons(blender_object, **kwargs)

Find all related skeleton objects.

Returns: Generator[bpy.types.Object] — Generator yielding skeleton objects.


find_related_mesh_base_or_assets(blender_object, **kwargs)

Find all related mesh objects (basemesh and assets).

Returns: Generator[bpy.types.Object] — Generator yielding mesh objects.


find_related_mesh_assets(blender_object, **kwargs)

Find all related mesh asset objects (not basemesh).

Returns: Generator[bpy.types.Object] — Generator yielding mesh asset objects.


find_related_body_part_assets(blender_object, **kwargs)

Find all related body part assets (eyes, teeth, etc.).

Returns: Generator[bpy.types.Object] — Generator yielding body part objects.


find_deformed_child_meshes(armature_object)

Find all mesh objects deformed by an armature.

Argument Type Default Description
armature_object bpy.types.Object | None The armature to check

Returns: Generator[bpy.types.Object] — Generator yielding deformed mesh objects.


Rigify Integration

object_is_generated_rigify_rig(blender_object)

Check if an object is a generated Rigify rig.

Returns: bool | None — Truthy (the rig_id value) if the armature has a rig_id data property; None when the object is None.


object_is_rigify_metarig(blender_object, *, check_bones=False)

Check if an object is a Rigify metarig.

Argument Type Default Description
blender_object bpy.types.Object | None The object to check
check_bones bool False Also check bones for rigify_type property

Returns: boolTrue if the object is a Rigify metarig.


find_rigify_metarig_by_rig(blender_object)

Find the metarig associated with a generated Rigify rig.

Argument Type Default Description
blender_object bpy.types.Object | None The generated rig

Returns: bpy.types.Object or None — The associated metarig.


find_rigify_rig_by_metarig(blender_object)

Find the generated rig associated with a Rigify metarig.

Argument Type Default Description
blender_object bpy.types.Object | None The metarig

Returns: bpy.types.Object or None — The generated rig.


find_armature_context_objects(armature_object, *, operator=None, is_subrig=None, only_basemesh=False)

Find the base rig, basemesh, and directly controlled mesh for an armature.

Argument Type Default Description
armature_object bpy.types.Object The armature to analyze
operator bpy.types.Operator None Operator for error reporting
is_subrig bool None Override subrig detection
only_basemesh bool False Only find basemesh, not direct mesh

Returns: tuple[Object, Object, Object](base_rig, basemesh, direct_mesh), any may be None.


File Operations

load_wavefront_file(filepath, context=None)

Load a Wavefront OBJ file into Blender.

Argument Type Default Description
filepath str Path to the OBJ file
context bpy.types.Context None Blender context

Returns: bpy.types.Object — The loaded mesh object.

Raises: ValueError if filepath is None, IOError if file doesn't exist.


save_wavefront_file(filepath, mesh_object, context=None)

Save a mesh object to a Wavefront OBJ file.

Argument Type Default Description
filepath str Path for the OBJ file
mesh_object bpy.types.Object The mesh to save
context bpy.types.Context None Blender context

Returns: None

Raises: ValueError if filepath is None or mesh_object is invalid.


load_base_mesh(context=None, scale_factor=1.0, load_vertex_groups=True, exclude_vertex_groups=None)

Load the MPFB base mesh.

Argument Type Default Description
context bpy.types.Context None Blender context
scale_factor float 1.0 Scale to apply to the mesh
load_vertex_groups bool True Whether to load vertex groups
exclude_vertex_groups list[str] None Vertex groups to skip

Returns: bpy.types.Object — The loaded basemesh with smooth shading and MPFB properties set.


Vertex Group Operations

has_vertex_group(blender_object, vertex_group_name)

Check if an object has a specific vertex group.

Argument Type Default Description
blender_object bpy.types.Object | None The object to check
vertex_group_name str | None The vertex group name

Returns: boolTrue if the vertex group exists.


get_vertex_indexes_for_vertex_group(blender_object, vertex_group_name)

Get vertex indices belonging to a vertex group.

Argument Type Default Description
blender_object bpy.types.Object | None The mesh object
vertex_group_name str | None The vertex group name

Returns: list[int] — List of vertex indices.


assign_vertex_groups(blender_object, vertex_group_definition, exclude_groups=None)

Assign vertex groups from a definition dictionary.

Argument Type Default Description
blender_object bpy.types.Object The mesh object
vertex_group_definition dict Dict mapping group names to vertex index lists
exclude_groups list[str] None Groups to skip

Returns: None


get_base_mesh_vertex_group_definition()

Get the standard vertex group definition for the base mesh.

Returns: dict — Dictionary mapping group names to lists of vertex indices.

Results are cached after first load.


Mesh Utilities

get_lowest_point(basemesh, take_shape_keys_into_account=True)

Get the lowest Z coordinate of a basemesh.

Argument Type Default Description
basemesh bpy.types.Object The mesh object
take_shape_keys_into_account bool True Consider shape key deformation

Returns: float — The minimum Z coordinate.

Useful for positioning characters on a ground plane.


get_face_to_vertex_table()

Get the face-to-vertex mapping table for the base mesh.

Returns: dict — Dictionary mapping face indices to vertex index lists.

Results are cached after first load.


get_vertex_to_face_table()

Get the vertex-to-face mapping table for the base mesh.

Returns: dict — Dictionary mapping vertex indices to face index lists.

Results are cached after first load.


extract_vertex_group_to_new_object(existing_object, vertex_group_name)

Extract a vertex group into a new mesh object.

Argument Type Default Description
existing_object bpy.types.Object The source mesh
vertex_group_name str The vertex group to extract

Returns: None — The method creates a new "clothes" mesh object (linked to the current collection) as a side effect, but does not return it.


Examples

Loading and Manipulating the Base Mesh

from mpfb.services.objectservice import ObjectService
import bpy

# Load the base mesh at 10% scale
basemesh = ObjectService.load_base_mesh(
    context=bpy.context,
    scale_factor=0.1,
    load_vertex_groups=True
)

# Make it active
ObjectService.activate_blender_object(basemesh, deselect_all=True)

# Check what type it is
print(ObjectService.get_object_type(basemesh))  # "Basemesh"

Finding Related Objects

from mpfb.services.objectservice import ObjectService

# Starting from any object in a character hierarchy
selected = bpy.context.active_object

# Find the basemesh
basemesh = ObjectService.find_object_of_type_amongst_nearest_relatives(
    selected, "Basemesh"
)

# Find all clothes
for clothes in ObjectService.find_all_objects_of_type_amongst_nearest_relatives(
    basemesh, "Clothes", only_children=True
):
    print(f"Found clothes: {clothes.name}")

# Find the skeleton
skeleton = ObjectService.find_object_of_type_amongst_nearest_relatives(
    basemesh, "Skeleton"
)

Working with Rigify

from mpfb.services.objectservice import ObjectService

armature = bpy.context.active_object

if ObjectService.object_is_generated_rigify_rig(armature):
    # Find the metarig
    metarig = ObjectService.find_rigify_metarig_by_rig(armature)
    print(f"Metarig: {metarig.name}")
elif ObjectService.object_is_rigify_metarig(armature):
    # Find the generated rig
    rig = ObjectService.find_rigify_rig_by_metarig(armature)
    if rig:
        print(f"Generated rig: {rig.name}")

Creating Custom Objects

from mpfb.services.objectservice import ObjectService

# Create an empty as a parent
parent_empty = ObjectService.create_empty("CharacterRoot", empty_type="PLAIN_AXES")

# Create a mesh parented to it
mesh_obj = ObjectService.create_blender_object_with_mesh(
    name="CustomMesh",
    parent=parent_empty
)

# Create an armature parented to the empty
armature_obj = ObjectService.create_blender_object_with_armature(
    name="CustomRig",
    parent=parent_empty
)