In [1]:
# Anny
# Copyright (C) 2025 NAVER Corp.
# Apache License, Version 2.0

Alternative models¶

In this tutorial, we show how to use different rigs and topologies with Anny.

Imports and helper functions¶

In [2]:
import torch
import roma  # A PyTorch library useful to deal with space transformations.
import anny  # The main library for the Anny model.
import trimesh  # For 3D mesh visualization.

# Create and show multiple rigs in one cell
from IPython.display import display, Markdown
import trimesh.viewer.notebook as nb

# Some helper objects for visualization.
trimesh_scene_transform = (
    roma.Rigid(
        linear=roma.euler_to_rotmat("x", [-90.0], degrees=True), translation=None
    )
    .to_homogeneous()
    .cpu()
    .numpy()
)

mesh_material = trimesh.visual.material.PBRMaterial(
    baseColorFactor=[0.6, 0.8, 0.7, 0.5],
    metallicFactor=0.5,
    doubleSided=False,
    alphaMode="BLEND",
)

world_axis = trimesh.creation.axis(axis_length=1.0)
axis = trimesh.creation.axis(axis_length=0.1)


def add_skeleton_to_scene(scene, model, output):
    # Add bones visualization. Procrustes rigs (the default) have no bone tails,
    # so we draw each bone as a segment from its head to its parent's head.
    bone_poses = output["bone_poses"].squeeze(dim=0).cpu()
    bone_heads = bone_poses[:, :3, 3]
    bone_color = [0.8, 0.3, 0.3, 1.0]
    bone_visual = trimesh.visual.TextureVisuals(
        material=trimesh.visual.material.PBRMaterial(
            baseColorFactor=bone_color,
            metallicFactor=0.0,
            roughnessFactor=1.0,
            doubleSided=True,
            alphaMode="BLEND",
        )
    )
    for i in range(1, len(bone_heads)):
        bone_head = bone_heads[model.bone_parents[i]]
        bone_tail = bone_heads[i]
        length = torch.norm(bone_tail - bone_head).item()
        if length < 1e-6:
            continue
        cylinder = trimesh.creation.cylinder(radius=0.005, height=length, sections=16)
        t = (bone_head + bone_tail) / 2
        M = roma.special_gramschmidt(
            torch.stack(
                [
                    bone_tail - bone_head,
                    torch.tensor([0.0, 0.0, 1.0], dtype=torch.float32),
                ],
                dim=-1,
            )
        )
        R = torch.stack([M[:, 2], M[:, 1], M[:, 0]], dim=-1)
        cylinder.visual = bone_visual
        scene.add_geometry(
            cylinder,
            transform=roma.Rigid(R, t).to_homogeneous().numpy(),
            node_name=f"bone_{model.bone_labels[i]}",
        )

    # Add some spheres at the joints
    joint_sphere = trimesh.creation.icosphere(radius=0.008, subdivisions=2)
    joint_sphere.visual = trimesh.visual.TextureVisuals(
        material=trimesh.visual.material.PBRMaterial(
            baseColorFactor=[0.1, 0.1, 0.1, 1.0],
            metallicFactor=0.5,
            roughnessFactor=1.0,
            doubleSided=True,
            alphaMode="OPAQUE",
        )
    )
    for i in range(len(bone_poses)):
        scene.add_geometry(
            joint_sphere,
            transform=bone_poses[i],
            node_name=f"joint_{model.bone_labels[i]}",
        )

Rigs and topology¶

Anny supports various skeletal rigs, including:

  • "makehuman", the default rig provided by MPFB2 (https://github.com/makehumancommunity/mpfb2) with minor vertex weights fixes.
  • "anny" (the default), an adaptation of the "makehuman" rig with more stable bone orientations when the shape changes.
  • "mixamo", inspired by characters from https://www.mixamo.com/.
  • "soma", for compatibility with https://www.github.com/NVlabs/SOMA-X/.

Bones that are not useful for your application can be removed from the Anny rig. Using "anny-notoes" will ignore bones animating individual toes, for example.

Anny also supports various mesh topologies. A topology such as "notoes_collapse5pc" provides coarser mesh output for example, allowing to speed up inference and reduce memory consumption. We provide a "smplx" topology for interoperability with the SMPL-X model (https://smpl-x.is.tue.mpg.de/), for non-commercial use only.

We show below a few combinations of meshes and topologies supported by the model:

In [3]:
viewers = []

for rig, topology in [
    ("anny", "anny"),
    (
        "mixamo",
        "anny",
    ),
    (
        "anny",
        "smplx",
    ),
    (
        "anny",
        "soma",
    ),
    ("anny-notoes-noeyes", "makehuman"),
]:
    model = anny.Anny(rig=rig, topology=topology)
    output = model()

    mesh = trimesh.Trimesh(
        vertices=output["vertices"].squeeze(0).cpu().numpy(),
        faces=model.faces.cpu().numpy(),
    )
    mesh.visual.material = mesh_material
    scene = trimesh.Scene([mesh])

    add_skeleton_to_scene(scene, model, output)
    scene.apply_transform(trimesh_scene_transform)

    # Convert to a notebook widget/HTML
    viewers.append(
        Markdown(
            f"#### '{rig}' rig ({model.bone_count} bones) / '{topology}' topology "
            f"({len(output['vertices'].squeeze(0))} vertices, {len(model.faces)} faces)"
        )
    )
    viewers.append(Markdown("  - " + ", ".join([label for label in model.bone_labels])))
    viewers.append(nb.scene_to_notebook(scene))


# Display all viewers
display(*viewers)
Warp 1.13.0 initialized:
   CUDA Toolkit 12.9, Driver 12.9
   Devices:
     "cpu"      : "x86_64"
     "cuda:0"   : "NVIDIA A100 80GB PCIe" (79 GiB, sm_80, mempool enabled)
   Kernel cache:
some_path
Module anny.skinning.warp_skinning d91b4e4 load on device 'cpu' took 4.25 ms  (cached)
Module anny.skinning.warp_skinning cf0fbd4 load on device 'cpu' took 2.34 ms  (cached)
Module anny.skinning.warp_skinning ba63a59 load on device 'cpu' took 3.19 ms  (cached)
Module anny.skinning.warp_skinning 234553d load on device 'cpu' took 2.22 ms  (cached)

'anny' rig (104 bones) / 'anny' topology (13718 vertices, 27420 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, toe1-1.L, toe1-2.L, toe2-1.L, toe2-2.L, toe2-3.L, toe3-1.L, toe3-2.L, toe3-3.L, toe4-1.L, toe4-2.L, toe4-3.L, toe5-1.L, toe5-2.L, toe5-3.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, toe1-1.R, toe1-2.R, toe2-1.R, toe2-2.R, toe2-3.R, toe3-1.R, toe3-2.R, toe3-3.R, toe4-1.R, toe4-2.R, toe4-3.R, toe5-1.R, toe5-2.R, toe5-3.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head, eye.L, eye.R

'mixamo' rig (52 bones) / 'anny' topology (13718 vertices, 27420 faces)¶

  • mixamorig:Hips, mixamorig:LeftUpLeg, mixamorig:LeftLeg, mixamorig:LeftFoot, mixamorig:LeftToeBase, mixamorig:RightUpLeg, mixamorig:RightLeg, mixamorig:RightFoot, mixamorig:RightToeBase, mixamorig:Spine, mixamorig:Spine1, mixamorig:Spine2, mixamorig:LeftShoulder, mixamorig:LeftArm, mixamorig:LeftForeArm, mixamorig:LeftHand, mixamorig:LeftHandIndex1, mixamorig:LeftHandIndex2, mixamorig:LeftHandIndex3, mixamorig:LeftHandMiddle1, mixamorig:LeftHandMiddle2, mixamorig:LeftHandMiddle3, mixamorig:LeftHandPinky1, mixamorig:LeftHandPinky2, mixamorig:LeftHandPinky3, mixamorig:LeftHandRing1, mixamorig:LeftHandRing2, mixamorig:LeftHandRing3, mixamorig:LeftHandThumb1, mixamorig:LeftHandThumb2, mixamorig:LeftHandThumb3, mixamorig:Neck, mixamorig:Head, mixamorig:RightShoulder, mixamorig:RightArm, mixamorig:RightForeArm, mixamorig:RightHand, mixamorig:RightHandIndex1, mixamorig:RightHandIndex2, mixamorig:RightHandIndex3, mixamorig:RightHandMiddle1, mixamorig:RightHandMiddle2, mixamorig:RightHandMiddle3, mixamorig:RightHandPinky1, mixamorig:RightHandPinky2, mixamorig:RightHandPinky3, mixamorig:RightHandRing1, mixamorig:RightHandRing2, mixamorig:RightHandRing3, mixamorig:RightHandThumb1, mixamorig:RightHandThumb2, mixamorig:RightHandThumb3

'anny' rig (104 bones) / 'smplx' topology (10475 vertices, 20908 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, toe1-1.L, toe1-2.L, toe2-1.L, toe2-2.L, toe2-3.L, toe3-1.L, toe3-2.L, toe3-3.L, toe4-1.L, toe4-2.L, toe4-3.L, toe5-1.L, toe5-2.L, toe5-3.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, toe1-1.R, toe1-2.R, toe2-1.R, toe2-2.R, toe2-3.R, toe3-1.R, toe3-2.R, toe3-3.R, toe4-1.R, toe4-2.R, toe4-3.R, toe5-1.R, toe5-2.R, toe5-3.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head, eye.L, eye.R

'anny' rig (104 bones) / 'soma' topology (18056 vertices, 36108 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, toe1-1.L, toe1-2.L, toe2-1.L, toe2-2.L, toe2-3.L, toe3-1.L, toe3-2.L, toe3-3.L, toe4-1.L, toe4-2.L, toe4-3.L, toe5-1.L, toe5-2.L, toe5-3.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, toe1-1.R, toe1-2.R, toe2-1.R, toe2-2.R, toe2-3.R, toe3-1.R, toe3-2.R, toe3-3.R, toe4-1.R, toe4-2.R, toe4-3.R, toe5-1.R, toe5-2.R, toe5-3.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head, eye.L, eye.R

'anny-notoes-noeyes' rig (74 bones) / 'makehuman' topology (19158 vertices, 13742 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head

Interoperability with SMPL-X¶

Beyond the SMPL-X topology shown above, Anny ships a first-class SMPLX model class (from anny.models.smpl import SMPLX) that wraps the official SMPL-X model. It follows the same construction and forward conventions as anny.Anny, but is driven by SMPL-X betas / expression / pose parameters rather than Anny phenotypes, making it a drop-in differentiable SMPL-X implementation.

Running it requires the SMPL-X model files, which are distributed separately (for non-commercial use only) at https://smpl-x.is.tue.mpg.de/. Download them and point the SMPLX_MODEL_PATH environment variable at the directory that contains them. The cell below skips itself automatically when that variable is not set.

Here we request the "anny" output topology (topology="anny") rather than the native SMPL-X mesh. The retopology is folded into the model at construction time, so a single forward pass directly outputs the body in Anny's common mesh — there is no separate conversion step. This is what makes the outputs interoperable: a SMPL-X-driven body and a phenotype-driven anny.Anny body live in the same topology, so per-vertex operations (texturing, correspondences, losses) transfer directly between them. Use topology="smplx" instead if you need the native SMPL-X mesh.

Note that some Anny vertices have no SMPL-X counterpart — for instance the internal mouth bag, which SMPL-X does not model — and are therefore ignored by this operation.

In [4]:
import os

smplx_model_path = os.environ.get("SMPLX_MODEL_PATH")
if not smplx_model_path or not os.path.isdir(smplx_model_path):
    display(
        Markdown(
            "> **SMPL-X example skipped.** Download the SMPL-X model files from "
            "[smpl-x.is.tue.mpg.de](https://smpl-x.is.tue.mpg.de/) and set the `SMPLX_MODEL_PATH` "
            "environment variable to the directory that contains them to run this cell."
        )
    )
else:
    from anny.models.smpl import SMPLX

    dtype = torch.float32
    # topology="anny" retopologizes the SMPL-X output onto Anny's common mesh (see above).
    model = SMPLX(smplx_model_path, gender="neutral", use_pca=True, topology="anny").to(
        dtype=dtype
    )

    # Random shape, expression and pose parameters, using SMPL-X's standard parameter
    # dimensions (10 shape betas, 10 expression coefficients, 21 body joints, 6 PCA hand
    # components).
    torch.manual_seed(0)
    pose_kwargs = dict(
        betas=0.5 * torch.randn((1, 10), dtype=dtype),
        expression=0.5 * torch.randn((1, 10), dtype=dtype),
        global_orient=0.1 * torch.randn((1, 3), dtype=dtype),
        body_pose=0.1 * torch.randn((1, 21 * 3), dtype=dtype),
        transl=torch.zeros((1, 3), dtype=dtype),
        jaw_pose=0.1 * torch.randn((1, 3), dtype=dtype),
        leye_pose=0.1 * torch.randn((1, 3), dtype=dtype),
        reye_pose=0.1 * torch.randn((1, 3), dtype=dtype),
        left_hand_pose=torch.randn((1, 6), dtype=dtype),
        right_hand_pose=torch.randn((1, 6), dtype=dtype),
    )

    output = model(**pose_kwargs)

    # Anny's SMPL-X mesh with its skeleton.
    mesh = trimesh.Trimesh(
        vertices=output["vertices"].squeeze(0).detach().cpu().numpy(),
        faces=model.faces.cpu().numpy(),
    )
    mesh.visual.material = mesh_material
    scene = trimesh.Scene([mesh])

    add_skeleton_to_scene(scene, model, output)
    scene.apply_transform(trimesh_scene_transform)

    display(
        Markdown(
            f"#### `anny.SMPLX` model ({model.bone_count} bones, "
            f"{len(output['vertices'].squeeze(0))} vertices)"
        )
    )
    display(nb.scene_to_notebook(scene))
Module anny.skinning.warp_skinning 3cbd7a9 load on device 'cpu' took 2.56 ms  (cached)

anny.SMPLX model (55 bones, 13188 vertices)¶