Source code for wbc_mjlab.extension

"""Public API for external WBC robot + task extensions."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from wbc_mjlab.motion.robot_assets import RobotMotionSpec
from wbc_mjlab.robots.symmetry import RobotSymmetryConfig
from wbc_mjlab.tasks.config import WbcTaskConfig

EnvCfgBuilder = Callable[..., Any]
RlCfgBuilder = Callable[[], Any]


[docs] @dataclass(frozen=True) class WbcRobotSpec: """Robot wiring registered into wbc-mjlab from an external package.""" robot_id: str """Canonical robot id (e.g. ``\"h2\"``).""" make_env_cfg: EnvCfgBuilder """Builder returning a ``ManagerBasedRlEnvCfg``.""" make_rl_cfg: RlCfgBuilder """Builder returning the RL runner config.""" motion_spec: RobotMotionSpec | None = None """Optional FK / debias metadata for ``wbc-mjlab-data-to-npz``.""" symmetry_config: RobotSymmetryConfig | None = None """Optional :class:`~wbc_mjlab.robots.symmetry.RobotSymmetryConfig` for ``--mirror``.""" aliases: tuple[str, ...] = () """Alternate ids accepted by CLIs.""" project_root: Path | str | None = None """Extension package root (for ``data/<robot_id>/`` resolution).""" mjcf_path: Path | str | None = None """Optional MJCF path when not set on ``motion_spec``."""
[docs] def register_robot(spec: WbcRobotSpec) -> None: """Register robot id, env/RL builders, and optional motion-conversion metadata. Prefer :func:`register_wbc_extension` when also registering tasks. """ from wbc_mjlab.motion.robot_assets import register_robot_motion_spec from wbc_mjlab.robots.env import register_robot_builders from wbc_mjlab.robots.ids import register_robot_id rid = spec.robot_id.strip().lower() register_robot_id(rid, aliases=spec.aliases) register_robot_builders(rid, spec.make_env_cfg, spec.make_rl_cfg) if spec.motion_spec is not None: motion_spec = spec.motion_spec if spec.mjcf_path is not None and motion_spec.mjcf_path is None: from dataclasses import replace motion_spec = replace(motion_spec, mjcf_path=Path(spec.mjcf_path)) register_robot_motion_spec(rid, motion_spec) elif spec.mjcf_path is not None: from wbc_mjlab.motion.robot_assets import register_robot_mjcf_path register_robot_mjcf_path(rid, spec.mjcf_path) if spec.symmetry_config is not None: from wbc_mjlab.robots.symmetry import register_robot_symmetry_config register_robot_symmetry_config(rid, spec.symmetry_config) if spec.project_root is not None: from wbc_mjlab.data_paths import register_project_root register_project_root(spec.project_root)
[docs] def register_wbc_extension( robot: WbcRobotSpec, tasks: WbcTaskConfig | tuple[WbcTaskConfig, ...], ) -> None: """Register a robot and its WBC task table in one call. Call at extension import time (e.g. from the package ``__init__`` or the ``mjlab.tasks`` entry point) so ``wbc-mjlab-list-envs`` / train / play discover the robot. """ register_robot(robot) from wbc_mjlab.tasks import register_wbc_tasks register_wbc_tasks(tasks)
__all__ = [ "EnvCfgBuilder", "RlCfgBuilder", "WbcRobotSpec", "register_robot", "register_wbc_extension", ]