Skip to content

SmplHumanoid

SmplHumanoid is a rigid articulated humanoid model loaded from SMPL-compatible MJCF XML variants.

Setup

SmplHumanoid downloads its XML assets from the public abcamiletto/body-models Hugging Face repository. To prefetch and save the hosted path:

# Download the SmplHumanoid MJCF XML assets.
body-models download smpl-humanoid

The hosted folder includes license/provenance notes for the XML variants.

API

body_models.robots.smpl_humanoid.numpy.SmplHumanoid

SmplHumanoid(source='humenv')

Bases: RigidBodyModel

Rigid humanoid loaded from MJCF using the canonical 24-joint SMPL hierarchy.

METHOD DESCRIPTION
joint_index

Resolve a standard joint to this model's native joint index.

unpack_pose

Unpack a flattened pose [..., Q] into name -> [..., dof] arrays.

pack_pose

Pack name -> [..., dof] arrays into a flattened pose [..., Q].

to_qpos

Build full MuJoCo qpos as [root_xyz, root_wxyz, body_pose].

ATTRIBUTE DESCRIPTION
common_joints

Common anatomical joints mapped to this model's native joint names.

TYPE: Mapping[Joint, str]

num_actuated

Number of actuated pose coordinates.

TYPE: int

actuated_joint_slices

Consecutive scalar coordinate slices keyed by actuated joint name.

TYPE: Mapping[str, slice]

Source code in src/body_models/robots/smpl_humanoid/numpy.py
23
24
25
26
27
def __init__(
    self,
    source: Path | str = "humenv",
) -> None:
    self.weights = load_model_data(source)

common_joints property

common_joints

Common anatomical joints mapped to this model's native joint names.

num_actuated property

num_actuated

Number of actuated pose coordinates.

actuated_joint_slices property

actuated_joint_slices

Consecutive scalar coordinate slices keyed by actuated joint name.

joint_index

joint_index(joint)

Resolve a standard joint to this model's native joint index.

Source code in src/body_models/base.py
190
191
192
193
194
195
196
197
198
def joint_index(self, joint: Joint) -> int:
    """Resolve a standard joint to this model's native joint index."""
    if not isinstance(joint, Joint):
        raise TypeError("joint_index() expects a body_models.Joint; use joint_names.index(...) for native names.")
    try:
        native_name = self.common_joints[joint]
    except KeyError as exc:
        raise KeyError(f"{self.__class__.__name__} has no standard joint {joint.value!r}") from exc
    return self.joint_names.index(native_name)

unpack_pose

unpack_pose(pose)

Unpack a flattened pose [..., Q] into name -> [..., dof] arrays.

Source code in src/body_models/base.py
270
271
272
273
274
def unpack_pose(self, pose: Any) -> dict[str, Any]:
    """Unpack a flattened pose ``[..., Q]`` into ``name -> [..., dof]`` arrays."""
    if pose.shape[-1] != self.num_actuated:
        raise ValueError(f"pose must have shape [..., {self.num_actuated}], got {tuple(pose.shape)}")
    return {name: pose[..., joint_slice] for name, joint_slice in self.actuated_joint_slices.items()}

pack_pose

pack_pose(pose_by_joint)

Pack name -> [..., dof] arrays into a flattened pose [..., Q].

Source code in src/body_models/base.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def pack_pose(self, pose_by_joint: Mapping[str, Any]) -> Any:
    """Pack ``name -> [..., dof]`` arrays into a flattened pose ``[..., Q]``."""
    pieces = []
    expected_names = set(self.actuated_joint_slices)
    extra_names = set(pose_by_joint) - expected_names
    if extra_names:
        raise KeyError(f"Unknown actuated joint names: {sorted(extra_names)}")
    for name, joint_slice in self.actuated_joint_slices.items():
        if name not in pose_by_joint:
            raise KeyError(f"Missing actuated joint name: {name!r}")
        value = pose_by_joint[name]
        dof = joint_slice.stop - joint_slice.start
        if value.shape[-1] != dof:
            raise ValueError(f"{name!r} must have shape [..., {dof}], got {tuple(value.shape)}")
        pieces.append(value)
    return get_namespace(*pieces).concat(pieces, axis=-1)

to_qpos

to_qpos(
    body_pose,
    global_translation=None,
    *,
    global_rotation=None,
    clamp_to_limits=False,
)

Build full MuJoCo qpos as [root_xyz, root_wxyz, body_pose].

body_pose is the model's flattened scalar coordinate vector [..., Q]. The root prefix is converted from the model coordinate frame to MuJoCo's coordinate frame.

Source code in src/body_models/base.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
def to_qpos(
    self,
    body_pose: Any,
    global_translation: Any | None = None,
    *,
    global_rotation: Any | None = None,
    clamp_to_limits: bool = False,
) -> Any:
    """Build full MuJoCo ``qpos`` as ``[root_xyz, root_wxyz, body_pose]``.

    ``body_pose`` is the model's flattened scalar coordinate vector ``[..., Q]``.
    The root prefix is converted from the model coordinate frame to MuJoCo's
    coordinate frame.
    """
    if body_pose.shape[-1] != self.num_actuated:
        raise ValueError(f"body_pose must have shape [..., {self.num_actuated}], got {tuple(body_pose.shape)}")

    xp = get_namespace(body_pose)
    batch_shape = tuple(body_pose.shape[:-1])
    if global_translation is None:
        global_translation = zeros_as(body_pose, shape=(*batch_shape, 3), xp=xp)
    if global_rotation is None:
        root_ref = zeros_as(body_pose, shape=(*batch_shape, 3), xp=xp)
        root_rot = eye_as(root_ref, batch_dims=batch_shape, xp=xp)
    else:
        root_rot = SO3.convert(global_rotation, src="axis_angle", dst="rotmat", xp=xp)

    coord = xp.asarray(self.mujoco_to_model, dtype=body_pose.dtype)
    model_to_mujoco = coord.mT if hasattr(coord, "mT") else xp.swapaxes(coord, -1, -2)
    root_t = xp.squeeze(model_to_mujoco @ global_translation[..., None], axis=-1)
    root_rot_mujoco = model_to_mujoco @ root_rot @ coord
    root_quat = SO3.conversions.from_rotmat_to_quat(root_rot_mujoco, convention="wxyz", xp=xp)

    if clamp_to_limits:
        limits = xp.asarray(self.actuated_joint_limits, dtype=body_pose.dtype)
        body_pose = xp.clip(body_pose, limits[:, 0], limits[:, 1])
    return xp.concat([root_t, root_quat, body_pose], axis=-1)