Source code for protomotions.robot_configs.anatomic_soma

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, List, Tuple

from protomotions.robot_configs.base import (
    ControlConfig,
    ControlType,
    RobotAssetConfig,
    RobotConfig,
    SimulatorParams,
)
from protomotions.robot_configs.motor_classes import MotorClass
from protomotions.simulator.isaacgym.config import IsaacGymSimParams
from protomotions.simulator.isaaclab.config import (
    IsaacLabPhysXParams,
    IsaacLabSimParams,
)
from protomotions.simulator.mujoco.config import MujocoSimParams

# Motor classes, written out here so this robot does not move when another
# robot's config does. Provenance: Unitree G1 actuator classes (7520-14,
# 7520-22, 5020, 4010, and a 50 Nm ankle on the 5020 rotor) with 25% more
# capacity, the rotor armature and the torque limit scaling together like a
# stronger motor of the same design, and a 1.5x speed limit. The speed
# headroom exists because the anatomical corpus is retargeted under a 20 rad/s
# joint-speed budget: at 1x the slowest class (20 rad/s, on the knees and the
# hip roll hinges) would sit exactly at the reference peak and the hard PhysX velocity
# clamp would leave the policy no room to catch up after an error. These are
# simulation design choices, not measured human actuator properties.
CAPACITY = 1.25
SPEED = 1.5
WAIST = MotorClass(
    armature=CAPACITY * 0.010177520,
    effort_limit=CAPACITY * 88,
    velocity_limit=SPEED * 32,
)
HIP_KNEE = MotorClass(
    armature=CAPACITY * 0.025101925,
    effort_limit=CAPACITY * 139,
    velocity_limit=SPEED * 20,
)
ARM = MotorClass(
    armature=CAPACITY * 0.003609725,
    effort_limit=CAPACITY * 25,
    velocity_limit=SPEED * 37,
)
WRIST = MotorClass(
    armature=CAPACITY * 0.00425, effort_limit=CAPACITY * 5, velocity_limit=SPEED * 22
)
ANKLE = MotorClass(
    armature=CAPACITY * 0.003609725,
    effort_limit=CAPACITY * 50,
    velocity_limit=SPEED * 37,
)


[docs] @dataclass class AnatomicSomaRobotConfig(RobotConfig): """52 serial hinges, 23 semantic parts and 39 primitive colliders. The SOMA23 skeleton with its joints expanded into serial hinges on their own carrier links: 15 of the 22 joints get three hinges (pitch, roll and twist), the knees, elbows and toes one hinge each, and Neck2 a single yaw (15 * 3 + 7 = 52). That gives 53 bodies, of which the 30 intermediate links are non-colliding hinge carriers sitting on their semantic body. Joint ranges are engineering limits, not a medically validated human range-of-motion model. Motion files must be retargeted to this skeleton; SOMA23 packs (66 DOFs, 23 bodies) do not load. See mjcf/anatomic_soma.xml for the definition. """ semantic_forward_axis_xy: Tuple[float, float] = (1.0, 0.0) anchor_body_name: str = "Chest" common_naming_to_robot_body_names: Dict[str, List[str]] = field( default_factory=lambda: { "all_left_foot_bodies": ["LeftFoot", "LeftToeBase"], "all_right_foot_bodies": ["RightFoot", "RightToeBase"], "all_left_hand_bodies": ["LeftHand"], "all_right_hand_bodies": ["RightHand"], "head_body_name": ["Head"], "torso_body_name": ["Chest"], } ) trackable_bodies_subset: List[str] = field( default_factory=lambda: [ "Chest", "Head", "LeftFoot", "RightFoot", "LeftHand", "RightHand", ] ) default_root_height: float = 0.95 asset: RobotAssetConfig = field( default_factory=lambda: RobotAssetConfig( asset_file_name="mjcf/anatomic_soma.xml", self_collisions=True, max_linear_velocity=100.0, max_angular_velocity=100.0, angular_damping=0.0, linear_damping=0.0, ) ) control: ControlConfig = field( default_factory=lambda: ControlConfig( control_type=ControlType.BUILT_IN_PD, # Class assignments are not homologous axes: every torso axis uses # the waist-yaw class, and every wrist axis uses the weak wrist # class. Each ankle carries two motors (scale=2 doubles armature and # gains, not the speed limit). override_control_info={ "(Spine[12]|Chest)_.*": WAIST.control_info(), "(Left|Right)Leg_[xz]": WAIST.control_info(), "(Left|Right)(Leg_y|Shin_x)": HIP_KNEE.control_info(), "(Left|Right)Foot_.*": ANKLE.control_info(scale=2), "(Neck1|(Left|Right)(Shoulder|Arm|ForeArm|ToeBase))_.*": ARM.control_info(), "(Neck2|Head|(Left|Right)Hand)_.*": WRIST.control_info(), }, ) ) control_hz: int = 30 # Same blocks as SOMA23 except IsaacLab, which gets more contact-solver # resolution for the serial hinge chains: 240 Hz physics with 8 position # iterations. Each passed block replaces the humanoid template for its # backend; the decision rate stays 30 Hz everywhere. simulation_params: SimulatorParams = field( default_factory=lambda: SimulatorParams.humanoid( 30, isaacgym=IsaacGymSimParams(fps=60), isaaclab=IsaacLabSimParams( fps=240, physx=IsaacLabPhysXParams( num_position_iterations=8, num_velocity_iterations=4, max_depenetration_velocity=1, ), ), mujoco=MujocoSimParams(fps=960, integrator="implicitfast"), ) )