Upstream Snapshot 32a241c28f
Some checks failed
nightly / Test against latest dependencies (py3.10) (push) Has been cancelled
nightly / Test against latest dependencies (py3.13) (push) Has been cancelled
tests / tests (3.13, locked) (push) Has been cancelled
tests / tests (3.13, unlocked) (push) Has been cancelled
tests / pyright (3.10) (push) Has been cancelled
tests / lint-format (push) Has been cancelled
tests / tests (3.10, locked) (push) Has been cancelled
tests / tests (3.11, locked) (push) Has been cancelled
tests / tests (3.12, locked) (push) Has been cancelled
tests / pyright (3.11) (push) Has been cancelled
tests / pyright (3.12) (push) Has been cancelled
tests / pyright (3.13) (push) Has been cancelled
tests / ty-check (3.10) (push) Has been cancelled
tests / ty-check (3.11) (push) Has been cancelled
tests / ty-check (3.12) (push) Has been cancelled
tests / ty-check (3.13) (push) Has been cancelled
tests / stubs (push) Has been cancelled
tests / smoke-test (push) Has been cancelled
Docker / check_paths (push) Has been cancelled
docs / build (push) Has been cancelled
Docker / build (push) Has been cancelled
Import upstream snapshot c19f713c415a699a79d71cd96aa13c3104a05047
Upstream: https://github.com/michaelgillett/mjlab
Upstream-Commit: c19f713c415a699a79d71cd96aa13c3104a05047
Upstream-Branch: main
2026-08-28 15:42:17 +08:00

356 lines
12 KiB
ReStructuredText

.. _entity:
Entity
======
An ``Entity`` represents a physical object in the simulation: a robot, a
manipulated object, or a fixed fixture like a table. It is the central
abstraction in mjlab's physics layer.
A single ``Entity`` class covers all variants (contrast Isaac Lab, which
splits this across ``Articulation``, ``RigidObject``, and several other
subclasses of ``AssetBase``). Two orthogonal boolean properties classify
each instance:
**Base type.**
A *fixed-base* entity is welded to the world and has no free joint. A
*floating-base* entity has a free joint giving it 6-DOF movement.
**Articulation.**
An *articulated* entity has internal joints (revolute, prismatic, etc.).
A *non-articulated* entity has none beyond a possible free joint.
.. list-table::
:header-rows: 1
:widths: 30 25 15 15 15
* - Type
- Example
- ``is_fixed_base``
- ``is_articulated``
- ``is_actuated``
* - Fixed non-articulated
- Table, wall
- True
- False
- False
* - Fixed articulated
- Robot arm, door
- True
- True
- True/False
* - Floating non-articulated
- Box, ball, mug
- False
- False
- False
* - Floating articulated
- Humanoid, quadruped
- False
- True
- True/False
.. note::
mjlab automatically wraps every fixed-base entity in a
`mocap body <https://mujoco.readthedocs.io/en/stable/modeling.html#mocap-bodies>`_
so that each parallel environment can place the entity at a different
position. Without this wrapping, all fixed-base entities would be
welded to the world origin. The wrapping is transparent, but
**positioning only happens when a reset event runs**. You must
include a reset event such as ``reset_root_state_uniform`` in your
event config; without one, every fixed-base entity will remain at
the origin. See the :ref:`FAQ <faq>` for a full example. Mocap
entities can also be repositioned at runtime via
``entity.write_mocap_pose_to_sim()``.
Configuring an entity
---------------------
Every entity is described by an ``EntityCfg``. Only ``spec_fn`` is
required in practice; all other fields have sensible defaults. A passive
floating object needs nothing more than:
.. code-block:: python
from mjlab.entity import EntityCfg
cube_cfg = EntityCfg(spec_fn=get_cube_spec)
An actuated robot uses more of the interface:
.. code-block:: python
from mjlab.entity import EntityCfg, EntityArticulationInfoCfg
from mjlab.actuator import IdealPDActuatorCfg
robot_cfg = EntityCfg(
spec_fn=get_spec,
init_state=EntityCfg.InitialStateCfg(
pos=(0.0, 0.0, 0.8),
joint_pos={".*_hip_.*": 0.5, ".*": 0.0},
),
articulation=EntityArticulationInfoCfg(
actuators=(
IdealPDActuatorCfg(
target_names_expr=(".*",),
stiffness={".*": 50.0},
damping={".*": 5.0},
),
),
),
collisions=(my_collision_cfg,),
)
The following sections describe each field.
``spec_fn``
^^^^^^^^^^^
A callable that returns an ``mujoco.MjSpec``. The scene calls it during
composition, attaches the returned spec with a name prefix, and compiles
everything into a shared ``MjModel``.
For simple cases a lambda suffices:
.. code-block:: python
spec_fn = lambda: mujoco.MjSpec.from_file("robot.xml")
For anything more involved, use a regular function. MuJoCo resolves mesh
assets from disk automatically, so ``get_spec`` only needs to load the
XML:
.. code-block:: python
def get_spec() -> mujoco.MjSpec:
return mujoco.MjSpec.from_file(str(ROBOT_XML))
Because ``spec_fn`` is an arbitrary callable, you can perform any
`MjSpec edits <https://mujoco.readthedocs.io/en/stable/python.html#spec>`_
before returning: add bodies, change joint limits, swap materials,
or build the entire model programmatically without an XML file at all.
``init_state``
^^^^^^^^^^^^^^
Default root pose, root velocity, and joint positions/velocities. These
values are stored as a MuJoCo keyframe and used by reset events to
return the entity to its initial configuration.
``joint_pos`` and ``joint_vel`` are dicts mapping regex patterns to
values. Patterns are matched against joint names in order, so later
entries override earlier ones for any joint that matches both:
.. code-block:: python
init_state = EntityCfg.InitialStateCfg(
pos=(0.0, 0.0, 0.8), # root position
rot=(1.0, 0.0, 0.0, 0.0), # root quaternion (w, x, y, z)
joint_pos={
".*": 0.0, # all joints to zero
".*_hip_.*": 0.5, # then override hips to 0.5
},
)
Set ``joint_pos=None`` to use an existing keyframe from the MJCF model
instead of defining values here.
``articulation``
^^^^^^^^^^^^^^^^
Actuator configuration. Only needed for entities that have actuated
joints. Passive objects (boxes, tables, walls) can omit this field
entirely. See :ref:`actuators` for details on actuator types.
``soft_joint_pos_limit_factor`` (default 1.0) shrinks the joint range
used by soft-limit penalty rewards, so the policy is penalized before
reaching the physical hard stop. This does not modify the actual joint
limits in the MuJoCo model.
Spec editors
^^^^^^^^^^^^
The remaining fields are optional tuples of spec editor configs that
modify the ``MjSpec`` before compilation:
.. list-table::
:header-rows: 1
:widths: 20 80
* - Field
- Purpose
* - ``collisions``
- Replace the entity's collision structure: which geoms collide, and
with what contact parameters.
* - ``lights``
- Add lights to specific bodies.
* - ``cameras``
- Add cameras to specific bodies.
* - ``textures``
- Add procedural textures (checker, gradient, etc.).
* - ``materials``
- Add materials and optionally assign them to geoms by regex.
* - ``geoms``
- Patch attributes of existing geoms (visualization group, collision
attributes). Unset attributes are left untouched.
Each editor accepts regex patterns to target specific elements. For
example, a ``CollisionCfg`` with ``geom_names_expr=(".*_foot.*",)``
sets contact parameters only on foot geoms. See the asset zoo
(``mjlab.asset_zoo.robots``) for complete examples.
``geoms`` and ``collisions`` both write geom attributes but with
different semantics. A ``GeomCfg`` is a sparse *patch*: every attribute
defaults to ``None``, and only attributes you set are written. A
``CollisionCfg`` is a *policy*: ``contype``, ``conaffinity``,
``condim``, and ``priority`` are required and always written to every
matched geom, and non-matched geoms have collision disabled by default,
so the entity's contact behavior is fully determined by the config
regardless of the source XML. Collision configs are applied after geom
configs; mjlab warns if a ``GeomCfg`` sets a collision attribute that a
``CollisionCfg`` then overwrites.
Heterogeneous worlds
^^^^^^^^^^^^^^^^^^^^
For scenes that need different mesh assets in different parallel worlds
(for example, training a manipulation policy that generalizes across
object shapes), use ``VariantEntityCfg`` instead of ``EntityCfg``. Each
world is assigned a variant proportional to a configurable weight, and
mesh-dependent compiled constants (collision bounds, body inertials,
subtree mass) are stored as per-world arrays so domain randomization and
viewers stay consistent. See :ref:`heterogeneous_worlds`.
Subclassing Entity
^^^^^^^^^^^^^^^^^^
``Entity`` and ``EntityCfg`` can be subclassed for specialized behavior.
mjlab itself does this for terrain: ``TerrainEntity`` extends ``Entity``
with procedural terrain generation and per-environment origin
computation, and ``TerrainEntityCfg`` adds fields like
``terrain_type``, ``env_spacing``, and ``terrain_generator``. The same
pattern works for any domain-specific entity that needs logic beyond
what ``EntityCfg`` and spec editors provide.
Finding elements
^^^^^^^^^^^^^^^^
Entity provides ``find_*`` methods that accept regex patterns and return
matched element indices and names:
.. code-block:: python
ids, names = entity.find_joints((".*_hip_.*", ".*_knee_.*"))
ids, names = entity.find_geoms((".*foot.*",))
ids, names = entity.find_bodies((".*",))
Available methods: ``find_bodies()``, ``find_joints()``,
``find_geoms()``, ``find_sites()``, ``find_tendons()``.
These are used internally during scene construction and manager
initialization. In reward and observation terms, prefer
``SceneEntityCfg`` with name patterns as described below.
Reading runtime state
---------------------
Once entities are added to a ``SceneCfg`` and the environment is
constructed, their state is accessible through three interfaces at
decreasing levels of abstraction.
EntityData
^^^^^^^^^^
``entity.data`` is the primary interface for reward, observation, and
termination functions. It exposes kinematic state (poses, velocities, accelerations), actuator forces,
generalized forces, and derived body-frame quantities such as projected
gravity, all as PyTorch tensors with
shape ``(num_envs, ...)``. See :ref:`entity_data` for the full property
reference.
``SceneEntityCfg`` selects which entity and which elements within it a
term operates on. Regex patterns in ``joint_names``, ``body_names``,
``site_names``, etc. are resolved to integer indices once at manager
initialization, so there is no regex overhead at runtime:
.. code-block:: python
from mjlab.managers.scene_entity_config import SceneEntityCfg
def flat_orientation_l2(
env,
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
) -> torch.Tensor:
"""Penalize non-flat base orientation using projected gravity."""
asset = env.scene[asset_cfg.name]
return torch.sum(
torch.square(asset.data.projected_gravity_b[:, :2]), dim=1
)
``SceneEntityCfg`` also supports regex element selection through
``joint_names``, ``body_names``, ``site_names``, etc. The resolved
integer indices (e.g., ``asset_cfg.joint_ids``) make the runtime read a
single tensor slice with no regex overhead.
Sensors
^^^^^^^
Sensors are configured on the **scene**, not on individual entities.
A sensor can reference an entity element (e.g., a contact sensor on the
robot's feet, an accelerometer attached to a body site), but it can also
be independent of any entity. This is why sensors live in ``SceneCfg``
rather than ``EntityCfg``.
At runtime, sensors are accessed by name through ``env.scene``, the same
way entities are:
.. code-block:: python
def angular_momentum_penalty(env, sensor_name: str) -> torch.Tensor:
sensor = env.scene[sensor_name]
return torch.sum(torch.square(sensor.data), dim=-1)
Builtin sensors wrap MuJoCo sensor types (accelerometer, gyro, framepos,
subtreeangmom, etc.). ``ContactSensor``, ``RayCastSensor``, and
``CameraSensor`` provide higher-level abstractions for contact detection,
terrain scanning, and RGB-D rendering. See :ref:`sensors` for details.
Raw simulation data
^^^^^^^^^^^^^^^^^^^
For anything not covered by ``EntityData`` or sensors, the underlying
MuJoCo Warp arrays are accessible through ``env.sim.data`` and
``env.sim.model``. These expose the full ``mjData`` and ``mjModel``
fields as PyTorch tensors (zero-copy), indexed by global MuJoCo IDs
rather than per-entity IDs:
.. code-block:: python
# Global joint positions across all entities.
qpos = env.sim.data.qpos # (num_envs, nq)
# All body positions.
xpos = env.sim.data.xpos # (num_envs, nbody, 3)
# Model-level constants.
body_mass = env.sim.model.body_mass # (nbody,)
This is useful for low-level operations or when you need quantities
that span multiple entities.
.. note::
The main limitation of raw sim data is that you must manage global
MuJoCo indices yourself. In the future, we plan to support MuJoCo's
`bind <https://mujoco.readthedocs.io/en/latest/python.html#relationship-to-pymjcf-and-bind>`_
functionality, which will allow binding spec elements directly to
their corresponding data views without manual index bookkeeping.
.. toctree::
:maxdepth: 1
entity_data
per_world_mesh