Skip to content

Entities

Entities are Eden's wrappers over Genesis entities: robots, rigid objects, deformables, terrain, cameras and sensors. An env creates one per entry in its scene options and exposes them as env.entities["<name>"]. This page is the class reference; the entities guide covers pose frames, the two DOF index spaces, armature, contacts and the other behaviour you need before writing a term against an entity.

from eden.entities.rigid import RigidEntity

The class an entry gets is chosen from its options (rigid, particle, FEM, terrain, Mochi). Setters take envs_idx= to touch a subset of environments, and quaternions are wxyz everywhere.

Base and rigid entities

Entity

Entity(env: EnvBase, options: EntityOptions)

Bases: ConfigurableMixin[EntityOptions]

Entity class for Eden, wrapping Genesis' Entity class.

PARAMETER DESCRIPTION
default_root_pos

The default position of the entity's root link.

default_root_quat

The default orientation of the entity's root link (w, x, y, z).

Source code in eden/entities/base.py
def __init__(self, env: EnvBase, options: EntityOptions):
    self._options = options
    self._env = env
    self._is_attached = False
    self._attachment_parent: Entity | None = None
    self.is_heterogeneous = isinstance(self._options, GroupedEntityOptions)
    # Heterogeneous only: each env's own variant default pose, (num_envs, 3) / (num_envs, 4).
    self._variant_default_root_pos: torch.Tensor | None = None
    self._variant_default_root_quat: torch.Tensor | None = None
    # Heterogeneous only: the variant each env holds, (num_envs,) long, and the first env holding each variant
    # present (all of them, unless there are fewer envs than variants).
    self._env_variant_idx: torch.Tensor | None = None
    self._variant_first_envs: list[int] = []
    # Heterogeneous only: each present variant's value for a per-variant field they differ in; a field
    # they all resolve alike is mirrored as that one value instead. And each variant's material, present or
    # not, so the checks on it do not depend on the env count.
    self._variant_fields: dict[str, list] = {}
    self._variant_materials: list[MaterialLike] | None = None

    # Create morph from options if not already set
    if not self.is_heterogeneous:
        self._create_morph_from_options()
    else:
        self._create_grouped_morphs_from_options()

    # Mirror options onto the instance. Entity does not delegate __init__ to
    # ConfigurableMixin -- see mirror_options_onto's docstring for why, and for the
    # aliasing this protects against. Unlike the mixin, Entity sets no ``_uid``: its
    # ``uid`` is the wrapped Genesis entity's, so there is nothing to seed here.
    # SupportSurfaceMixin's attributes need no skip list -- get_parameter_names() walks
    # only ConfigurableMixin bases, so they never show up here.
    mirror_options_onto(self, self._options)
    if self.is_heterogeneous:
        self._resolve_variant_fields()

    # forward_vec is derived from the options' front direction (single source of truth);
    # entities without a front (e.g. primitives) keep the class default.
    self.forward_vec = getattr(self._options, "front", self.forward_vec)
    self.forward_vec = torch.tensor(self.forward_vec, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_pos = torch.tensor(self.default_root_pos, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_quat = torch.tensor(
        self.default_root_quat,
        dtype=gs.tc_float,
        device=self.device,
    ).unsqueeze(0)

Attributes

morph instance-attribute

morph: MorphLike

material class-attribute instance-attribute

material: MaterialLike | None = None

surface class-attribute instance-attribute

surface: SurfaceLike | None = None

visualize_contact class-attribute instance-attribute

visualize_contact: bool = False

vis_mode class-attribute instance-attribute

vis_mode: VisMode = 'visual'

is_fixed_base class-attribute instance-attribute

is_fixed_base: bool = False

is_heterogeneous instance-attribute

is_heterogeneous = isinstance(self._options, GroupedEntityOptions)

forward_vec class-attribute instance-attribute

forward_vec: Vec3FType = torch.tensor(self.forward_vec, dtype=gs.tc_float, device=self.device).unsqueeze(0)

default_root_pos class-attribute instance-attribute

default_root_pos: Vec3FType = torch.tensor(self.default_root_pos, dtype=gs.tc_float, device=self.device).unsqueeze(0)

default_root_quat class-attribute instance-attribute

default_root_quat: UnitVec4FType = torch.tensor(self.default_root_quat, dtype=gs.tc_float, device=self.device).unsqueeze(0)

num_variants property

num_variants: int

How many variants a heterogeneous (GroupedEntityOptions) entity was built from.

env_variant_idx property

env_variant_idx: Tensor

The variant each env holds, (num_envs,) long, indexing GroupedEntityOptions.grouped_entities.

Fixed at build: a reset re-poses an env's variant but never swaps it. This is the mapping Genesis itself distributes the variants by (contiguous, balanced env blocks), not an inference from the built geometry.

name property

name: str

The entity's registered name.

entity property

entity

The underlying Genesis entity this wrapper delegates to.

metadata property

metadata

User-defined metadata attached to the entity's options.

solver property

solver

The Genesis solver that owns this entity.

env property

env

The environment this entity belongs to.

idx property

idx

The entity's index within its solver.

uid property

uid

The entity's globally unique identifier.

is_built property

is_built: bool

Whether the owning scene has been built.

device property

device: device

The torch device the entity's tensors live on.

is_attached property

is_attached: bool

Whether this entity is welded onto a link of another entity.

Genesis attach merges the pair into one kinematic tree and removes the child's root joint, so an attached entity has no base DOFs of its own even when is_fixed_base is False — see :attr:has_immobile_base. (Mirrors the is_attached property on Camera / Sensor.)

attachment_parent property

attachment_parent: Entity | None

Entity whose link carries this welded entity, or None when it is not attached.

has_immobile_base property

has_immobile_base: bool

Whether the entity's base link carries no DOFs of its own.

True for a fixed-base entity and for an attached one (its base link is carried by the parent link). Prefer this over is_fixed_base wherever the question is "are there base DOFs to read or write", since is_fixed_base alone reports False for a welded gripper / dexterous hand.

fixed property

fixed: bool

Whether the entity has a fixed base (cannot move freely).

Methods:

get_default_root_pose

get_default_root_pose() -> tuple[Tensor, Tensor]

Get the default root pose for all environments, (num_envs, 3) / (num_envs, 4).

For a heterogeneous entity, each env's variant default pose is composed onto the GroupedEntityOptions default root pose as an additional offset.

Source code in eden/entities/base.py
def get_default_root_pose(self) -> tuple[torch.Tensor, torch.Tensor]:
    """Get the default root pose for all environments, ``(num_envs, 3)`` / ``(num_envs, 4)``.

    For a heterogeneous entity, each env's variant default pose is composed onto the
    GroupedEntityOptions default root pose as an additional offset.
    """
    default_pos = self.default_root_pos.repeat(self._env.num_envs, 1)
    default_quat = self.default_root_quat.repeat(self._env.num_envs, 1)
    if not self.is_heterogeneous:
        return default_pos, default_quat
    return (
        default_pos + transform_by_quat(self._variant_default_root_pos, default_quat),
        gu.transform_quat_by_quat(self._variant_default_root_quat, default_quat),
    )

pre_build

pre_build() -> None

Add the entity to the Genesis scene before the scene is built.

Genesis' asset-level warnings repeat once per mesh (a G1 has nine non-watertight collision meshes), so the load collapses identical ones into a single counted line.

Source code in eden/entities/base.py
def pre_build(self) -> None:
    """Add the entity to the Genesis scene before the scene is built.

    Genesis' asset-level warnings repeat once per mesh (a G1 has nine non-watertight
    collision meshes), so the load collapses identical ones into a single counted line.
    """
    # Genesis falls back to an auto-generated name when the Eden scene key is absent
    # (entities constructed outside EnvBase).
    name = getattr(self, "_name", None)
    subject = f"entity {name!r}" if name is not None else f"a {type(self).__name__}"
    with collapse_repeated_genesis_warnings(subject):
        material = self._parse_material()
        if isinstance(material, mochi_materials.Base):
            # Genesis ships no MochiSolver: Eden hosts one (eden.engine.solvers.mochi), installed on the scene's
            # first Mochi entity, after which the scene's `add_entity` takes Mochi materials.
            self._env._install_mochi_solver(subject)
        self._entity: GenesisEntity = self._env.scene.add_entity(
            self.morph,
            material=material,
            surface=self._parse_surface(),
            visualize_contact=self.visualize_contact,
            vis_mode=self.vis_mode,
            name=name,
        )

post_build

post_build() -> None

Finalize the entity after the scene is built (no-op for the base class).

Source code in eden/entities/base.py
def post_build(self) -> None:
    """Finalize the entity after the scene is built (no-op for the base class)."""
    pass

get_pos

get_pos(envs_idx=None, *, relative: bool = False)

Return the position of the entity's base link in world frame.

PARAMETER DESCRIPTION
envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Must be False for this entity kind (see :meth:_reject_relative); only :class:~eden.entities.rigid.RigidEntity has a second frame to report.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
pos

The base-link position.

TYPE: (Tensor, shape(3) or (n_envs, 3))

Source code in eden/entities/base.py
def get_pos(self, envs_idx=None, *, relative: bool = False):
    """Return the position of the entity's base link in world frame.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Must be False for this entity kind (see :meth:`_reject_relative`); only
        :class:`~eden.entities.rigid.RigidEntity` has a second frame to report.

    Returns
    -------
    pos : torch.Tensor, shape (3,) or (n_envs, 3)
        The base-link position.
    """
    self._reject_relative(relative, "get_pos")
    return self._entity.get_pos(envs_idx=envs_idx, relative=False)

get_quat

get_quat(envs_idx=None, *, relative: bool = False)

Return the quaternion of the entity's base link in world frame.

PARAMETER DESCRIPTION
envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Must be False for this entity kind (see :meth:_reject_relative); only :class:~eden.entities.rigid.RigidEntity has a second frame to report.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
quat

The base-link quaternion in (w, x, y, z) format.

TYPE: (Tensor, shape(4) or (n_envs, 4))

Source code in eden/entities/base.py
def get_quat(self, envs_idx=None, *, relative: bool = False):
    """Return the quaternion of the entity's base link in world frame.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Must be False for this entity kind (see :meth:`_reject_relative`); only
        :class:`~eden.entities.rigid.RigidEntity` has a second frame to report.

    Returns
    -------
    quat : torch.Tensor, shape (4,) or (n_envs, 4)
        The base-link quaternion in ``(w, x, y, z)`` format.
    """
    self._reject_relative(relative, "get_quat")
    return self._entity.get_quat(envs_idx=envs_idx, relative=False)

get_heading

get_heading(envs_idx=None)

Return the heading of the entity's base link in world frame.

Source code in eden/entities/base.py
def get_heading(self, envs_idx=None):
    """Return the heading of the entity's base link in world frame."""
    return heading_from_quat(self.get_quat(envs_idx=envs_idx), self.forward_vec)

get_T

get_T(envs_idx=None)

Return the entity's base-link pose as a 4x4 homogeneous transform matrix.

PARAMETER DESCRIPTION
envs_idx

The indices of the environments. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
transform

The base-link homogeneous transform.

TYPE: (Tensor, shape(4, 4) or (n_envs, 4, 4))

Source code in eden/entities/base.py
def get_T(self, envs_idx=None):
    """Return the entity's base-link pose as a 4x4 homogeneous transform matrix.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments are considered. Defaults to None.

    Returns
    -------
    transform : torch.Tensor, shape (4, 4) or (n_envs, 4, 4)
        The base-link homogeneous transform.
    """
    pos = self.get_pos(envs_idx=envs_idx)
    quat = self.get_quat(envs_idx=envs_idx)
    return gu.trans_quat_to_T(pos, quat)

get_vel

get_vel(envs_idx=None, *, frame: Literal['world', 'body'] = 'world')

Return the linear velocity of the entity's base link.

PARAMETER DESCRIPTION
envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

frame

Coordinate frame for the returned linear velocity. Valid options: - "world": linear velocity expressed in world coordinates (default) - "body": linear velocity expressed in the body (base link) frame

TYPE: Literal['world', 'body'] DEFAULT: 'world'

Source code in eden/entities/base.py
def get_vel(self, envs_idx=None, *, frame: Literal["world", "body"] = "world"):
    """Return the linear velocity of the entity's base link.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    frame : Literal["world", "body"], optional
        Coordinate frame for the returned linear velocity. Valid options:
        - "world": linear velocity expressed in world coordinates (default)
        - "body": linear velocity expressed in the body (base link) frame
    """
    vel_w = self._entity.get_vel(envs_idx=envs_idx, relative=False)
    if frame == "world":
        return vel_w
    elif frame == "body":
        quat_wb = self.get_quat(envs_idx=envs_idx)
        return inv_transform_by_quat(vel_w, quat_wb)
    else:
        raise ValueError(f"Invalid frame '{frame}'. Expected 'world' or 'body'.")

get_ang

get_ang(envs_idx=None, *, frame: Literal['world', 'body'] = 'world')

Return the angular velocity of the entity's base link.

PARAMETER DESCRIPTION
envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

frame

Coordinate frame for the returned angular velocity. Valid options: - "world": angular velocity expressed in world coordinates (default) - "body": angular velocity expressed in the body (base link) frame

TYPE: Literal['world', 'body'] DEFAULT: 'world'

Source code in eden/entities/base.py
def get_ang(self, envs_idx=None, *, frame: Literal["world", "body"] = "world"):
    """Return the angular velocity of the entity's base link.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    frame : Literal["world", "body"], optional
        Coordinate frame for the returned angular velocity. Valid options:
        - "world": angular velocity expressed in world coordinates (default)
        - "body": angular velocity expressed in the body (base link) frame
    """
    ang_w = self._entity.get_ang(envs_idx=envs_idx)

    if frame == "world":
        return ang_w
    elif frame == "body":
        quat_wb = self.get_quat(envs_idx=envs_idx)
        return inv_transform_by_quat(ang_w, quat_wb)
    else:
        raise ValueError(f"Invalid frame '{frame}'. Expected 'world' or 'body'.")

set_pos

set_pos(pos, envs_idx=None) -> None

Require concrete entity classes to define position-setter semantics.

Source code in eden/entities/base.py
def set_pos(self, pos, envs_idx=None) -> None:
    """Require concrete entity classes to define position-setter semantics."""
    raise NotImplementedError("set_pos must be implemented by a concrete Entity subclass.")

set_quat

set_quat(quat, envs_idx=None) -> None

Require concrete entity classes to define orientation-setter semantics.

Source code in eden/entities/base.py
def set_quat(self, quat, envs_idx=None) -> None:
    """Require concrete entity classes to define orientation-setter semantics."""
    raise NotImplementedError("set_quat must be implemented by a concrete Entity subclass.")

attach_to

attach_to(entity: Entity, link_name: str, attach_offset: Vec3FType | None = None, attach_quat: UnitVec4FType | None = None) -> None

Reject attachment for entity kinds that cannot be welded to a parent link.

Overridden by :class:~eden.entities.rigid.RigidEntity (a weld) and :class:~eden.entities.mochi.MochiSoftEntity (a soft body tied to the link). Declared here — taking the mounting-transform kwargs that EnvBase always passes — so attachments_dict naming a deformable/particle child fails with an explanatory error instead of a bare AttributeError/TypeError.

Source code in eden/entities/base.py
def attach_to(
    self,
    entity: Entity,
    link_name: str,
    attach_offset: Vec3FType | None = None,
    attach_quat: UnitVec4FType | None = None,
) -> None:
    """Reject attachment for entity kinds that cannot be welded to a parent link.

    Overridden by :class:`~eden.entities.rigid.RigidEntity` (a weld) and
    :class:`~eden.entities.mochi.MochiSoftEntity` (a soft body tied to the link). Declared
    here — taking the mounting-transform kwargs that ``EnvBase`` always passes — so
    ``attachments_dict`` naming a deformable/particle child fails with an explanatory error
    instead of a bare ``AttributeError``/``TypeError``.
    """
    raise NotImplementedError(
        f"{type(self).__name__} '{self.name}' does not support attach_to (requested parent "
        f"'{entity.name}', link '{link_name}'). Only RigidEntity supports parent-child "
        "attachments (and MochiSoftEntity a mount on a Mochi link); remove this entity from `attachments_dict`."
    )

get_mass

get_mass()

Return the total mass of the entity, in kg.

For rigid entities this sums the links that are not fixed to the world, and returns a 0-dim tensor — or a (num_envs,) tensor when link info is batched (EnvOptions.batch_links_info or a heterogeneous entity).

Source code in eden/entities/base.py
def get_mass(self):
    """Return the total mass of the entity, in kg.

    For rigid entities this sums the links that are not fixed to the world, and returns
    a 0-dim tensor — or a ``(num_envs,)`` tensor when link info is batched
    (``EnvOptions.batch_links_info`` or a heterogeneous entity).
    """
    return self._entity.get_mass()

set_mass

set_mass(mass)

Set the total mass of the entity.

PARAMETER DESCRIPTION
mass

The target total mass.

TYPE: float

Source code in eden/entities/base.py
def set_mass(self, mass):
    """Set the total mass of the entity.

    Parameters
    ----------
    mass : float
        The target total mass.
    """
    self._entity.set_mass(mass)

RigidEntity

RigidEntity(env: EnvBase, options: EntityOptions)

Bases: Entity, SupportSurfaceMixin, DofDefaultsMixin

Entity subclass for rigid and kinematic materials.

Provides DOF/link/geom access, FK/IK, Jacobians, contacts, and support-surface utilities on top of the base :class:Entity.

Notes

DOF ordering. Every *_dofs_* accessor below indexes DOFs in dofs_name order — the entity's configured dofs_name list, a pattern expanding in Genesis' joint order, not the asset's declaration order — because the default dofs_idx_local is :attr:dofs_idx_local, which is name-resolved in that order. :meth:find_named_dofs_idx_local returns indices in Genesis' kinematic-tree order instead: that is what Genesis-level APIs want, but it is not a column index into :meth:get_jacobian, which already permutes its columns back to dofs_name order. Passing an explicit dofs_idx_local returns values in the order you supplied.

DOF defaults. The configured per-DOF tensors (default_dofs_*, soft_dofs_pos_limits) are laid out in dofs_name order too, which no find_named_dofs_idx_local index addresses. Read them by name with get_default_dofs_* / :meth:get_soft_dofs_pos_limits; to slice or write one, resolve once with :meth:find_named_dofs, whose selection carries the columns and the matching dofs_idx_local — see :class:~eden.entities.dofs_default_mixin.DofDefaultsMixin.

Quaternions are (w, x, y, z).

Batching. Shapes below are written with the leading n_envs axis they carry; where an accessor takes envs_idx (int / slice / index tensor / bool mask), that axis is narrowed to the selection. (:meth:get_mass is the exception that proves the rule — it reports the entity's total mass, which is not a per-DOF or per-link quantity at all.)

For the info getters that axis is Eden's contract, not Genesis'. Genesis splits per-DOF data in two: state (position, velocity, force) is always env-batched, while info — the nine model-parameter reads :meth:get_dofs_kp, :meth:get_dofs_kd, :meth:get_dofs_stiffness, :meth:get_dofs_damping, :meth:get_dofs_armature, :meth:get_dofs_frictionloss, :meth:get_dofs_invweight, :meth:get_dofs_limit and :meth:get_dofs_force_range — is batched only when EnvOptions.batch_dofs_info is set, and rejects envs_idx outright when it is not. Since the shape a caller got would otherwise depend on the task config (and Eden ships tasks on both settings), the info getters normalize: when DOF info is unbatched every environment shares the one stored row, so broadcasting it over environments is exact.

Note that EnvOptions.batch_dofs_info only reaches the rigid solver. KinematicSolver keeps unbatched DOF info unless it holds a heterogeneous entity, so a kinematic entity's info reads are otherwise broadcast however the env is configured. Either solver batches by itself once it holds one (:attr:has_batched_dofs_info).

Source code in eden/entities/base.py
def __init__(self, env: EnvBase, options: EntityOptions):
    self._options = options
    self._env = env
    self._is_attached = False
    self._attachment_parent: Entity | None = None
    self.is_heterogeneous = isinstance(self._options, GroupedEntityOptions)
    # Heterogeneous only: each env's own variant default pose, (num_envs, 3) / (num_envs, 4).
    self._variant_default_root_pos: torch.Tensor | None = None
    self._variant_default_root_quat: torch.Tensor | None = None
    # Heterogeneous only: the variant each env holds, (num_envs,) long, and the first env holding each variant
    # present (all of them, unless there are fewer envs than variants).
    self._env_variant_idx: torch.Tensor | None = None
    self._variant_first_envs: list[int] = []
    # Heterogeneous only: each present variant's value for a per-variant field they differ in; a field
    # they all resolve alike is mirrored as that one value instead. And each variant's material, present or
    # not, so the checks on it do not depend on the env count.
    self._variant_fields: dict[str, list] = {}
    self._variant_materials: list[MaterialLike] | None = None

    # Create morph from options if not already set
    if not self.is_heterogeneous:
        self._create_morph_from_options()
    else:
        self._create_grouped_morphs_from_options()

    # Mirror options onto the instance. Entity does not delegate __init__ to
    # ConfigurableMixin -- see mirror_options_onto's docstring for why, and for the
    # aliasing this protects against. Unlike the mixin, Entity sets no ``_uid``: its
    # ``uid`` is the wrapped Genesis entity's, so there is nothing to seed here.
    # SupportSurfaceMixin's attributes need no skip list -- get_parameter_names() walks
    # only ConfigurableMixin bases, so they never show up here.
    mirror_options_onto(self, self._options)
    if self.is_heterogeneous:
        self._resolve_variant_fields()

    # forward_vec is derived from the options' front direction (single source of truth);
    # entities without a front (e.g. primitives) keep the class default.
    self.forward_vec = getattr(self._options, "front", self.forward_vec)
    self.forward_vec = torch.tensor(self.forward_vec, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_pos = torch.tensor(self.default_root_pos, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_quat = torch.tensor(
        self.default_root_quat,
        dtype=gs.tc_float,
        device=self.device,
    ).unsqueeze(0)

Attributes

dofs_name class-attribute instance-attribute

dofs_name: list[str] = []

default_dofs_pos class-attribute instance-attribute

default_dofs_pos: dict[str, float] = {}

default_dofs_vel class-attribute instance-attribute

default_dofs_vel: dict[str, float] = {}

default_dofs_stiffness class-attribute instance-attribute

default_dofs_stiffness: dict[str, float] = {}

default_dofs_damping class-attribute instance-attribute

default_dofs_damping: dict[str, float] = {}

default_dofs_armature class-attribute instance-attribute

default_dofs_armature: dict[str, float] = {}

default_dofs_frictionloss class-attribute instance-attribute

default_dofs_frictionloss: dict[str, float] = {}

default_dofs_spring_ref class-attribute instance-attribute

default_dofs_spring_ref: dict[str, float] = {}

default_dofs_kp class-attribute instance-attribute

default_dofs_kp: dict[str, float] = {}

default_dofs_kd class-attribute instance-attribute

default_dofs_kd: dict[str, float] = {}

default_dofs_pos_limits class-attribute instance-attribute

default_dofs_pos_limits: dict[str, Vec2FType] = {}

soft_dofs_pos_limits class-attribute instance-attribute

soft_dofs_pos_limits: dict[str, Vec2FType] = {}

default_dofs_force_limits class-attribute instance-attribute

default_dofs_force_limits: dict[str, float] = {}

default_equalities_sol_params class-attribute instance-attribute

default_equalities_sol_params: tuple[float, ...] | None = None

default_geoms_sol_params class-attribute instance-attribute

default_geoms_sol_params: dict[str, tuple[float, ...]] = {}

default_geoms_friction class-attribute instance-attribute

default_geoms_friction: dict[str, float] = {}

default_geoms_friction_torsional class-attribute instance-attribute

default_geoms_friction_torsional: dict[str, float] = {}

default_geoms_friction_rolling class-attribute instance-attribute

default_geoms_friction_rolling: dict[str, float] = {}

is_articulated class-attribute instance-attribute

is_articulated: bool = False

is_actuated class-attribute instance-attribute

is_actuated: bool = False

is_support_enabled class-attribute instance-attribute

is_support_enabled: bool = False

support_sample_method class-attribute instance-attribute

support_sample_method: Literal['uniform', 'grid'] = 'uniform'

support_minimum_clearance class-attribute instance-attribute

support_minimum_clearance: float = 0.05

support_shrink class-attribute instance-attribute

support_shrink: float = 0.1

support_num_sample_points class-attribute instance-attribute

support_num_sample_points: int = 300

support_grid_size class-attribute instance-attribute

support_grid_size: float = 0.01
support_links_name: list[str] = []

fixed property

fixed: bool

dofs_spec property

dofs_spec

is_kinematic property

is_kinematic: bool

Whether this entity is kinematic: not simulated, and owning no collision geometry.

A kinematic material puts the entity in Genesis' kinematic solver, which builds visual geoms only — KinematicEntity exposes vgeoms and has no geoms attribute at all. Anything that reaches for collision geometry has nothing to work with on such an entity.

root_qpos_is_authored_pose cached property

root_qpos_is_authored_pose: bool

Whether the free base's 7 root coordinates are its authored pose (get_pos/get_quat(relative=True)).

They are whenever Genesis moves the root link by no morph offset or inertial alignment, so a relative=True pose can be written straight into qpos with :meth:set_qpos — Genesis' bool-mask path, no GPU->CPU sync — where set_pos / set_quat would go through the offset composition. Read once, comparing Genesis' own relative and solver-frame getters (one sync, on first use). False for an entity with no free base, and under IPC coupling, where Genesis refuses a qpos write to an IPC-articulated entity that its pose setters may still take.

dofs_order property

dofs_order: dict[str, int]

Get the mapping from dofs name to its local index.

joints_name property

joints_name: list[str]

Every joint's name, in Genesis' joint order (not the asset's): the free root and any fixed or passive joints as well as the controlled ones in dofs_name.

joints property

joints: list[RigidJoint]

Every Genesis joint of this entity, in declaration order: free, fixed and DOF-carrying alike.

Each joint's name, type, n_dofs, dofs_idx_local and qs_idx_local together describe the whole simulated state. :attr:dofs_name does not: it is the controlled subset, and it leaves out passive DOFs such as a Robotiq 2F85's followers. Read this for a full state snapshot.

free_joints property

free_joints: list[RigidJoint]

All free joints, detected by joint type — never by name.

num_dofs property

num_dofs

Return the number of controllable DOFs specified by dofs_name.

This may differ from the total number of DOFs of the entity; uncontrollable DOFs such as free base joints are not included.

n_dofs property

n_dofs

n_qs property

n_qs
num_links
links
link_start
link_end

geoms property

geoms

This entity's collision geoms, as an entity-local list.

The list is local but each geom.idx is scene-global — a Genesis RigidGeom has no idx_local (unlike a link, which carries both idx and idx_local). So entity.geoms[geom.idx] is wrong on any entity with geoms before it in the scene (geom_start != 0); the local index is geom.idx - entity.geom_start.

geom_start property

geom_start

Scene-global index of this entity's first geom: geom.idx - geom_start indexes :attr:geoms.

geom_end property

geom_end

Scene-global index one past this entity's last geom (geoms spans geom_start:geom_end).

dof_start property

dof_start

dof_end property

dof_end
base_link_name: str

Name of this entity's root link (the base of its kinematic tree).

The frame a mounted entity's pose is expressed in, and the link an asset is rooted at — so a caller can reference it without hard-coding an asset-specific name ("base", "right_hand_C_MC", …) or reaching through _entity. Note that for an attached entity Genesis reparents this link under the parent, so it is no longer a free root.

has_batched_dofs_info property

has_batched_dofs_info: bool

Whether the solver holding this entity stores per-environment DOF info.

The solver's flag, not EnvOptions.batch_dofs_info: that option configures the rigid solver only, and Genesis turns it on by itself in either solver once it holds a heterogeneous entity, whose variants each bring their own joint parameters (https://github.com/Genesis-Embodied-AI/Genesis/pull/3497).

has_batched_links_info: bool

Whether the solver holding this entity stores per-environment link info.

The solver's flag, not EnvOptions.batch_links_info: Genesis turns it on by itself for a scene that holds a heterogeneous entity, whose variants differ in mass and inertia.

setters_take_every_row property

setters_take_every_row: bool

Whether set_dofs_vel / set_qpos take every env's row alongside a bool envs_idx.

On Genesis' zero-copy path they write the masked rows through torch.where, on the device, as long as the DOF (q) argument is a range (:func:eden.utils.misc.contiguous_range) — no nonzero() sync to turn the mask into indices. The selected rows are accepted in every case, so pass those otherwise. Not under hibernation: set_dofs_velocity first wakes the entities it touches, which turns the mask into indices and then rejects the unselected envs' rows. Reads the solver's own flag rather than EnvOptions.use_hibernation: a rigid_options handed to the env constructor can switch hibernation on behind that option, and Genesis switches it off under requires_grad. set_dofs_pos takes every row too when its DOFs are hinges or slides (it writes them through set_qpos); the control_dofs_* setters never do. A kinematic entity's solver never hibernates.

offset cached property

offset

Methods:

pre_build

pre_build() -> None
Source code in eden/entities/rigid.py
def pre_build(self) -> None:
    self._downgrade_kinematic_collision_vis_mode()
    super().pre_build()
    self._refuse_variant_density_without_density_mass()
    self._validate_opt_in_friction_axes()
    self._apply_collision_link_mask()

post_build

post_build() -> None
Source code in eden/entities/rigid.py
@gs.assert_built
def post_build(self) -> None:
    # First: every step below branches on ``is_fixed_base``, so a declaration the asset
    # contradicts must stop the build before any of them runs on the wrong branch.
    self._check_mjcf_base_matches_is_fixed_base()

    is_kinematic = self.is_kinematic
    self._build_dof_properties(is_kinematic)
    self._init_qs_idx_local()
    self._init_control_gains_and_damping(is_kinematic)
    self._apply_dofs_spring_ref(is_kinematic)
    self._apply_constraint_sol_params()
    self._apply_variant_densities()

    # Set initial root pose. Skipped for a welded child: ``attach`` already consumed its
    # ``default_root_pos``/``_quat`` as the mounting transform and made its world pose FK
    # output, so there is no root pose left to write here.
    if not self._is_attached:
        self._set_initial_root_pose()

    if self.is_support_enabled and is_kinematic:
        # Same shape as _apply_constraint_sol_params' kinematic guard: warn and skip rather
        # than drop the configuration silently. A kinematic entity produces no contacts, so
        # nothing could rest on the surface anyway, and the option can arrive by inheritance
        # (a ghost of a support-enabled table) rather than by intent.
        en.logger.warning(
            f"is_support_enabled ignored on kinematic entity '{self.name}': support surfaces "
            f"are extracted for contact-producing rigid entities only."
        )
    elif self.is_support_enabled:
        self._prepare_support(
            minimum_clearance=self.support_minimum_clearance,
            pre_sample_points=True,
            pre_num_sample_points=self.support_num_sample_points,
            pre_shrink=self.support_shrink,
            pre_sample_method=self.support_sample_method,
            pre_grid_size=self.support_grid_size,
            support_links_name=self.support_links_name,
        )

attach_to

attach_to(entity: Entity, link_name: str, attach_offset: Vec3FType | None = None, attach_quat: UnitVec4FType | None = None) -> None

Attach this entity to another entity.

The mounting transform — this entity's base link pose relative to the parent link — defaults to the entity's default_root_pos / default_root_quat. Pass attach_offset / attach_quat to override it: after attachment the base link's world pose is parent_link_pose ∘ (attach_offset, attach_quat). Genesis composes the transform with the morph frame offset (up/front alignment) exactly as it does for the morph pose at load time. (Mirrors the attach_offset / attach_quat naming on CameraOptions.)

PARAMETER DESCRIPTION
entity

The entity to attach to.

TYPE: Entity

link_name

The name of the link to attach this entity to.

TYPE: str

attach_offset

Mounting position of this entity's base link in the parent link frame. Defaults to the entity's default_root_pos.

TYPE: Vec3FType | None DEFAULT: None

attach_quat

Mounting orientation (w, x, y, z) of this entity's base link in the parent link frame. Defaults to the entity's default_root_quat.

TYPE: UnitVec4FType | None DEFAULT: None

Source code in eden/entities/rigid.py
@gs.assert_unbuilt
def attach_to(
    self,
    entity: Entity,
    link_name: str,
    attach_offset: Vec3FType | None = None,
    attach_quat: UnitVec4FType | None = None,
) -> None:
    """Attach this entity to another entity.

    The mounting transform — this entity's base link pose relative to the parent link —
    defaults to the entity's ``default_root_pos`` / ``default_root_quat``. Pass
    ``attach_offset`` / ``attach_quat`` to override it: after attachment the base link's world
    pose is ``parent_link_pose ∘ (attach_offset, attach_quat)``. Genesis composes the transform
    with the morph frame offset (up/front alignment) exactly as it does for the morph pose at
    load time. (Mirrors the ``attach_offset`` / ``attach_quat`` naming on ``CameraOptions``.)

    Parameters
    ----------
    entity: Entity
        The entity to attach to.
    link_name: str
        The name of the link to attach this entity to.
    attach_offset: array-like[float, float, float], optional
        Mounting position of this entity's base link in the parent link frame.
        Defaults to the entity's ``default_root_pos``.
    attach_quat: array-like[float, float, float, float], optional
        Mounting orientation (w, x, y, z) of this entity's base link in the parent link
        frame. Defaults to the entity's ``default_root_quat``.
    """
    # Genesis's ``attach`` writes one link-local pose shared by all envs, so a grouped
    # child's per-variant root offsets can't be expressed — reject rather than silently
    # mount every env at the same pose.
    if self.is_heterogeneous:
        raise NotImplementedError(
            f"attach_to is not supported for heterogeneous (GroupedEntityOptions) entity "
            f"'{self.name}': the mounting transform is a single pose shared by all envs and "
            f"would silently drop the per-variant root offsets. Attach a non-grouped entity, "
            f"or add per-variant mount support."
        )

    mount_pos = self._options.default_root_pos if attach_offset is None else attach_offset
    mount_quat = self._options.default_root_quat if attach_quat is None else attach_quat

    def _do_attach():
        child = self._entity
        # MJCF entities may have multiple kinematic trees (e.g. a world link
        # in one tree and the actual robot base in another).  Genesis's
        # ``attach`` assumes every link shares the same ``root_idx`` as
        # ``links[0]``.  Merge secondary trees into the first so the
        # assertion holds.
        if len(child.links) > 1:
            base_root = child.links[0].root_idx
            roots_to_merge = set()
            for link in child.links[1:]:
                if link.root_idx != base_root and link.root_idx != -1:
                    roots_to_merge.add(link.root_idx)
            if roots_to_merge:
                for link in child.links:
                    if link.root_idx in roots_to_merge:
                        link._root_idx = base_root

        # Forward kinematics interprets the base link's local pose relative to the new
        # parent link, so ``(mount_pos, mount_quat)`` acts as the mounting transform.
        # Genesis composes it with the morph frame offset (``_offset_pos``/``_offset_quat``)
        # internally. Always pass an explicit pose — the morph alone only carries
        # ``default_root_pos`` for a floating base, so relying on the morph would silently
        # drop ``default_root_quat``.
        child.attach(
            entity._entity,
            link_name,
            pos=np.asarray(mount_pos, dtype=gs.np_float),
            quat=np.asarray(mount_quat, dtype=gs.np_float),
        )

    self._env.register_pre_build_hook(_do_attach)
    self._is_attached = True
    self._attachment_parent = entity

find_named_dofs_idx_local

find_named_dofs_idx_local(dofs_name: list[str] | str, name_scope: list[str] | None = None, preserve_order: bool = True) -> tuple[list[str], list[int]]

Find DOF indices by DOF/joint names or patterns.

Returns local indices, Genesis' own: hand them to a dofs_idx_local= argument and nowhere else. For the controlled joints, :meth:find_named_dofs resolves the same query into these and the matching columns of the property tensors at once.

Supports: - Exact names: "joint_1", taken literally even when they contain a pattern character ("pin.1" selects pin.1 alone, not also pin_1). Judged against every joint of the entity, so the exact name of a joint outside name_scope selects nothing rather than a neighbour inside it - Glob patterns: "joint_", "arm?[LR]_" (fnmatch semantics) - Regex patterns: any valid regex (uses fullmatch). If a pattern isn't an exact match or glob, we attempt to treat it as a regex.

PARAMETER DESCRIPTION
dofs_name

The names of the DOFs to find.

TYPE: list[str] | str

name_scope

The names of the joints to search within. If None, all joints will be searched; an empty list searches none, so any key raises.

TYPE: list[str] | None DEFAULT: None

preserve_order

Whether to preserve the order of the DOFs. If True, the DOFs are returned in the order of the keys in dofs_name.

TYPE: bool DEFAULT: True

Source code in eden/entities/rigid.py
@gs.assert_built
def find_named_dofs_idx_local(
    self,
    dofs_name: list[str] | str,
    name_scope: list[str] | None = None,
    preserve_order: bool = True,
) -> tuple[list[str], list[int]]:
    """Find DOF indices by DOF/joint names or patterns.

    Returns *local* indices, Genesis' own: hand them to a ``dofs_idx_local=`` argument and nowhere
    else. For the controlled joints, :meth:`find_named_dofs` resolves the same query into these and
    the matching columns of the property tensors at once.

    Supports:
    - Exact names: "joint_1", taken literally even when they contain a pattern character
      ("pin.1" selects ``pin.1`` alone, not also ``pin_1``). Judged against every joint of the
      entity, so the exact name of a joint outside ``name_scope`` selects nothing rather than a
      neighbour inside it
    - Glob patterns: "joint_*", "arm?[LR]_*" (fnmatch semantics)
    - Regex patterns: any valid regex (uses fullmatch). If a pattern isn't an exact
      match or glob, we attempt to treat it as a regex.

    Parameters
    ----------
    dofs_name: list[str] | str
        The names of the DOFs to find.
    name_scope: list[str] | None
        The names of the joints to search within. If None, all joints will be searched; an
        empty list searches none, so any key raises.
    preserve_order: bool
        Whether to preserve the order of the DOFs. If True, the DOFs are returned in the order of the keys in
        ``dofs_name``.
    """
    all_joint_names = self._every_joint_name
    joint_names = all_joint_names if name_scope is None else name_scope

    _, names = resolve_matching_names(
        dofs_name, joint_names, preserve_order=preserve_order, literal_names=all_joint_names
    )

    res_dofs_name: list[str] = []
    res_dofs_idx: list[int] = []
    for name in names:
        joint = self._entity.get_joint(name)
        res_dofs_name.extend([name] * len(joint.dofs_idx_local))
        res_dofs_idx.extend(joint.dofs_idx_local)

    return res_dofs_name, res_dofs_idx

find_free_base_dofs_idx_local

find_free_base_dofs_idx_local(joint_name: str | None = None) -> list[int]

Find the six local DOF indices of the floating base's free joint.

With joint_name=None (the default) the free joint is detected by joint type — the only convention-free spelling: an MJCF <freejoint> can carry any name (Unitree's G1 uses floating_base_joint) while Genesis synthesizes root_joint for a URDF floating base, so any fixed name default breaks one of the two formats. Raises unless exactly one free joint exists. Pass an explicit joint_name to pick one on the rare multi-free-joint entity; it must name a free joint, so a hinge that happens to match a naming convention can never silently receive base-DOF writes.

PARAMETER DESCRIPTION
joint_name

Exact name of the free joint to select, or None to auto-detect by joint type.

TYPE: str | None DEFAULT: None

Source code in eden/entities/rigid.py
@gs.assert_built
def find_free_base_dofs_idx_local(self, joint_name: str | None = None) -> list[int]:
    """Find the six local DOF indices of the floating base's free joint.

    With ``joint_name=None`` (the default) the free joint is detected by joint
    *type* — the only convention-free spelling: an MJCF ``<freejoint>`` can carry
    any name (Unitree's G1 uses ``floating_base_joint``) while Genesis synthesizes
    ``root_joint`` for a URDF floating base, so any fixed name default breaks one
    of the two formats. Raises unless exactly one free joint exists. Pass an
    explicit ``joint_name`` to pick one on the rare multi-free-joint entity; it
    must name a free joint, so a hinge that happens to match a naming convention
    can never silently receive base-DOF writes.

    Parameters
    ----------
    joint_name: str | None
        Exact name of the free joint to select, or ``None`` to auto-detect by
        joint type.
    """
    free_joints = self.free_joints
    if joint_name is None:
        if len(free_joints) != 1:
            raise ValueError(
                f"Entity '{self.name}': cannot auto-detect the floating base — expected exactly one "
                f"free joint, found {len(free_joints)} ({[joint.name for joint in free_joints]}). "
                + (
                    "Set `joint_name` to pick one."
                    if free_joints
                    else "The entity has no floating base (fixed or welded); this lookup does not apply to it."
                )
            )
        return list(free_joints[0].dofs_idx_local)
    for joint in free_joints:
        if joint.name == joint_name:
            return list(joint.dofs_idx_local)
    raise ValueError(
        f"Entity '{self.name}': joint_name='{joint_name}' does not name a free joint. "
        f"Free joints: {[joint.name for joint in free_joints]}; "
        f"all joints: {[joint.name for joint in self._entity.joints]}. "
        f"Pass None to auto-detect the free base joint by type."
    )
find_named_links_idx_local(links_name: list[str] | str, name_scope: list[str] | None = None, preserve_order: bool = True) -> tuple[list[str], list[int]]

Find link indices by link names or patterns.

Supports: - Exact names: "link_1" - Glob patterns: "link_", "arm?[LR]_" (fnmatch semantics) - Regex patterns: any valid regex (uses fullmatch). If a pattern isn't an exact match or glob, we attempt to treat it as a regex.

PARAMETER DESCRIPTION
links_name

The names of the links to find.

TYPE: list[str] | str

name_scope

The names of the links to search within. If None, all links will be searched; an empty list searches none, so any key raises.

TYPE: list[str] | None DEFAULT: None

preserve_order

Whether to preserve the order of the links. If True, the links are returned in the order of the keys in links_name.

TYPE: bool DEFAULT: True

Source code in eden/entities/rigid.py
@gs.assert_built
def find_named_links_idx_local(
    self,
    links_name: list[str] | str,
    name_scope: list[str] | None = None,
    preserve_order: bool = True,
) -> tuple[list[str], list[int]]:
    """Find link indices by link names or patterns.

    Supports:
    - Exact names: "link_1"
    - Glob patterns: "link_*", "arm?[LR]_*" (fnmatch semantics)
    - Regex patterns: any valid regex (uses fullmatch). If a pattern isn't an exact
      match or glob, we attempt to treat it as a regex.

    Parameters
    ----------
    links_name: list[str] | str
        The names of the links to find.
    name_scope: list[str] | None
        The names of the links to search within. If None, all links will be searched; an
        empty list searches none, so any key raises.
    preserve_order: bool
        Whether to preserve the order of the links. If True, the links are returned in the order of the keys in
        ``links_name``.
    """
    every_link_name = [link.name for link in self._entity.links]
    all_links_name = every_link_name if name_scope is None else name_scope
    _, names = resolve_matching_names(
        links_name, all_links_name, preserve_order=preserve_order, literal_names=every_link_name
    )

    res_links_name: list[str] = []
    res_links_idx: list[int] = []
    for name in names:
        link = self._entity.get_link(name)
        res_links_name.extend([name])
        res_links_idx.extend([link.idx_local])

    return res_links_name, res_links_idx

forward_kinematics

forward_kinematics(qpos, qs_idx_local=None, links_idx_local=None, envs_idx=None)

Compute forward kinematics for a single link or multiple links.

Source code in eden/entities/rigid.py
def forward_kinematics(self, qpos, qs_idx_local=None, links_idx_local=None, envs_idx=None):
    """Compute forward kinematics for a single link or multiple links."""
    return self._entity.forward_kinematics(qpos, qs_idx_local, links_idx_local, envs_idx)

inverse_kinematics

inverse_kinematics(links_name, poss=None, quats=None, local_points=None, init_qpos=None, respect_joint_limit=True, max_samples=50, max_solver_iters=20, damping=0.01, pos_tol=0.0005, rot_tol=0.005, pos_mask=[True, True, True], rot_mask=[True, True, True], max_step_size=0.5, dofs_idx_local=None, return_error=False, envs_idx=None)

Compute inverse kinematics for a single link or multiple links.

PARAMETER DESCRIPTION
links_name

The name of the link to compute inverse kinematics for.

poss

The target position of each link. If None, position error is not considered. Defaults to None.

DEFAULT: None

quats

The target orientation (w, x, y, z) of each link. If None, orientation error is not considered. Defaults to None.

DEFAULT: None

local_points

The point in each link's local frame that poss / quats place, one per link. Use it to steer a frame the solver does not own -- a tool tip, or a gripper welded ahead of the flange -- without solving for the link origin and correcting afterwards. If None, each link's origin is placed. Defaults to None.

DEFAULT: None

init_qpos

Initial joint positions used to seed the solver. Defaults to None.

DEFAULT: None

respect_joint_limit

Whether to clamp the solution to the joint limits. Defaults to True.

DEFAULT: True

max_samples

Maximum number of random restarts attempted by the solver. Defaults to 50.

DEFAULT: 50

max_solver_iters

Maximum number of iterations per sample. Defaults to 20.

DEFAULT: 20

damping

Damping factor for the damped least-squares update. Defaults to 0.01.

DEFAULT: 0.01

pos_tol

Position tolerance for convergence, in meters. Defaults to 5e-4.

DEFAULT: 0.0005

rot_tol

Rotation tolerance for convergence, in radians. Defaults to 5e-3.

DEFAULT: 0.005

pos_mask

Per-axis (x, y, z) mask selecting which position components to match. Defaults to [True, True, True].

DEFAULT: [True, True, True]

rot_mask

Per-axis (x, y, z) mask selecting which rotation components to match. Defaults to [True, True, True].

DEFAULT: [True, True, True]

max_step_size

Maximum joint update applied per iteration. Defaults to 0.5.

DEFAULT: 0.5

dofs_idx_local

Local indices of the DOFs the solver is allowed to move. If None, all DOFs are used. Defaults to None.

DEFAULT: None

return_error

If True, also return the final residual error alongside the solution. Defaults to False.

DEFAULT: False

envs_idx

The indices of the environments to solve for. If None, all environments are used. Defaults to None.

DEFAULT: None

RETURNS DESCRIPTION
qpos

Solver qpos (the entity's generalized coordinates).

TYPE: (array_like, shape(n_qs) or (n_envs, n_qs) or (len(envs_idx), n_qs))

error_pose

Pose error [pos_x, pos_y, pos_z, rot_x, rot_y, rot_z] per target; returned only when return_error is True.

TYPE: (array_like, shape(..., 6))

Source code in eden/entities/rigid.py
def inverse_kinematics(
    self,
    links_name,
    poss=None,
    quats=None,
    local_points=None,
    init_qpos=None,
    respect_joint_limit=True,
    max_samples=50,
    max_solver_iters=20,
    damping=0.01,
    pos_tol=5e-4,  # 0.5 mm
    rot_tol=5e-3,  # 0.28 degree
    pos_mask=[True, True, True],
    rot_mask=[True, True, True],
    max_step_size=0.5,
    dofs_idx_local=None,
    return_error=False,
    envs_idx=None,
):
    """
    Compute inverse kinematics for a single link or multiple links.

    Parameters
    ----------
    links_name: str | list[str]
        The name of the link to compute inverse kinematics for.
    poss: array_like | list[array_like], optional
        The target position of each link. If None, position error is not considered. Defaults to None.
    quats: array_like | list[array_like], optional
        The target orientation (w, x, y, z) of each link. If None, orientation error is not considered.
        Defaults to None.
    local_points: array_like | list[array_like], optional
        The point in each link's *local* frame that ``poss`` / ``quats`` place, one per link. Use it to steer a
        frame the solver does not own -- a tool tip, or a gripper welded ahead of the flange -- without solving
        for the link origin and correcting afterwards. If None, each link's origin is placed. Defaults to None.
    init_qpos: array_like, optional
        Initial joint positions used to seed the solver. Defaults to None.
    respect_joint_limit: bool, optional
        Whether to clamp the solution to the joint limits. Defaults to True.
    max_samples: int, optional
        Maximum number of random restarts attempted by the solver. Defaults to 50.
    max_solver_iters: int, optional
        Maximum number of iterations per sample. Defaults to 20.
    damping: float, optional
        Damping factor for the damped least-squares update. Defaults to 0.01.
    pos_tol: float, optional
        Position tolerance for convergence, in meters. Defaults to 5e-4.
    rot_tol: float, optional
        Rotation tolerance for convergence, in radians. Defaults to 5e-3.
    pos_mask: list[bool], optional
        Per-axis ``(x, y, z)`` mask selecting which position components to match.
        Defaults to ``[True, True, True]``.
    rot_mask: list[bool], optional
        Per-axis ``(x, y, z)`` mask selecting which rotation components to match.
        Defaults to ``[True, True, True]``.
    max_step_size: float, optional
        Maximum joint update applied per iteration. Defaults to 0.5.
    dofs_idx_local: array_like, optional
        Local indices of the DOFs the solver is allowed to move. If None, all DOFs are used. Defaults to None.
    return_error: bool, optional
        If True, also return the final residual error alongside the solution. Defaults to False.
    envs_idx: array_like, optional
        The indices of the environments to solve for. If None, all environments are used. Defaults to None.

    Returns
    -------
    qpos : array_like, shape (n_qs,) or (n_envs, n_qs) or (len(envs_idx), n_qs)
        Solver qpos (the entity's generalized coordinates).
    error_pose : array_like, shape (..., 6)
        Pose error ``[pos_x, pos_y, pos_z, rot_x, rot_y, rot_z]`` per target; returned only when
        ``return_error`` is True.
    """
    if isinstance(links_name, str):
        return self._entity.inverse_kinematics(
            link=self.get_link(links_name),
            pos=poss,
            quat=quats,
            local_point=local_points,
            init_qpos=init_qpos,
            respect_joint_limit=respect_joint_limit,
            max_samples=max_samples,
            max_solver_iters=max_solver_iters,
            damping=damping,
            pos_tol=pos_tol,
            rot_tol=rot_tol,
            pos_mask=pos_mask,
            rot_mask=rot_mask,
            max_step_size=max_step_size,
            dofs_idx_local=dofs_idx_local,
            return_error=return_error,
            envs_idx=envs_idx,
        )
    else:
        return self._entity.inverse_kinematics_multilink(
            links=[self.get_link(name) for name in links_name],
            poss=poss,
            quats=quats,
            local_points=local_points,
            init_qpos=init_qpos,
            respect_joint_limit=respect_joint_limit,
            max_samples=max_samples,
            max_solver_iters=max_solver_iters,
            damping=damping,
            pos_tol=pos_tol,
            rot_tol=rot_tol,
            pos_mask=pos_mask,
            rot_mask=rot_mask,
            max_step_size=max_step_size,
            dofs_idx_local=dofs_idx_local,
            return_error=return_error,
            envs_idx=envs_idx,
        )

get_jacobian

get_jacobian(link, local_point=None)

Get the spatial Jacobian for a point on a target link.

Columns are returned in dofs_name order (matching set_qpos / get_dofs_limit), not the Genesis kinematic-tree order.

PARAMETER DESCRIPTION
link

The target link.

TYPE: RigidLink

local_point

Coordinates of the point in the link's local frame. If None, the link origin is used.

TYPE: (Tensor or None, shape(3)) DEFAULT: None

RETURNS DESCRIPTION
jacobian

The Jacobian matrix of shape (n_envs, 6, entity.n_dofs) or (6, entity.n_dofs) if n_envs == 0.

TYPE: Tensor

Source code in eden/entities/rigid.py
def get_jacobian(self, link, local_point=None):
    """
    Get the spatial Jacobian for a point on a target link.

    Columns are returned in ``dofs_name`` order (matching ``set_qpos`` /
    ``get_dofs_limit``), **not** the Genesis kinematic-tree order.

    Parameters
    ----------
    link : RigidLink
        The target link.
    local_point : torch.Tensor or None, shape (3,)
        Coordinates of the point in the link's *local* frame.
        If None, the link origin is used.

    Returns
    -------
    jacobian : torch.Tensor
        The Jacobian matrix of shape (n_envs, 6, entity.n_dofs) or (6, entity.n_dofs) if n_envs == 0.
    """
    J = self._entity.get_jacobian(link, local_point)
    if self._jac_perm is not None:
        J = J[..., self._jac_perm]
    return J
get_link(link_name: str)
Source code in eden/entities/rigid.py
def get_link(self, link_name: str):
    return self._entity.get_link(link_name)

get_AABB

get_AABB()
Source code in eden/entities/rigid.py
def get_AABB(self):
    return self._entity.get_AABB()

get_qpos

get_qpos(qs_idx_local=None, envs_idx=None)

Get the entity's generalized coordinates.

These are q-coordinates, not DOFs: a floating base contributes 7 root qs (position + (w, x, y, z) quaternion) against 6 root DOFs, so n_qs and n_dofs differ for a free-base entity. A welded (attached) child has no root qs at all.

PARAMETER DESCRIPTION
qs_idx_local

Local q indices to read. If None, reads the entity's own q range in dofs_name order (root qs first for a floating base). Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
qpos

The entity's generalized coordinates.

TYPE: (Tensor, shape(n_qs) or (n_envs, n_qs))

Source code in eden/entities/rigid.py
def get_qpos(self, qs_idx_local=None, envs_idx=None):
    """Get the entity's generalized coordinates.

    These are q-coordinates, not DOFs: a floating base contributes 7 root qs
    (position + ``(w, x, y, z)`` quaternion) against 6 root DOFs, so ``n_qs`` and
    ``n_dofs`` differ for a free-base entity. A welded (attached) child has no root
    qs at all.

    Parameters
    ----------
    qs_idx_local : None | array_like, optional
        Local q indices to read. If None, reads the entity's own q range in
        dofs_name order (root qs first for a floating base). Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.

    Returns
    -------
    qpos : torch.Tensor, shape (n_qs,) or (n_envs, n_qs)
        The entity's generalized coordinates.
    """
    if qs_idx_local is None:
        qs_idx_local = self._genesis_qs_idx
    else:
        qs_idx_local = _genesis_selection(qs_idx_local)
    return _row_major_selection(self._entity.get_qpos(qs_idx_local, envs_idx=envs_idx), qs_idx_local)

get_dofs_control_force

get_dofs_control_force(dofs_idx_local=None, envs_idx=None)

Get the force the controller applied to the entity's dofs over the last physics step.

This is the actuator/PD contribution only, already clipped to the force range — use :meth:get_dofs_force for the total internal force. A target set since that step shows in the force of the next one.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
force

The control force on the entity's dofs.

TYPE: (Tensor, shape(n_dofs) or (n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_control_force(self, dofs_idx_local=None, envs_idx=None):
    """Get the force the controller applied to the entity's dofs over the last physics step.

    This is the actuator/PD contribution only, already clipped to the force range —
    use :meth:`get_dofs_force` for the total internal force. A target set since that step
    shows in the force of the next one.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.

    Returns
    -------
    force : torch.Tensor, shape (n_dofs,) or (n_envs, n_dofs)
        The control force on the entity's dofs.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    return _row_major_selection(
        self._entity.get_dofs_control_force(dofs_idx_local=dofs_idx_local, envs_idx=envs_idx), dofs_idx_local
    )

get_dofs_force

get_dofs_force(dofs_idx_local=None, envs_idx=None)

Get the total internal force acting on the entity's dofs.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
force

The total force on the entity's dofs.

TYPE: (Tensor, shape(n_dofs) or (n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_force(self, dofs_idx_local=None, envs_idx=None):
    """Get the total internal force acting on the entity's dofs.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.

    Returns
    -------
    force : torch.Tensor, shape (n_dofs,) or (n_envs, n_dofs)
        The total force on the entity's dofs.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    return _row_major_selection(
        self._entity.get_dofs_force(dofs_idx_local=dofs_idx_local, envs_idx=envs_idx), dofs_idx_local
    )

get_dofs_vel

get_dofs_vel(dofs_idx_local=None, envs_idx=None)

Get the entity's dofs' velocity.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
velocity

The entity's dofs' velocity.

TYPE: (Tensor, shape(n_dofs) or (n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_vel(self, dofs_idx_local=None, envs_idx=None):
    """
    Get the entity's dofs' velocity.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.

    Returns
    -------
    velocity : torch.Tensor, shape (n_dofs,) or (n_envs, n_dofs)
        The entity's dofs' velocity.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    return _row_major_selection(self._entity.get_dofs_velocity(dofs_idx_local, envs_idx), dofs_idx_local)

get_dofs_pos

get_dofs_pos(dofs_idx_local=None, envs_idx=None)

Get the entity's dofs' position.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
position

The entity's dofs' position.

TYPE: (Tensor, shape(n_dofs) or (n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_pos(self, dofs_idx_local=None, envs_idx=None):
    """
    Get the entity's dofs' position.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.

    Returns
    -------
    position : torch.Tensor, shape (n_dofs,) or (n_envs, n_dofs)
        The entity's dofs' position.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    return _row_major_selection(self._entity.get_dofs_position(dofs_idx_local, envs_idx), dofs_idx_local)

get_dofs_kp

get_dofs_kp(dofs_idx_local=None, envs_idx=None)

Get the positional (proportional) gain of the entity's PD controllers.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
kp

The positional gain of the entity's dofs. Carries the environment axis in either batch_dofs_info mode — see the class Batching note.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_kp(self, dofs_idx_local=None, envs_idx=None):
    """Get the positional (proportional) gain of the entity's PD controllers.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    kp : torch.Tensor, shape (n_envs, n_dofs)
        The positional gain of the entity's dofs. Carries the environment axis in either
        ``batch_dofs_info`` mode — see the class ``Batching`` note.
    """
    return self._get_dofs_info(self._entity.get_dofs_kp, dofs_idx_local, envs_idx)

get_dofs_kd

get_dofs_kd(dofs_idx_local=None, envs_idx=None)

Get the velocity (derivative) gain of the entity's PD controllers.

Genesis names this gain kv; Eden exposes it as kd throughout.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
kd

The velocity gain of the entity's dofs. Carries the environment axis in either batch_dofs_info mode — see the class Batching note.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_kd(self, dofs_idx_local=None, envs_idx=None):
    """Get the velocity (derivative) gain of the entity's PD controllers.

    Genesis names this gain ``kv``; Eden exposes it as ``kd`` throughout.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    kd : torch.Tensor, shape (n_envs, n_dofs)
        The velocity gain of the entity's dofs. Carries the environment axis in either
        ``batch_dofs_info`` mode — see the class ``Batching`` note.
    """
    return self._get_dofs_info(self._entity.get_dofs_kv, dofs_idx_local, envs_idx)

get_dofs_force_range

get_dofs_force_range(dofs_idx_local=None, envs_idx=None) -> tuple[Tensor, Tensor]

Get the force range (min and max limits) for the entity's dofs.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
lower_limit

The lower limit of the force range for the entity's dofs.

TYPE: (Tensor, shape(n_envs, n_dofs))

upper_limit

The upper limit of the force range for the entity's dofs.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_force_range(self, dofs_idx_local=None, envs_idx=None) -> tuple[torch.Tensor, torch.Tensor]:
    """
    Get the force range (min and max limits) for the entity's dofs.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    lower_limit : torch.Tensor, shape (n_envs, n_dofs)
        The lower limit of the force range for the entity's dofs.
    upper_limit : torch.Tensor, shape (n_envs, n_dofs)
        The upper limit of the force range for the entity's dofs.
    """
    return self._get_dofs_info(self._entity.get_dofs_force_range, dofs_idx_local, envs_idx)

get_dofs_limit

get_dofs_limit(dofs_idx_local=None, envs_idx=None)

Get the positional limits of the entity's dofs.

Returns the two bounds as a tuple of tensors, not one stacked tensor — unpack it as lower, upper = entity.get_dofs_limit(). Unlimited dofs report -inf / +inf. Both tensors carry the environment axis.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
lower_limit

The lower positional limit of the entity's dofs.

TYPE: (Tensor, shape(n_envs, n_dofs))

upper_limit

The upper positional limit of the entity's dofs.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_limit(self, dofs_idx_local=None, envs_idx=None):
    """Get the positional limits of the entity's dofs.

    Returns the two bounds as a *tuple of tensors*, not one stacked tensor —
    unpack it as ``lower, upper = entity.get_dofs_limit()``. Unlimited dofs report
    ``-inf`` / ``+inf``. Both tensors carry the environment axis.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    lower_limit : torch.Tensor, shape (n_envs, n_dofs)
        The lower positional limit of the entity's dofs.
    upper_limit : torch.Tensor, shape (n_envs, n_dofs)
        The upper positional limit of the entity's dofs.
    """
    return self._get_dofs_info(self._entity.get_dofs_limit, dofs_idx_local, envs_idx)

get_qpos_limit

get_qpos_limit(envs_idx=None)

Get the positional limits of the entity's generalized coordinates, in :meth:get_qpos order.

A hinge / slide coordinate takes its DOF's limit and a free joint's position its translational DOFs'. A free or ball joint's (w, x, y, z) quaternion takes the unit bounds [-1, 1], since it is normalized rather than limited. The limits are read off the solver, so each environment reports those of the heterogeneous variant it carries.

PARAMETER DESCRIPTION
envs_idx

The environment indices. If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
lower_limit

The lower positional limit of the entity's generalized coordinates.

TYPE: (Tensor, shape(n_envs, n_qs))

upper_limit

The upper positional limit of the entity's generalized coordinates.

TYPE: (Tensor, shape(n_envs, n_qs))

Source code in eden/entities/rigid.py
def get_qpos_limit(self, envs_idx=None):
    """Get the positional limits of the entity's generalized coordinates, in :meth:`get_qpos` order.

    A hinge / slide coordinate takes its DOF's limit and a free joint's position its translational
    DOFs'. A free or ball joint's ``(w, x, y, z)`` quaternion takes the unit bounds ``[-1, 1]``, since
    it is normalized rather than limited. The limits are read off the solver, so each environment
    reports those of the heterogeneous variant it carries.

    Parameters
    ----------
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices. If None, all environments will be considered. Defaults to None.

    Returns
    -------
    lower_limit : torch.Tensor, shape (n_envs, n_qs)
        The lower positional limit of the entity's generalized coordinates.
    upper_limit : torch.Tensor, shape (n_envs, n_qs)
        The upper positional limit of the entity's generalized coordinates.
    """
    # Every DOF, in Genesis order, then every q of the entity, joint by joint.
    lower, upper = self._get_dofs_info(self._entity.get_dofs_limit, range(self._entity.n_dofs), envs_idx)
    unit = torch.ones((lower.shape[0], 4), dtype=lower.dtype, device=lower.device)
    qs_lower, qs_upper = [lower[:, :0]], [upper[:, :0]]
    for joint in self._entity.joints:
        dof_start = joint.dof_start - self._entity.dof_start
        if joint.type in (gs.JOINT_TYPE.FREE, gs.JOINT_TYPE.SPHERICAL):
            translation = slice(dof_start, dof_start + joint.n_qs - 4)  # 3 for a free joint, none for a ball
            qs_lower += [lower[:, translation], -unit]
            qs_upper += [upper[:, translation], unit]
        elif joint.type != gs.JOINT_TYPE.FIXED:
            qs_lower.append(lower[:, dof_start : dof_start + joint.n_dofs])
            qs_upper.append(upper[:, dof_start : dof_start + joint.n_dofs])
    qs_lower, qs_upper = torch.cat(qs_lower, dim=1), torch.cat(qs_upper, dim=1)
    if self.qs_idx_local is None:
        return qs_lower, qs_upper
    return qs_lower[:, self.qs_idx_local], qs_upper[:, self.qs_idx_local]

get_dofs_stiffness

get_dofs_stiffness(dofs_idx_local=None, envs_idx=None)

Get the joint-spring stiffness of the entity's dofs.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
stiffness

The spring stiffness of the entity's dofs. Carries the environment axis in either batch_dofs_info mode — see the class Batching note.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_stiffness(self, dofs_idx_local=None, envs_idx=None):
    """Get the joint-spring stiffness of the entity's dofs.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    stiffness : torch.Tensor, shape (n_envs, n_dofs)
        The spring stiffness of the entity's dofs. Carries the environment axis in either
        ``batch_dofs_info`` mode — see the class ``Batching`` note.
    """
    return self._get_dofs_info(self._entity.get_dofs_stiffness, dofs_idx_local, envs_idx)

get_dofs_invweight

get_dofs_invweight(dofs_idx_local=None, envs_idx=None)

Get the inverse effective inertia (invweight) of the entity's dofs.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
invweight

The inverse effective inertia of the entity's dofs. Carries the environment axis in either batch_dofs_info mode — see the class Batching note.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_invweight(self, dofs_idx_local=None, envs_idx=None):
    """Get the inverse effective inertia (``invweight``) of the entity's dofs.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    invweight : torch.Tensor, shape (n_envs, n_dofs)
        The inverse effective inertia of the entity's dofs. Carries the environment axis in either
        ``batch_dofs_info`` mode — see the class ``Batching`` note.
    """
    return self._get_dofs_info(self._entity.get_dofs_invweight, dofs_idx_local, envs_idx)

get_dofs_armature

get_dofs_armature(dofs_idx_local=None, envs_idx=None)

Get the armature (added rotor inertia) of the entity's dofs.

For an actuated entity under EnvOptions.integrator="euler" this is not the configured value: post_build folds kd * substep_dt into the armature for implicit-damping stability. Under approximate_implicitfast (the default) and implicitfast it is.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
armature

The armature of the entity's dofs. Carries the environment axis in either batch_dofs_info mode — see the class Batching note.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_armature(self, dofs_idx_local=None, envs_idx=None):
    """Get the armature (added rotor inertia) of the entity's dofs.

    For an actuated entity under ``EnvOptions.integrator="euler"`` this is *not* the configured
    value: ``post_build`` folds ``kd * substep_dt`` into the armature for implicit-damping
    stability. Under ``approximate_implicitfast`` (the default) and ``implicitfast`` it is.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    armature : torch.Tensor, shape (n_envs, n_dofs)
        The armature of the entity's dofs. Carries the environment axis in either
        ``batch_dofs_info`` mode — see the class ``Batching`` note.
    """
    return self._get_dofs_info(self._entity.get_dofs_armature, dofs_idx_local, envs_idx)

get_dofs_damping

get_dofs_damping(dofs_idx_local=None, envs_idx=None)

Get the viscous damping of the entity's dofs.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
damping

The damping of the entity's dofs. Carries the environment axis in either batch_dofs_info mode — see the class Batching note.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_damping(self, dofs_idx_local=None, envs_idx=None):
    """Get the viscous damping of the entity's dofs.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    damping : torch.Tensor, shape (n_envs, n_dofs)
        The damping of the entity's dofs. Carries the environment axis in either
        ``batch_dofs_info`` mode — see the class ``Batching`` note.
    """
    return self._get_dofs_info(self._entity.get_dofs_damping, dofs_idx_local, envs_idx)

get_dofs_frictionloss

get_dofs_frictionloss(dofs_idx_local=None, envs_idx=None)

Get the dry-friction loss (stick band) of the entity's dofs.

PARAMETER DESCRIPTION
dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments will be considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
frictionloss

The dry-friction loss of the entity's dofs. Carries the environment axis in either batch_dofs_info mode — see the class Batching note.

TYPE: (Tensor, shape(n_envs, n_dofs))

Source code in eden/entities/rigid.py
def get_dofs_frictionloss(self, dofs_idx_local=None, envs_idx=None):
    """Get the dry-friction loss (stick band) of the entity's dofs.

    Parameters
    ----------
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments will be considered. Defaults to None.

    Returns
    -------
    frictionloss : torch.Tensor, shape (n_envs, n_dofs)
        The dry-friction loss of the entity's dofs. Carries the environment axis in either
        ``batch_dofs_info`` mode — see the class ``Batching`` note.
    """
    return self._get_dofs_info(self._entity.get_dofs_frictionloss, dofs_idx_local, envs_idx)

get_mass_mat

get_mass_mat(envs_idx=None, decompose=False)
Source code in eden/entities/rigid.py
def get_mass_mat(self, envs_idx=None, decompose=False):
    return self._entity.get_mass_mat(envs_idx, decompose)

get_pos

get_pos(envs_idx=None, *, relative: bool = False)

Return the position of the entity's base link.

PARAMETER DESCRIPTION
envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Whether to report the Genesis user-frame position with the morph pose offset and inertial alignment stripped. Defaults to False, which returns the solver/world-frame position.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
pos

The base-link position.

TYPE: (Tensor, shape(3) or (n_envs, 3))

Source code in eden/entities/rigid.py
def get_pos(self, envs_idx=None, *, relative: bool = False):
    """Return the position of the entity's base link.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Whether to report the Genesis user-frame position with the morph pose offset and inertial alignment
        stripped. Defaults to False, which returns the solver/world-frame position.

    Returns
    -------
    pos : torch.Tensor, shape (3,) or (n_envs, 3)
        The base-link position.
    """
    return self._entity.get_pos(envs_idx=envs_idx, relative=relative)

get_quat

get_quat(envs_idx=None, *, relative: bool = False)

Return the quaternion of the entity's base link.

PARAMETER DESCRIPTION
envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Whether to report the Genesis user-frame orientation with the morph pose offset and inertial alignment stripped. Defaults to False, which returns the solver/world-frame orientation.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
quat

The base-link quaternion in (w, x, y, z) format.

TYPE: (Tensor, shape(4) or (n_envs, 4))

Source code in eden/entities/rigid.py
def get_quat(self, envs_idx=None, *, relative: bool = False):
    """Return the quaternion of the entity's base link.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Whether to report the Genesis user-frame orientation with the morph pose offset and inertial alignment
        stripped. Defaults to False, which returns the solver/world-frame orientation.

    Returns
    -------
    quat : torch.Tensor, shape (4,) or (n_envs, 4)
        The base-link quaternion in ``(w, x, y, z)`` format.
    """
    return self._entity.get_quat(envs_idx=envs_idx, relative=relative)

get_T

get_T(envs_idx=None, *, relative: bool = False)

Return the entity's base-link pose as a 4x4 homogeneous transform matrix.

PARAMETER DESCRIPTION
envs_idx

The indices of the environments. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Whether to use Genesis's user-frame pose with the morph pose offset and inertial alignment stripped. Defaults to False, which returns the solver/world-frame transform.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
transform

The base-link homogeneous transform.

TYPE: (Tensor, shape(4, 4) or (n_envs, 4, 4))

Source code in eden/entities/rigid.py
def get_T(self, envs_idx=None, *, relative: bool = False):
    """Return the entity's base-link pose as a 4x4 homogeneous transform matrix.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Whether to use Genesis's user-frame pose with the morph pose offset and inertial alignment stripped.
        Defaults to False, which returns the solver/world-frame transform.

    Returns
    -------
    transform : torch.Tensor, shape (4, 4) or (n_envs, 4, 4)
        The base-link homogeneous transform.
    """
    pos = self.get_pos(envs_idx=envs_idx, relative=relative)
    quat = self.get_quat(envs_idx=envs_idx, relative=relative)
    return gu.trans_quat_to_T(pos, quat)
get_links_pos(ls_idx_local=None, envs_idx=None, *, ref='link_origin', relative: bool = False)

Return the positions of the entity's links.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

ref

Point on each link whose position is reported: the link frame origin, or the link's center of mass. Defaults to "link_origin".

TYPE: ('link_origin', 'link_com') DEFAULT: "link_origin"

relative

Whether to report Genesis user-frame positions with the morph pose offset stripped. Defaults to False, which returns solver/world-frame positions. Only affects ref="link_origin".

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
pos

The link positions.

TYPE: (Tensor, shape(n_links, 3) or (n_envs, n_links, 3))

Source code in eden/entities/rigid.py
def get_links_pos(self, ls_idx_local=None, envs_idx=None, *, ref="link_origin", relative: bool = False):
    """Return the positions of the entity's links.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    ref : {"link_origin", "link_com"} | gs.link_ref_frame, optional
        Point on each link whose position is reported: the link frame origin, or the link's center of mass.
        Defaults to "link_origin".
    relative : bool, optional
        Whether to report Genesis user-frame positions with the morph pose offset stripped. Defaults to False,
        which returns solver/world-frame positions. Only affects ``ref="link_origin"``.

    Returns
    -------
    pos : torch.Tensor, shape (n_links, 3) or (n_envs, n_links, 3)
        The link positions.
    """
    pos = self._entity.get_links_pos(
        links_idx_local=_genesis_selection(ls_idx_local),
        envs_idx=envs_idx,
        ref=resolve_link_ref_frame(ref),
        relative=relative,
    )
    return _row_major_selection(pos, ls_idx_local)
get_links_quat(ls_idx_local=None, envs_idx=None, *, relative: bool = False)

Return the orientations of the entity's links.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Whether to report Genesis user-frame orientations with the morph pose offset stripped. Defaults to False, which returns solver/world-frame orientations.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
quat

The link quaternions in (w, x, y, z) format.

TYPE: (Tensor, shape(n_links, 4) or (n_envs, n_links, 4))

Source code in eden/entities/rigid.py
def get_links_quat(self, ls_idx_local=None, envs_idx=None, *, relative: bool = False):
    """Return the orientations of the entity's links.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Whether to report Genesis user-frame orientations with the morph pose offset stripped. Defaults to False,
        which returns solver/world-frame orientations.

    Returns
    -------
    quat : torch.Tensor, shape (n_links, 4) or (n_envs, n_links, 4)
        The link quaternions in ``(w, x, y, z)`` format.
    """
    quat = self._entity.get_links_quat(
        links_idx_local=_genesis_selection(ls_idx_local), envs_idx=envs_idx, relative=relative
    )
    return _row_major_selection(quat, ls_idx_local)
get_links_vel(ls_idx_local=None, envs_idx=None, *, ref='link_origin', relative: bool = False)

Return the linear velocities of the entity's links, in world coordinates.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

ref

Point on each link at which the velocity is expressed. A rotating link gives different answers at its origin and its center of mass. Defaults to "link_origin".

TYPE: ('link_origin', 'link_com') DEFAULT: "link_origin"

relative

Whether to report the velocity of the Genesis user-frame link origin, with the morph pose offset and inertial alignment stripped. Defaults to False, which returns the velocity of the solver/world-frame origin :meth:get_links_pos reports. The two differ by the transport term omega x d over the offset d, and only for ref="link_origin".

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
vel

The link linear velocities.

TYPE: (Tensor, shape(n_links, 3) or (n_envs, n_links, 3))

Source code in eden/entities/rigid.py
def get_links_vel(self, ls_idx_local=None, envs_idx=None, *, ref="link_origin", relative: bool = False):
    """Return the linear velocities of the entity's links, in world coordinates.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    ref : {"link_origin", "link_com"} | gs.link_ref_frame, optional
        Point on each link at which the velocity is expressed. A rotating link gives
        different answers at its origin and its center of mass. Defaults to "link_origin".
    relative : bool, optional
        Whether to report the velocity of the Genesis user-frame link origin, with the morph pose offset and
        inertial alignment stripped. Defaults to False, which returns the velocity of the solver/world-frame
        origin :meth:`get_links_pos` reports. The two differ by the transport term ``omega x d`` over the offset
        ``d``, and only for ``ref="link_origin"``.

    Returns
    -------
    vel : torch.Tensor, shape (n_links, 3) or (n_envs, n_links, 3)
        The link linear velocities.
    """
    vel = self._entity.get_links_vel(
        links_idx_local=_genesis_selection(ls_idx_local),
        envs_idx=envs_idx,
        ref=resolve_link_ref_frame(ref),
        relative=relative,
    )
    return _row_major_selection(vel, ls_idx_local)
get_links_ang(ls_idx_local=None, envs_idx=None)

Return the angular velocities of the entity's links, in world coordinates.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
ang

The link angular velocities, as rotation vectors in rad/s.

TYPE: (Tensor, shape(n_links, 3) or (n_envs, n_links, 3))

Source code in eden/entities/rigid.py
def get_links_ang(self, ls_idx_local=None, envs_idx=None):
    """Return the angular velocities of the entity's links, in world coordinates.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.

    Returns
    -------
    ang : torch.Tensor, shape (n_links, 3) or (n_envs, n_links, 3)
        The link angular velocities, as rotation vectors in rad/s.
    """
    return _row_major_selection(
        self._entity.get_links_ang(links_idx_local=_genesis_selection(ls_idx_local), envs_idx=envs_idx),
        ls_idx_local,
    )
get_links_acc(ls_idx_local=None, envs_idx=None, *, relative: bool = False)

Return the linear accelerations of the entity's link origins, in world coordinates.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Whether to report the acceleration of the Genesis user-frame link origin, with the morph pose offset and inertial alignment stripped. Defaults to False, which returns the acceleration of the solver/world-frame origin :meth:get_links_pos reports.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
acc

The link linear accelerations.

TYPE: (Tensor, shape(n_links, 3) or (n_envs, n_links, 3))

Source code in eden/entities/rigid.py
def get_links_acc(self, ls_idx_local=None, envs_idx=None, *, relative: bool = False):
    """Return the linear accelerations of the entity's link origins, in world coordinates.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Whether to report the acceleration of the Genesis user-frame link origin, with the morph pose offset and
        inertial alignment stripped. Defaults to False, which returns the acceleration of the solver/world-frame
        origin :meth:`get_links_pos` reports.

    Returns
    -------
    acc : torch.Tensor, shape (n_links, 3) or (n_envs, n_links, 3)
        The link linear accelerations.
    """
    acc = self._entity.get_links_acc(
        links_idx_local=_genesis_selection(ls_idx_local), envs_idx=envs_idx, relative=relative
    )
    return _row_major_selection(acc, ls_idx_local)
get_links_acc_ang(ls_idx_local=None, envs_idx=None)

Return the angular accelerations of the entity's links, in world coordinates.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

RETURNS DESCRIPTION
acc_ang

The link angular accelerations, in rad/s^2.

TYPE: (Tensor, shape(n_links, 3) or (n_envs, n_links, 3))

Source code in eden/entities/rigid.py
def get_links_acc_ang(self, ls_idx_local=None, envs_idx=None):
    """Return the angular accelerations of the entity's links, in world coordinates.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.

    Returns
    -------
    acc_ang : torch.Tensor, shape (n_links, 3) or (n_envs, n_links, 3)
        The link angular accelerations, in rad/s^2.
    """
    acc_ang = self._entity.get_links_acc_ang(links_idx_local=_genesis_selection(ls_idx_local), envs_idx=envs_idx)
    return _row_major_selection(acc_ang, ls_idx_local)
get_links_inertial_mass(ls_idx_local=None, envs_idx=None) -> Tensor

Return the per-link mass the solver currently uses.

This is each link's own mass — unrelated to :meth:get_mass, which returns the entity's total mass. Genesis holds a single per-link mass channel, so mass-randomization events write here too: :meth:set_mass_shift moves this value, and the pre-randomization nominal is what :meth:get_mass_shift subtracts.

The leading environment dimension is always present, and envs_idx always works. Genesis, underneath, stores link info unbatched unless EnvOptions.batch_links_info is set and then refuses envs_idx outright; in that mode every environment shares the one stored value, so broadcasting it is exact and callers get the same shape either way — the one the per-link DR readback getters (:meth:get_mass_shift and friends) return.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments are considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
inertial_mass

The mass of each requested link, in kg. Always a copy, never a live view into the solver.

TYPE: (Tensor, shape(n_envs, n_links))

Source code in eden/entities/rigid.py
def get_links_inertial_mass(self, ls_idx_local=None, envs_idx=None) -> torch.Tensor:
    """Return the per-link mass the solver currently uses.

    This is each link's *own* mass — unrelated to :meth:`get_mass`, which returns the
    entity's **total** mass. Genesis holds a single per-link mass channel, so
    mass-randomization events write here too: :meth:`set_mass_shift` moves this value,
    and the pre-randomization nominal is what :meth:`get_mass_shift` subtracts.

    The leading environment dimension is always present, and ``envs_idx`` always
    works. Genesis, underneath, stores link info unbatched unless
    ``EnvOptions.batch_links_info`` is set and then refuses ``envs_idx`` outright; in
    that mode every environment shares the one stored value, so broadcasting it is
    exact and callers get the same shape either way — the one the per-link DR readback
    getters (:meth:`get_mass_shift` and friends) return.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments are considered. Defaults to None.

    Returns
    -------
    inertial_mass : torch.Tensor, shape (n_envs, n_links)
        The mass of each requested link, in kg. Always a copy, never a live
        view into the solver.
    """
    envs_idx = sanitize_envs_idx(envs_idx, self._env.num_envs, prefer_slice=False)
    ls_idx_local = _genesis_selection(ls_idx_local)
    if self.has_batched_links_info:
        return self._entity.get_links_mass(ls_idx_local, envs_idx)
    per_link = self._entity.get_links_mass(ls_idx_local)  # (n_links,)
    return per_link.unsqueeze(0).expand(self._env.num_envs, *per_link.shape)[envs_idx].contiguous()
get_links_invweight(ls_idx_local=None, envs_idx=None) -> Tensor

Return the inverse effective inertia (invweight) of the entity's links.

Two numbers per link — translational and rotational — giving the acceleration response of the link to a unit force / torque applied at its center of mass with the rest of the articulated body attached. Genesis derives them from link masses, poses, inertia matrices, joint placements and DOF armatures, so they are read-only: the per-link counterpart of :meth:get_dofs_invweight.

Carries a leading environment dimension and accepts envs_idx in either EnvOptions.batch_links_info mode — see :meth:get_links_inertial_mass for why.

PARAMETER DESCRIPTION
ls_idx_local

The local link indices. If None, all links are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The environment indices (int / slice / index tensor / bool mask). If None, all environments are considered. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
invweight

The translational and rotational inverse effective inertia of each requested link. Always a copy, never a live view into the solver.

TYPE: (Tensor, shape(n_envs, n_links, 2))

Source code in eden/entities/rigid.py
def get_links_invweight(self, ls_idx_local=None, envs_idx=None) -> torch.Tensor:
    """Return the inverse effective inertia (``invweight``) of the entity's links.

    Two numbers per link — translational and rotational — giving the acceleration
    response of the link to a unit force / torque applied at its center of mass with
    the rest of the articulated body attached. Genesis derives them from link masses,
    poses, inertia matrices, joint placements and DOF armatures, so they are read-only:
    the per-link counterpart of :meth:`get_dofs_invweight`.

    Carries a leading environment dimension and accepts ``envs_idx`` in either
    ``EnvOptions.batch_links_info`` mode — see :meth:`get_links_inertial_mass` for why.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        The local link indices. If None, all links are considered. Defaults to None.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        The environment indices (int / slice / index tensor / bool mask). If None, all
        environments are considered. Defaults to None.

    Returns
    -------
    invweight : torch.Tensor, shape (n_envs, n_links, 2)
        The translational and rotational inverse effective inertia of each requested
        link. Always a copy, never a live view into the solver.
    """
    envs_idx = sanitize_envs_idx(envs_idx, self._env.num_envs, prefer_slice=False)
    if self.has_batched_links_info:
        return self._entity.get_links_invweight(ls_idx_local, envs_idx)
    per_link = self._entity.get_links_invweight(ls_idx_local)  # (n_links, 2)
    return per_link.unsqueeze(0).expand(self._env.num_envs, *per_link.shape)[envs_idx].contiguous()
get_links_net_contact_force()

Return the net contact force on each of the entity's links, in world coordinates.

Covers direct external contacts only — constraint and actuator forces are not included. Values come from the most recent scene.step(). Unlike the other link accessors this one takes no indices: it always returns every link, for every environment.

RETURNS DESCRIPTION
force

The net contact force on each link, in N.

TYPE: (Tensor, shape(n_links, 3) or (n_envs, n_links, 3))

Source code in eden/entities/rigid.py
def get_links_net_contact_force(self):
    """Return the net contact force on each of the entity's links, in world coordinates.

    Covers direct external contacts only — constraint and actuator forces are not
    included. Values come from the most recent ``scene.step()``. Unlike the other
    link accessors this one takes no indices: it always returns every link, for
    every environment.

    Returns
    -------
    force : torch.Tensor, shape (n_links, 3) or (n_envs, n_links, 3)
        The net contact force on each link, in N.
    """
    return self._entity.get_links_net_contact_force()

get_contacts

get_contacts(with_entity: Entity | None = None, exclude_self_contact: bool = False, *, is_padded: bool = False)

Return contact information computed during the most recent scene.step().

If with_entity is provided, only returns contact information involving the caller and the specified entity. Otherwise, returns all contact information involving the caller entity. When with_entity is self, it will return the self-collision only.

The returned dict contains the following keys (a contact pair consists of two geoms: A and B):

  • 'geom_a' : The global geom index of geom A in the contact pair. (actual geom object can be obtained by scene.rigid_solver.geoms[geom_a])
  • 'geom_b' : The global geom index of geom B in the contact pair. (actual geom object can be obtained by scene.rigid_solver.geoms[geom_b])
  • 'link_a' : The global link index of link A (that contains geom A) in the contact pair. (actual link object can be obtained by scene.rigid_solver.links[link_a])
  • 'link_b' : The global link index of link B (that contains geom B) in the contact pair. (actual link object can be obtained by scene.rigid_solver.links[link_b])
  • 'position' : The contact position in world frame.
  • 'force_a' : The contact force applied to geom A.
  • 'force_b' : The contact force applied to geom B.
  • 'valid_mask' : A boolean mask indicating whether the contact information is valid. (Only when scene is parallelized)

The shape of each entry is (n_envs, n_contacts, ...) for scene with parallel envs and (n_contacts, ...) for non-parallelized scene.

PARAMETER DESCRIPTION
with_entity

The entity to check contact with. Defaults to None.

TYPE: Entity DEFAULT: None

exclude_self_contact

Exclude the self collision from the returning contacts. Defaults to False.

TYPE: bool DEFAULT: False

is_padded

Return every contact slot of each env, invalid ones masked out by valid_mask and their floating-point fields zeroed, instead of trimming to the most contacts any env has. Trimming reads that count back to the host (a GPU->CPU sync) and sizes the result by it; the padded form does neither, at the cost of masking the full capacity. While the env's graded phase runs under a CUDA graph (EnvOptions.cuda_graph_options) the padded form is returned regardless (see :meth:~eden.entities.base.Entity._query_contacts): a reader that respects valid_mask (which every reader must, the trimmed form masking out other entities' contacts too) gets the same contacts. Defaults to False.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
contact_info

The contact information.

TYPE: dict

Source code in eden/entities/rigid.py
def get_contacts(
    self, with_entity: Entity | None = None, exclude_self_contact: bool = False, *, is_padded: bool = False
):
    """Return contact information computed during the most recent ``scene.step()``.

    If ``with_entity`` is provided, only returns contact information involving the caller and the specified entity.
    Otherwise, returns all contact information involving the caller entity.
    When ``with_entity`` is ``self``, it will return the self-collision only.

    The returned dict contains the following keys (a contact pair consists of two geoms: A and B):

    - 'geom_a'     : The global geom index of geom A in the contact pair.
                    (actual geom object can be obtained by scene.rigid_solver.geoms[geom_a])
    - 'geom_b'     : The global geom index of geom B in the contact pair.
                    (actual geom object can be obtained by scene.rigid_solver.geoms[geom_b])
    - 'link_a'     : The global link index of link A (that contains geom A) in the contact pair.
                    (actual link object can be obtained by scene.rigid_solver.links[link_a])
    - 'link_b'     : The global link index of link B (that contains geom B) in the contact pair.
                    (actual link object can be obtained by scene.rigid_solver.links[link_b])
    - 'position'   : The contact position in world frame.
    - 'force_a'    : The contact force applied to geom A.
    - 'force_b'    : The contact force applied to geom B.
    - 'valid_mask' : A boolean mask indicating whether the contact information is valid.
                    (Only when scene is parallelized)

    The shape of each entry is (n_envs, n_contacts, ...) for scene with parallel envs
                           and (n_contacts, ...) for non-parallelized scene.

    Parameters
    ----------
    with_entity : Entity, optional
        The entity to check contact with. Defaults to None.
    exclude_self_contact: bool
        Exclude the self collision from the returning contacts. Defaults to False.
    is_padded: bool
        Return every contact slot of each env, invalid ones masked out by ``valid_mask`` and their
        floating-point fields zeroed, instead of trimming to the most contacts any env has. Trimming reads
        that count back to the host (a GPU->CPU sync) and sizes the result by it; the padded form does
        neither, at the cost of masking the full capacity. While the env's graded phase runs under a CUDA
        graph (``EnvOptions.cuda_graph_options``) the padded form is returned regardless (see
        :meth:`~eden.entities.base.Entity._query_contacts`): a reader that respects ``valid_mask`` (which
        every reader must, the trimmed form masking out other entities' contacts too) gets the same
        contacts. Defaults to False.

    Returns
    -------
    contact_info : dict
        The contact information.
    """
    if isinstance(with_entity, Entity):
        with_entity = with_entity._entity
    return self._query_contacts(
        lambda padded: self._entity.get_contacts(
            with_entity=with_entity, exclude_self_contact=exclude_self_contact, is_padded=padded
        ),
        is_padded,
    )

get_grouped_contacts

get_grouped_contacts(other_entity: 'RigidEntity', *, links_a_idx_local: Tensor | list[int] | None = None, links_b_idx_local: Tensor | list[int] | None = None) -> dict[str, Tensor]

Force-weighted contact aggregation per (self link, other link) pair.

Layered on top of :meth:get_contacts: per (link_a, link_b) pair, compute the contact-force-norm-weighted mean of contact positions across every contact between this entity (self) and other_entity in the most recent scene.step().

The output canonicalises orientation: axis 1 always indexes self's links and axis 2 always indexes other_entity's links, regardless of which side Genesis happened to label "a" or "b" for any given contact.

Port of dexmachina/envs/contacts.get_grouped_contact_pos.

PARAMETER DESCRIPTION
other_entity

The contact partner. Must be a different entity (other is not self); self-contact is not supported by this layer — use :meth:get_contacts directly with with_entity=self.

TYPE: 'RigidEntity'

links_a_idx_local

Local link indices on self to include on axis 1. Defaults to all of self's links ([0, num_links)). For per-step consumers, pre-resolve once at build() time and pass a cached torch.long tensor on self.device to avoid a tiny H2D copy each call (no-op early-return path).

TYPE: Tensor | list[int] | None DEFAULT: None

links_b_idx_local

Local link indices on other_entity to include on axis 2. Defaults to all of other_entity's links. Same hot-path note as above.

TYPE: Tensor | list[int] | None DEFAULT: None

RETURNS DESCRIPTION
dict with:
- ``position`` : ``(num_envs, n_a, n_b, 3)`` — contact-force-norm

weighted mean position in world frame; zero where valid is False.

- ``valid`` : ``(num_envs, n_a, n_b)`` bool — True iff the pair's

aggregate force-norm is strictly positive (force_norm_sum > 0), matching the DexMachina reference. Pairs whose contacts all carry zero force — a quasi-static initial overlap or the frame of make/break — are reported invalid because no force-weighted mean position is defined for them. Callers that want pure geometric "are they touching" detection should bool-AND on the raw get_contacts(...)["valid_mask"] themselves.

- ``force_norm_sum`` : ``(num_envs, n_a, n_b)`` — sum of contact

force magnitudes (Σ ‖f_i‖, not ‖Σ f_i‖; opposing-direction contacts do not cancel) across the pair. The denominator used to weight the mean; useful as a contact-strength feature in its own right.

Notes

Requires a parallelised scene (num_envs >= 1); the non-parallelised path is not implemented since the only consumers — RL reward / observation terms — always run parallelised.

Source code in eden/entities/rigid.py
def get_grouped_contacts(
    self,
    other_entity: "RigidEntity",
    *,
    links_a_idx_local: torch.Tensor | list[int] | None = None,
    links_b_idx_local: torch.Tensor | list[int] | None = None,
) -> dict[str, torch.Tensor]:
    """Force-weighted contact aggregation per (self link, other link) pair.

    Layered on top of :meth:`get_contacts`: per (link_a, link_b) pair,
    compute the contact-force-norm-weighted mean of contact positions
    across every contact between *this* entity (``self``) and
    ``other_entity`` in the most recent ``scene.step()``.

    The output canonicalises orientation: axis 1 always indexes
    ``self``'s links and axis 2 always indexes ``other_entity``'s
    links, regardless of which side Genesis happened to label "a" or
    "b" for any given contact.

    Port of ``dexmachina/envs/contacts.get_grouped_contact_pos``.

    Parameters
    ----------
    other_entity:
        The contact partner. Must be a different entity (``other is not
        self``); self-contact is not supported by this layer — use
        :meth:`get_contacts` directly with ``with_entity=self``.
    links_a_idx_local:
        Local link indices on ``self`` to include on axis 1. Defaults
        to all of ``self``'s links (``[0, num_links)``). For per-step
        consumers, pre-resolve once at ``build()`` time and pass a
        cached ``torch.long`` tensor on ``self.device`` to avoid a
        tiny H2D copy each call (no-op early-return path).
    links_b_idx_local:
        Local link indices on ``other_entity`` to include on axis 2.
        Defaults to all of ``other_entity``'s links. Same hot-path
        note as above.

    Returns
    -------
    dict with:

    - ``position`` : ``(num_envs, n_a, n_b, 3)`` — contact-force-norm
      weighted mean position in world frame; zero where ``valid`` is
      False.
    - ``valid`` : ``(num_envs, n_a, n_b)`` bool — True iff the pair's
      aggregate force-norm is strictly positive (``force_norm_sum >
      0``), matching the DexMachina reference. Pairs whose contacts
      all carry zero force — a quasi-static initial overlap or the
      frame of make/break — are reported invalid because no
      force-weighted mean position is defined for them. Callers that
      want pure geometric "are they touching" detection should
      bool-AND on the raw ``get_contacts(...)["valid_mask"]``
      themselves.
    - ``force_norm_sum`` : ``(num_envs, n_a, n_b)`` — sum of contact
      force magnitudes (``Σ ‖f_i‖``, **not** ``‖Σ f_i‖``;
      opposing-direction contacts do not cancel) across the pair.
      The denominator used to weight the mean; useful as a
      contact-strength feature in its own right.

    Notes
    -----
    Requires a parallelised scene (``num_envs >= 1``); the
    non-parallelised path is not implemented since the only consumers
    — RL reward / observation terms — always run parallelised.
    """
    if not isinstance(other_entity, RigidEntity):
        raise TypeError(f"other_entity must be a RigidEntity, got {type(other_entity).__name__}")
    if other_entity._entity.idx == self._entity.idx:
        raise ValueError(
            "get_grouped_contacts does not support self-contact; use get_contacts(with_entity=self) directly."
        )

    contacts = self.get_contacts(with_entity=other_entity)
    if "valid_mask" not in contacts:
        raise NotImplementedError(
            "get_grouped_contacts requires the env-leading contact layout "
            "(`valid_mask` field present in get_contacts output). Genesis "
            "scenes built with `n_envs == 0` return non-parallelised "
            "contacts without this field; rebuild with `num_envs >= 1` if "
            "you need this aggregation."
        )

    device = contacts["position"].device
    a_local = resolve_local_link_idx(links_a_idx_local, self.num_links, device)
    b_local = resolve_local_link_idx(links_b_idx_local, other_entity.num_links, device)
    a_global = a_local + self.link_start
    b_global = b_local + other_entity.link_start
    return aggregate_grouped_contacts(
        link_a=contacts["link_a"],
        link_b=contacts["link_b"],
        position=contacts["position"],
        force=contacts["force_a"],
        valid_mask=contacts["valid_mask"],
        a_global=a_global,
        b_global=b_global,
    )

detect_collision

detect_collision(env_idx=0)

Detect collision for the entity. This only supports a single environment.

Source code in eden/entities/rigid.py
def detect_collision(self, env_idx=0):
    """Detect collision for the entity. This only supports a single environment."""
    return self._entity.detect_collision(env_idx=env_idx)

set_pos

set_pos(pos, envs_idx=None, *, relative: bool = False) -> None

Set the position of the entity's base link.

PARAMETER DESCRIPTION
pos

The position to set.

TYPE: array_like

envs_idx

The environment indices. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

Whether pos is expressed in Genesis's user frame, with the morph pose offset and inertial alignment applied to reach the solver/world frame. Defaults to False: pos is the solver/world-frame position.

TYPE: bool DEFAULT: False

Source code in eden/entities/rigid.py
def set_pos(
    self,
    pos,
    envs_idx=None,
    *,
    relative: bool = False,
) -> None:
    """Set the position of the entity's base link.

    Parameters
    ----------
    pos : array_like
        The position to set.
    envs_idx : None | array_like, optional
        The environment indices. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        Whether ``pos`` is expressed in Genesis's user frame, with the morph pose offset and inertial alignment
        applied to reach the solver/world frame. Defaults to False: ``pos`` is the solver/world-frame position.
    """
    self._entity.set_pos(
        pos,
        envs_idx=envs_idx,
        relative=relative,
    )

set_quat

set_quat(quat, envs_idx=None, *, relative: bool = True) -> None

Set the entity's orientation.

PARAMETER DESCRIPTION
quat

The orientation to set in (w, x, y, z) format.

TYPE: array_like

envs_idx

The indices of the environments. If None, all environments are considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

relative

If True, quat is composed with the morph pose offset in Genesis's user frame. If False, quat is written directly in the solver/world frame. Defaults to True.

TYPE: bool DEFAULT: True

Source code in eden/entities/rigid.py
def set_quat(
    self,
    quat,
    envs_idx=None,
    *,
    relative: bool = True,
) -> None:
    """Set the entity's orientation.

    Parameters
    ----------
    quat : array_like
        The orientation to set in ``(w, x, y, z)`` format.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments are considered. Defaults to None.
    relative : bool, optional
        If True, ``quat`` is composed with the morph pose offset in Genesis's user frame. If False, ``quat`` is
        written directly in the solver/world frame. Defaults to True.
    """
    self._entity.set_quat(
        quat,
        envs_idx=envs_idx,
        relative=relative,
    )

set_qpos

set_qpos(qpos, qs_idx_local=None, envs_idx=None, *, zero_velocity=True, skip_forward=False) -> None

Teleport the entity by setting its generalized coordinates.

PARAMETER DESCRIPTION
qpos

The generalized coordinates to write, ordered to match qs_idx_local. With a bool-mask envs_idx, every env's row (num_envs, n_qs) is accepted too, and the mask picks which are written — with :attr:setters_take_every_row, the sync-free form for a reset.

TYPE: (array_like, shape(n_qs) or (len(envs_idx), n_qs))

qs_idx_local

Local q indices to write. If None, writes the entity's own q range in dofs_name order (root qs first for a floating base). Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

zero_velocity

Whether to zero the entity's velocities after the write, so the teleport does not leave stale momentum behind. Defaults to True.

TYPE: bool DEFAULT: True

skip_forward

Whether to skip the forward-kinematics pass that refreshes link poses. Set True only when a later setter in the same reset will trigger it anyway. Defaults to False.

TYPE: bool DEFAULT: False

Source code in eden/entities/rigid.py
def set_qpos(
    self,
    qpos,
    qs_idx_local=None,
    envs_idx=None,
    *,
    zero_velocity=True,
    skip_forward=False,
) -> None:
    """Teleport the entity by setting its generalized coordinates.

    Parameters
    ----------
    qpos : array_like, shape (n_qs,) or (len(envs_idx), n_qs)
        The generalized coordinates to write, ordered to match ``qs_idx_local``. With a bool-mask
        ``envs_idx``, every env's row ``(num_envs, n_qs)`` is accepted too, and the mask picks which
        are written — with :attr:`setters_take_every_row`, the sync-free form for a reset.
    qs_idx_local : None | array_like, optional
        Local q indices to write. If None, writes the entity's own q range in
        dofs_name order (root qs first for a floating base). Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    zero_velocity : bool, optional
        Whether to zero the entity's velocities after the write, so the teleport does
        not leave stale momentum behind. Defaults to True.
    skip_forward : bool, optional
        Whether to skip the forward-kinematics pass that refreshes link poses. Set True
        only when a later setter in the same reset will trigger it anyway. Defaults to False.
    """
    if qs_idx_local is None:
        qs_idx_local = self._genesis_qs_idx
    else:
        qs_idx_local = _genesis_selection(qs_idx_local)
    qpos, envs_idx = _mask_rows(
        qpos, envs_idx, on_device=isinstance(qs_idx_local, range) and self.setters_take_every_row
    )
    self._entity.set_qpos(
        qpos,
        qs_idx_local,
        envs_idx=envs_idx,
        zero_velocity=zero_velocity,
        skip_forward=skip_forward,
    )

set_friction

set_friction(friction: float) -> None

Set the friction coefficient for all of the entity's geoms.

The friction coefficient must be in range [1e-2, 5.0] for simulation stability.

PARAMETER DESCRIPTION
friction

The friction coefficient to set for all the entity's links.

TYPE: float

Source code in eden/entities/rigid.py
def set_friction(self, friction: float) -> None:
    """Set the friction coefficient for all of the entity's geoms.

    The friction coefficient must be in range [1e-2, 5.0] for simulation stability.

    Parameters
    ----------
    friction : float
        The friction coefficient to set for all the entity's links.
    """
    self._entity.set_friction(friction)

set_friction_ratio

set_friction_ratio(friction_ratio, ls_idx_local, envs_idx=None) -> None

Set per-link friction ratio (Genesis).

PARAMETER DESCRIPTION
friction_ratio

Follows the Genesis contract — shaped (len(envs_idx), len(ls_idx_local)) (i.e. already indexed by envs_idx), or any shape broadcastable to it.

TYPE: Tensor

ls_idx_local

Local link indices whose geoms receive the ratio.

TYPE: array_like

envs_idx

Environments to write; None writes all. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_friction_ratio(self, friction_ratio, ls_idx_local, envs_idx=None) -> None:
    """Set per-link friction ratio (Genesis).

    Parameters
    ----------
    friction_ratio : torch.Tensor
        Follows the Genesis contract — shaped ``(len(envs_idx), len(ls_idx_local))``
        (i.e. already indexed by ``envs_idx``), or any shape broadcastable to it.
    ls_idx_local : array_like
        Local link indices whose geoms receive the ratio.
    envs_idx : None | array_like, optional
        Environments to write; ``None`` writes all. Defaults to ``None``.
    """
    self._entity.set_friction_ratio(friction_ratio, ls_idx_local, envs_idx=envs_idx)
links_with_anchored_inertials(ls_idx_local, quantity: Literal['mass', 'COM']) -> list[str]

Names of the selected links whose quantity Genesis refuses to write after build.

Genesis anchors the frame of an aligned free body (EntityOptions.align, on by default for one free rigid body with nothing articulated under it, and always for a primitive) on the centre of mass and principal axes of all its links, and then rejects a runtime write that would move the anchor: any COM write, and a mass write unless the body is that one link. Mirrors the guard in genesis' RigidSolver._set_links_info so a randomization event can refuse at build() rather than on its first write. That guard also skips a link index held on a GPU outside gs.debug (reading it back would stall the device), which is how Eden's events pass it, so on GPU the unsupported write would otherwise go through unchecked.

PARAMETER DESCRIPTION
ls_idx_local

Local link indices the caller will write.

TYPE: array_like

quantity

Which inertial property will be written.

TYPE: ('mass', 'COM') DEFAULT: "mass"

RETURNS DESCRIPTION
list[str]

The offending link names, in ls_idx_local order; empty when every write is allowed.

Source code in eden/entities/rigid.py
def links_with_anchored_inertials(self, ls_idx_local, quantity: Literal["mass", "COM"]) -> list[str]:
    """Names of the selected links whose ``quantity`` Genesis refuses to write after build.

    Genesis anchors the frame of an *aligned* free body (``EntityOptions.align``, on by default
    for one free rigid body with nothing articulated under it, and always for a primitive) on the
    centre of mass and principal axes of all its links, and then rejects a runtime write that
    would move the anchor: any COM write, and a mass write unless the body is that one link.
    Mirrors the guard in genesis' ``RigidSolver._set_links_info`` so a randomization event can
    refuse at ``build()`` rather than on its first write. That guard also skips a link index held
    on a GPU outside ``gs.debug`` (reading it back would stall the device), which is how Eden's
    events pass it, so on GPU the unsupported write would otherwise go through unchecked.

    Parameters
    ----------
    ls_idx_local : array_like
        Local link indices the caller will write.
    quantity : {"mass", "COM"}
        Which inertial property will be written.

    Returns
    -------
    list[str]
        The offending link names, in ``ls_idx_local`` order; empty when every write is allowed.
    """
    solver_links = self._env.rigid_solver.links

    def anchor_of(link):
        # A link hangs off its body's anchor through fixed joints only.
        while link.parent_idx != -1 and all(joint.type == gs.JOINT_TYPE.FIXED for joint in link.joints):
            link = solver_links[link.parent_idx]
        return link.idx if link.aligned else None

    anchors = [anchor_of(link) for link in solver_links]
    blocked = []
    for i_l in torch.as_tensor(ls_idx_local).reshape(-1).tolist():
        link = self._entity.links[i_l]
        anchor = anchors[link.idx]
        if anchor is None or (quantity == "mass" and anchors.count(anchor) == 1):
            continue
        blocked.append(link.name)
    return blocked

set_mass_shift

set_mass_shift(mass_shift, ls_idx_local, envs_idx=None) -> None

Set per-link mass shift, in kg, relative to the nominal (unrandomized) mass.

The resulting mass (nominal + mass_shift) must stay strictly positive — neither Eden nor Genesis checks it here, since a device-side check would synchronize every write. The mass-randomization event terms validate their ranges at build().

A link fixed to the world (links[i].is_fixed: every joint from it to the root is fixed, as on a fixed-base arm's base link, a mounted hand's palm or a fixed=True prop) is written like any other, whatever form ls_idx_local takes, and :meth:get_mass_shift reads the value back — but the solver never reads that link's mass, so the shift changes no dynamics. The mass-randomization event terms reject such a link at build().

PARAMETER DESCRIPTION
mass_shift

Shaped (len(envs_idx), len(ls_idx_local)) (already indexed by envs_idx), or broadcastable to it. Per-env values require EnvOptions.batch_links_info=True.

TYPE: Tensor

ls_idx_local

Local link indices to shift.

TYPE: array_like

envs_idx

Environments to write; None writes all. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_mass_shift(self, mass_shift, ls_idx_local, envs_idx=None) -> None:
    """Set per-link mass shift, in kg, relative to the nominal (unrandomized) mass.

    The resulting mass (``nominal + mass_shift``) must stay strictly positive — neither
    Eden nor Genesis checks it here, since a device-side check would synchronize every
    write. The mass-randomization event terms validate their ranges at ``build()``.

    A link fixed to the world (``links[i].is_fixed``: every joint from it to the root is
    fixed, as on a fixed-base arm's base link, a mounted hand's palm or a ``fixed=True``
    prop) is written like any other, whatever form ``ls_idx_local`` takes, and
    :meth:`get_mass_shift` reads the value back — but the solver never reads that link's
    mass, so the shift changes no dynamics. The mass-randomization event terms reject such
    a link at ``build()``.

    Parameters
    ----------
    mass_shift : torch.Tensor
        Shaped ``(len(envs_idx), len(ls_idx_local))`` (already indexed by
        ``envs_idx``), or broadcastable to it. Per-env values require
        ``EnvOptions.batch_links_info=True``.
    ls_idx_local : array_like
        Local link indices to shift.
    envs_idx : None | array_like, optional
        Environments to write; ``None`` writes all. Defaults to ``None``.
    """
    nominal_mass, _ = self._nominal_links_inertials()
    base, ls_idx_local, envs_idx = self._links_write_base(nominal_mass, ls_idx_local, envs_idx)
    self._entity.set_links_mass(base + mass_shift, ls_idx_local, envs_idx=envs_idx)

set_COM_shift

set_COM_shift(com_shift, ls_idx_local, envs_idx=None) -> None

Set per-link COM shift, in meters in the link frame, relative to the nominal COM.

As with :meth:set_mass_shift, a link fixed to the world is written and reads back, but its COM changes no dynamics; :class:~eden.managers.terms.events.domain_rand.RandomizeComShift rejects such a link at build().

PARAMETER DESCRIPTION
com_shift

Shaped (len(envs_idx), len(ls_idx_local), 3) (already indexed by envs_idx), or broadcastable to it. Per-env values require EnvOptions.batch_links_info=True.

TYPE: Tensor

ls_idx_local

Local link indices to shift.

TYPE: array_like

envs_idx

Environments to write; None writes all. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_COM_shift(self, com_shift, ls_idx_local, envs_idx=None) -> None:
    """Set per-link COM shift, in meters in the link frame, relative to the nominal COM.

    As with :meth:`set_mass_shift`, a link fixed to the world is written and reads back, but
    its COM changes no dynamics; :class:`~eden.managers.terms.events.domain_rand.RandomizeComShift`
    rejects such a link at ``build()``.

    Parameters
    ----------
    com_shift : torch.Tensor
        Shaped ``(len(envs_idx), len(ls_idx_local), 3)`` (already indexed by
        ``envs_idx``), or broadcastable to it. Per-env values require
        ``EnvOptions.batch_links_info=True``.
    ls_idx_local : array_like
        Local link indices to shift.
    envs_idx : None | array_like, optional
        Environments to write; ``None`` writes all. Defaults to ``None``.
    """
    _, nominal_com = self._nominal_links_inertials()
    base, ls_idx_local, envs_idx = self._links_write_base(nominal_com, ls_idx_local, envs_idx)
    self._entity.set_links_COM(base + com_shift, ls_idx_local, envs_idx=envs_idx)

get_mass_shift

get_mass_shift(ls_idx_local=None, envs_idx=None) -> Tensor

Per-env link mass shift (kg) vs the nominal (unrandomized) mass; 0 if none.

Reads the live Genesis solver value and subtracts the nominal, so the result always reflects the randomization currently in effect.

PARAMETER DESCRIPTION
ls_idx_local

Local link indices to read; None reads all links. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

Environments to read (int / slice / index tensor / bool mask); None reads all. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
Tensor

Copy shaped (num_envs_or_subset, n_links_or_subset).

Source code in eden/entities/rigid.py
def get_mass_shift(self, ls_idx_local=None, envs_idx=None) -> torch.Tensor:
    """Per-env link mass shift (kg) vs the nominal (unrandomized) mass; 0 if none.

    Reads the live Genesis solver value and subtracts the nominal, so the result
    always reflects the randomization currently in effect.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        Local link indices to read; ``None`` reads all links. Defaults to ``None``.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        Environments to read (int / slice / index tensor / bool mask); ``None``
        reads all. Defaults to ``None``.

    Returns
    -------
    torch.Tensor
        Copy shaped ``(num_envs_or_subset, n_links_or_subset)``.
    """
    nominal_mass, _ = self._nominal_links_inertials()
    live = self.get_links_inertial_mass(ls_idx_local, envs_idx)  # (n_envs_sel, n_ls), a copy
    return live - self._select_links_info(nominal_mass, ls_idx_local, envs_idx)

get_friction_ratio

get_friction_ratio(ls_idx_local=None, envs_idx=None) -> Tensor

Per-env link friction ratio applied by friction-randomization events; 1.0 if none.

Reads the live Genesis solver state (per link, via the link's first geom).

PARAMETER DESCRIPTION
ls_idx_local

Local link indices to read; None reads all links. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

Environments to read (int / slice / index tensor / bool mask); None reads all. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
Tensor

Copy shaped (num_envs_or_subset, n_links_or_subset).

Source code in eden/entities/rigid.py
def get_friction_ratio(self, ls_idx_local=None, envs_idx=None) -> torch.Tensor:
    """Per-env link friction ratio applied by friction-randomization events; 1.0 if none.

    Reads the live Genesis solver state (per link, via the link's first geom).

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        Local link indices to read; ``None`` reads all links. Defaults to ``None``.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        Environments to read (int / slice / index tensor / bool mask); ``None``
        reads all. Defaults to ``None``.

    Returns
    -------
    torch.Tensor
        Copy shaped ``(num_envs_or_subset, n_links_or_subset)``.
    """
    envs_idx = sanitize_envs_idx(envs_idx, self._env.num_envs, prefer_slice=False)
    out = self._env.rigid_solver.get_geoms_friction_ratio(self._first_geom_per_link(ls_idx_local), envs_idx)
    return out.clone()

get_COM_shift

get_COM_shift(ls_idx_local=None, envs_idx=None) -> Tensor

Per-env link COM shift (m, link frame) vs the nominal (unrandomized) COM; 0 if none.

Reads the live Genesis solver value and subtracts the nominal, so the result always reflects the randomization currently in effect.

PARAMETER DESCRIPTION
ls_idx_local

Local link indices to read; None reads all links. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

Environments to read (int / slice / index tensor / bool mask); None reads all. Defaults to None.

TYPE: None | int | slice | array_like | Tensor DEFAULT: None

RETURNS DESCRIPTION
Tensor

Copy shaped (num_envs_or_subset, n_links_or_subset, 3).

Source code in eden/entities/rigid.py
def get_COM_shift(self, ls_idx_local=None, envs_idx=None) -> torch.Tensor:
    """Per-env link COM shift (m, link frame) vs the nominal (unrandomized) COM; 0 if none.

    Reads the live Genesis solver value and subtracts the nominal, so the result
    always reflects the randomization currently in effect.

    Parameters
    ----------
    ls_idx_local : None | array_like, optional
        Local link indices to read; ``None`` reads all links. Defaults to ``None``.
    envs_idx : None | int | slice | array_like | torch.Tensor, optional
        Environments to read (int / slice / index tensor / bool mask); ``None``
        reads all. Defaults to ``None``.

    Returns
    -------
    torch.Tensor
        Copy shaped ``(num_envs_or_subset, n_links_or_subset, 3)``.
    """
    _, nominal_com = self._nominal_links_inertials()
    base = self._select_links_info(nominal_com, ls_idx_local, envs_idx)
    envs_idx = sanitize_envs_idx(envs_idx, self._env.num_envs, prefer_slice=False)
    ls_idx_local = _genesis_selection(ls_idx_local)
    if self.has_batched_links_info:
        return self._entity.get_links_COM(ls_idx_local, envs_idx) - base
    per_link = self._entity.get_links_COM(ls_idx_local)  # (n_links, 3)
    live = per_link.unsqueeze(0).expand(self._env.num_envs, *per_link.shape)[envs_idx]
    return live - base

set_dofs_force_range

set_dofs_force_range(lower, upper, dofs_idx_local=None, envs_idx=None) -> None

Set the force range (the drive's saturation cap) of the entity's dofs.

PARAMETER DESCRIPTION
lower

The lower force limit, ordered to match dofs_idx_local.

TYPE: array_like

upper

The upper force limit, ordered to match dofs_idx_local.

TYPE: array_like

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_dofs_force_range(self, lower, upper, dofs_idx_local=None, envs_idx=None) -> None:
    """Set the force range (the drive's saturation cap) of the entity's dofs.

    Parameters
    ----------
    lower : array_like
        The lower force limit, ordered to match ``dofs_idx_local``.
    upper : array_like
        The upper force limit, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    self._entity.set_dofs_force_range(lower, upper, dofs_idx_local, envs_idx)

set_dofs_stiffness

set_dofs_stiffness(stiffness, dofs_idx_local=None, envs_idx=None) -> None

Set the joint-spring stiffness of the entity's dofs.

PARAMETER DESCRIPTION
stiffness

The spring stiffness, ordered to match dofs_idx_local.

TYPE: array_like

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_dofs_stiffness(self, stiffness, dofs_idx_local=None, envs_idx=None) -> None:
    """Set the joint-spring stiffness of the entity's dofs.

    Parameters
    ----------
    stiffness : array_like
        The spring stiffness, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    self._entity.set_dofs_stiffness(stiffness, dofs_idx_local, envs_idx)

set_dofs_armature

set_dofs_armature(armature, dofs_idx_local=None, envs_idx=None) -> None

Set the armature (added rotor inertia) of the entity's dofs.

Under EnvOptions.integrator="euler", post_build already folded kd * substep_dt into the armature of actuated entities; overwriting it here discards that implicit-damping term. Under the implicit integrators there is no fold to lose.

PARAMETER DESCRIPTION
armature

The armature, ordered to match dofs_idx_local.

TYPE: array_like

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_dofs_armature(self, armature, dofs_idx_local=None, envs_idx=None) -> None:
    """Set the armature (added rotor inertia) of the entity's dofs.

    Under ``EnvOptions.integrator="euler"``, ``post_build`` already folded ``kd * substep_dt``
    into the armature of actuated entities; overwriting it here discards that implicit-damping
    term. Under the implicit integrators there is no fold to lose.

    Parameters
    ----------
    armature : array_like
        The armature, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    self._entity.set_dofs_armature(armature, dofs_idx_local, envs_idx)

set_dofs_damping

set_dofs_damping(damping, dofs_idx_local=None, envs_idx=None) -> None

Set the viscous damping of the entity's dofs.

PARAMETER DESCRIPTION
damping

The damping, ordered to match dofs_idx_local.

TYPE: array_like

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_dofs_damping(self, damping, dofs_idx_local=None, envs_idx=None) -> None:
    """Set the viscous damping of the entity's dofs.

    Parameters
    ----------
    damping : array_like
        The damping, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    self._entity.set_dofs_damping(damping, dofs_idx_local, envs_idx)

set_dofs_kp

set_dofs_kp(kp, dofs_idx_local=None, envs_idx=None) -> None

Set the positional (proportional) gain of the entity's PD controllers.

PARAMETER DESCRIPTION
kp

The positional gain, ordered to match dofs_idx_local.

TYPE: array_like

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_dofs_kp(self, kp, dofs_idx_local=None, envs_idx=None) -> None:
    """Set the positional (proportional) gain of the entity's PD controllers.

    Parameters
    ----------
    kp : array_like
        The positional gain, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    self._entity.set_dofs_kp(kp, dofs_idx_local, envs_idx)

set_dofs_kd

set_dofs_kd(kd, dofs_idx_local=None, envs_idx=None) -> None

Set the velocity (derivative) gain of the entity's PD controllers.

Genesis names this gain kv; Eden exposes it as kd throughout.

PARAMETER DESCRIPTION
kd

The velocity gain, ordered to match dofs_idx_local.

TYPE: array_like

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_dofs_kd(self, kd, dofs_idx_local=None, envs_idx=None) -> None:
    """Set the velocity (derivative) gain of the entity's PD controllers.

    Genesis names this gain ``kv``; Eden exposes it as ``kd`` throughout.

    Parameters
    ----------
    kd : array_like
        The velocity gain, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    self._entity.set_dofs_kv(kd, dofs_idx_local, envs_idx)

set_dofs_vel

set_dofs_vel(velocity=None, dofs_idx_local=None, envs_idx=None, *, skip_forward=False) -> None

Set the entity's dofs' velocity.

PARAMETER DESCRIPTION
velocity

The velocity to write, ordered to match dofs_idx_local. If None, zero. With a bool-mask envs_idx, every env's row is accepted too (see :meth:set_qpos). Defaults to None.

TYPE: None | array_like DEFAULT: None

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

skip_forward

Whether to skip the forward pass that propagates the new velocities. Set True only when a later setter in the same reset will trigger it anyway. Defaults to False.

TYPE: bool DEFAULT: False

Source code in eden/entities/rigid.py
def set_dofs_vel(
    self,
    velocity=None,
    dofs_idx_local=None,
    envs_idx=None,
    *,
    skip_forward=False,
) -> None:
    """Set the entity's dofs' velocity.

    Parameters
    ----------
    velocity : None | array_like, optional
        The velocity to write, ordered to match ``dofs_idx_local``. If None, zero.
        With a bool-mask ``envs_idx``, every env's row is accepted too (see :meth:`set_qpos`).
        Defaults to None.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    skip_forward : bool, optional
        Whether to skip the forward pass that propagates the new velocities. Set True
        only when a later setter in the same reset will trigger it anyway. Defaults to False.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    velocity, envs_idx = _mask_rows(
        velocity, envs_idx, on_device=isinstance(dofs_idx_local, range) and self.setters_take_every_row
    )
    self._entity.set_dofs_velocity(velocity, dofs_idx_local, envs_idx, skip_forward=skip_forward)

set_dofs_frictionloss

set_dofs_frictionloss(frictionloss, dofs_idx_local=None, envs_idx=None) -> None

Set the dry-friction loss (stick band) of the entity's dofs.

PARAMETER DESCRIPTION
frictionloss

The dry-friction loss, ordered to match dofs_idx_local.

TYPE: array_like

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def set_dofs_frictionloss(self, frictionloss, dofs_idx_local=None, envs_idx=None) -> None:
    """Set the dry-friction loss (stick band) of the entity's dofs.

    Parameters
    ----------
    frictionloss : array_like
        The dry-friction loss, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    self._entity.set_dofs_frictionloss(frictionloss, dofs_idx_local, envs_idx)

set_dofs_pos

set_dofs_pos(position, dofs_idx_local=None, envs_idx=None, *, zero_velocity=True) -> None

Teleport the entity's dofs to a position.

This writes state directly. To command a PD target instead, use :meth:control_dofs_pos.

PARAMETER DESCRIPTION
position

The positions to write, ordered to match dofs_idx_local. With a bool-mask envs_idx, every env's row (num_envs, n_dofs) is accepted too, and the mask picks which are written — with :attr:setters_take_every_row, the sync-free form for a reset.

TYPE: (array_like, shape(n_dofs) or (len(envs_idx), n_dofs))

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.

TYPE: None | array_like DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

zero_velocity

Whether to zero the entity's velocities after the write, so the teleport does not leave stale momentum behind. Defaults to True.

TYPE: bool DEFAULT: True

Notes

Hinge and slide DOFs are written as their coordinates, qpos0 + position, through set_qpos (which takes a bool mask without a GPU->CPU sync); a selection that reaches a free or ball joint's DOFs goes through Genesis set_dofs_position. Either way the entity's DOF positions read back as position.

Source code in eden/entities/rigid.py
def set_dofs_pos(
    self,
    position,
    dofs_idx_local=None,
    envs_idx=None,
    *,
    zero_velocity=True,
) -> None:
    """Teleport the entity's dofs to a position.

    This writes state directly. To command a PD target instead, use
    :meth:`control_dofs_pos`.

    Parameters
    ----------
    position : array_like, shape (n_dofs,) or (len(envs_idx), n_dofs)
        The positions to write, ordered to match ``dofs_idx_local``. With a bool-mask
        ``envs_idx``, every env's row ``(num_envs, n_dofs)`` is accepted too, and the mask picks
        which are written — with :attr:`setters_take_every_row`, the sync-free form for a reset.
    dofs_idx_local : None | array_like, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    zero_velocity : bool, optional
        Whether to zero the entity's velocities after the write, so the teleport does
        not leave stale momentum behind. Defaults to True.

    Notes
    -----
    Hinge and slide DOFs are written as their coordinates, ``qpos0 + position``, through
    ``set_qpos`` (which takes a bool mask without a GPU->CPU sync); a selection that reaches a
    free or ball joint's DOFs goes through Genesis ``set_dofs_position``. Either way the entity's
    DOF positions read back as ``position``.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local)
    qs = self._hinge_qs(dofs_idx_local) if self.setters_take_every_row else None
    if qs is None:
        # Genesis' kernel takes the selected rows only.
        position, envs_idx = _mask_rows(position, envs_idx, on_device=False)
        self._entity.set_dofs_position(
            position,
            _kernel_selection(dofs_idx_local),
            envs_idx,
            zero_velocity=zero_velocity,
        )
        return
    # Hinge / slide DOFs, written as the coordinates Genesis derives from them (``qpos0 + position``):
    # its ``set_qpos`` takes a bool mask on the device, with every env's row or the selected ones,
    # where ``set_dofs_position`` runs only as a kernel over int ids — a ``nonzero()`` of the mask
    # and an error-flag write through the ids, two GPU->CPU syncs per call. It also leaves the
    # other entities alone: ``set_dofs_position`` re-derives every entity's qpos in those envs.
    position = torch.as_tensor(position, dtype=gs.tc_float, device=gs.device)
    n_rows = position.shape[0] if position.ndim == 2 else 1
    qpos = self._qpos0_rows(qs, envs_idx, n_rows) + position
    self._entity.set_qpos(qpos, qs, envs_idx, zero_velocity=zero_velocity)

control_dofs_force

control_dofs_force(force, dofs_idx_local=None, envs_idx=None) -> None

Command a feed-forward force on the entity's dofs for the next step.

Bypasses the PD controller: the commanded force is applied directly, still clipped to the force range. The command persists until it is overwritten.

PARAMETER DESCRIPTION
force

The force to command, ordered to match dofs_idx_local.

TYPE: (array_like, shape(n_dofs) or (len(envs_idx), n_dofs))

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. A :class:~eden.utils.misc.DofPermutation or :class:~eden.utils.misc.DofRuns (as_genesis_control_index) writes a reordered or gapped selection through range s, sync-free; an index tensor or list is resolved to one of those itself. Defaults to None.

TYPE: None | array_like | DofRuns DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def control_dofs_force(self, force, dofs_idx_local=None, envs_idx=None) -> None:
    """Command a feed-forward force on the entity's dofs for the next step.

    Bypasses the PD controller: the commanded force is applied directly, still
    clipped to the force range. The command persists until it is overwritten.

    Parameters
    ----------
    force : array_like, shape (n_dofs,) or (len(envs_idx), n_dofs)
        The force to command, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like | DofRuns, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. A
        :class:`~eden.utils.misc.DofPermutation` or :class:`~eden.utils.misc.DofRuns`
        (``as_genesis_control_index``) writes a reordered or gapped selection through ``range`` s,
        sync-free; an index tensor or list is resolved to one of those itself. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local, control=True)
    _control_dofs(self._entity.control_dofs_force, force, dofs_idx_local, envs_idx)

control_dofs_vel

control_dofs_vel(velocity, dofs_idx_local=None, envs_idx=None) -> None

Command a velocity target for the entity's PD controllers.

Sets a target, not state — the solver drives toward it through the kd gain, clipped to the force range. Use :meth:set_dofs_vel to write velocity directly.

PARAMETER DESCRIPTION
velocity

The velocity target, ordered to match dofs_idx_local.

TYPE: (array_like, shape(n_dofs) or (len(envs_idx), n_dofs))

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. A :class:~eden.utils.misc.DofPermutation or :class:~eden.utils.misc.DofRuns (as_genesis_control_index) writes a reordered or gapped selection through range s, sync-free; an index tensor or list is resolved to one of those itself. Defaults to None.

TYPE: None | array_like | DofRuns DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def control_dofs_vel(self, velocity, dofs_idx_local=None, envs_idx=None) -> None:
    """Command a velocity target for the entity's PD controllers.

    Sets a target, not state — the solver drives toward it through the ``kd`` gain,
    clipped to the force range. Use :meth:`set_dofs_vel` to write velocity directly.

    Parameters
    ----------
    velocity : array_like, shape (n_dofs,) or (len(envs_idx), n_dofs)
        The velocity target, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like | DofRuns, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. A
        :class:`~eden.utils.misc.DofPermutation` or :class:`~eden.utils.misc.DofRuns`
        (``as_genesis_control_index``) writes a reordered or gapped selection through ``range`` s,
        sync-free; an index tensor or list is resolved to one of those itself. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local, control=True)
    _control_dofs(self._entity.control_dofs_velocity, velocity, dofs_idx_local, envs_idx)

control_dofs_pos

control_dofs_pos(position, dofs_idx_local=None, envs_idx=None) -> None

Command a position target for the entity's PD controllers.

Sets a target, not state — the solver drives toward it through the kp/kd gains, clipped to the force range. Use :meth:set_dofs_pos to teleport instead. This is the setter behind the joint-position action terms.

PARAMETER DESCRIPTION
position

The position target, ordered to match dofs_idx_local.

TYPE: (array_like, shape(n_dofs) or (len(envs_idx), n_dofs))

dofs_idx_local

Local DOF indices. If None, all of the entity's dofs in dofs_name order. A :class:~eden.utils.misc.DofPermutation or :class:~eden.utils.misc.DofRuns (as_genesis_control_index) writes a reordered or gapped selection through range s, sync-free; an index tensor or list is resolved to one of those itself. Defaults to None.

TYPE: None | array_like | DofRuns DEFAULT: None

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

Source code in eden/entities/rigid.py
def control_dofs_pos(self, position, dofs_idx_local=None, envs_idx=None) -> None:
    """Command a position target for the entity's PD controllers.

    Sets a target, not state — the solver drives toward it through the ``kp``/``kd``
    gains, clipped to the force range. Use :meth:`set_dofs_pos` to teleport instead.
    This is the setter behind the joint-position action terms.

    Parameters
    ----------
    position : array_like, shape (n_dofs,) or (len(envs_idx), n_dofs)
        The position target, ordered to match ``dofs_idx_local``.
    dofs_idx_local : None | array_like | DofRuns, optional
        Local DOF indices. If None, all of the entity's dofs in dofs_name order. A
        :class:`~eden.utils.misc.DofPermutation` or :class:`~eden.utils.misc.DofRuns`
        (``as_genesis_control_index``) writes a reordered or gapped selection through ``range`` s,
        sync-free; an index tensor or list is resolved to one of those itself. Defaults to None.
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    if dofs_idx_local is None:
        dofs_idx_local = self._genesis_dofs_idx
    else:
        dofs_idx_local = _genesis_selection(dofs_idx_local, control=True)
    _control_dofs(self._entity.control_dofs_position, position, dofs_idx_local, envs_idx)

zero_all_dofs_velocity

zero_all_dofs_velocity(envs_idx=None, *, skip_forward: bool = False) -> None

Zero the velocity of every one of the entity's dofs, including a floating base.

PARAMETER DESCRIPTION
envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: None | array_like DEFAULT: None

skip_forward

Skip the forward-kinematics pass. Only when a later write to the same envs runs one (for example a set_dofs_pos) before anything reads link state. Defaults to False.

TYPE: bool DEFAULT: False

Source code in eden/entities/rigid.py
def zero_all_dofs_velocity(self, envs_idx=None, *, skip_forward: bool = False) -> None:
    """Zero the velocity of every one of the entity's dofs, including a floating base.

    Parameters
    ----------
    envs_idx : None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    skip_forward : bool, optional
        Skip the forward-kinematics pass. Only when a later write to the same envs runs one (for example a
        ``set_dofs_pos``) before anything reads link state. Defaults to False.
    """
    self._entity.zero_all_dofs_velocity(envs_idx, skip_forward=skip_forward)

place_on_to

place_on_to(entity: RigidEntity, sampler: Callable, envs_idx: slice | Tensor | None = None)

Place this entity onto the support surface of another entity.

PARAMETER DESCRIPTION
entity

the entity on which this object will be placed

TYPE: RigidEntity

sampler

the sampler to use for location sampling

TYPE: Callable

envs_idx

The indices of the environments. If None, all environments will be considered. Defaults to None.

TYPE: slice | Tensor | None DEFAULT: None

Source code in eden/entities/rigid.py
def place_on_to(
    self,
    entity: RigidEntity,
    sampler: Callable,
    envs_idx: slice | torch.Tensor | None = None,
):
    """Place this entity onto the support surface of another entity.

    Parameters
    ----------
    entity: RigidEntity
        the entity on which this object will be placed
    sampler: Callable
        the sampler to use for location sampling
    envs_idx: None | array_like, optional
        The indices of the environments. If None, all environments will be considered. Defaults to None.
    """
    envs_idx, n_envs = sanitize_envs_idx(envs_idx, self._env.num_envs, return_n_envs=True)

    AABB = self.get_AABB()
    AABB = AABB[envs_idx]
    # NOTE: support bool envs_idx
    if AABB.shape[0] == 0:
        return
    clearance = (AABB[:, 1, 2] - AABB[:, 0, 2]).max(dim=0)[0]
    support = entity.current_support(
        minimum_clearance=clearance,
        gravity=self._env.rigid_solver.get_gravity(envs_idx=envs_idx),
        envs_idx=envs_idx,
    )
    # NOTE: force to update collision state
    entity.solver.collider.clear()
    # NOTE: update slightly bigger AABBs
    entity._kernel_update_aabbs(
        self.solver.dyn_state.geoms.pos,
        self.solver.dyn_state.geoms.quat,
        self.solver.geoms_init_AABB,
        self.solver.dyn_state.geoms.aabb_min,
        self.solver.dyn_state.geoms.aabb_max,
    )
    # NOTE: use broad phase to check the occupied space on the support surface
    self._run_broad_phase()
    tensor = torch.zeros(
        (self._env.num_envs, self.solver.n_entities, 2, 3),
        dtype=gs.tc_float,
        device=self.device,
    )
    tensor[..., 0, :] = torch.inf
    tensor[..., 1, :] = -torch.inf
    valid_mask = torch.zeros(
        (self._env.num_envs, self.solver.n_entities),
        dtype=gs.tc_int,
        device=self.device,
    )
    # NOTE: get set of AABBs that is already occupied
    entity._kernel_filter_detection(
        tensor,
        valid_mask,
        self.solver.collider.collider_state.n_broad_pairs,
        self.solver.collider.collider_state.broad_collision_pairs,
        self.solver.dyn_info.geoms.link_idx,
        self.solver.dyn_info.links.entity_idx,
        self.solver.dyn_info.entities.geom_start,
        self.solver.dyn_info.entities.geom_end,
        self.solver.dyn_state.geoms.aabb_min,
        self.solver.dyn_state.geoms.aabb_max,
    )
    AABBs = tensor[:, (valid_mask == 1).any(dim=0)]
    AABBs = AABBs[envs_idx]

    pos, offset_direction, valid_mask = sampler(
        support,
        asset_AABB=AABB,
        holes=AABBs,
        batch_size=n_envs,
    )
    pos += torch.sum(self.offset * offset_direction, dim=-1, keepdim=True) * offset_direction
    if self.is_attached:
        raise RuntimeError(
            f"Cannot place '{self.name}': it is welded onto another entity, so its pose is "
            f"determined by the parent link and any write here is discarded by the next "
            f"forward-kinematics pass. Place the entity it is attached to instead."
        )
    if self.is_fixed_base and not self.morph.batch_fixed_verts:
        self.set_pos(pos)
    else:
        if valid_mask is not None:
            pos = pos[valid_mask]
            if isinstance(envs_idx, slice):
                envs_idx = torch.arange(*envs_idx.indices(self._env.num_envs))[valid_mask.cpu()]
            else:
                envs_idx = envs_idx[valid_mask]
        if isinstance(envs_idx, torch.Tensor):
            envs_idx = envs_idx.to(self.device)
        self.set_pos(pos, envs_idx=envs_idx)

Rigid (articulated/actuated) entity wrapper with DOF, IK, and contact helpers.

DOF-ordering gotcha: set_qpos / get_dofs_limit / get_jacobian all use dofs_name order (the configured dofs_name list, a pattern expanding in Genesis' joint order; not the asset's declaration order), whereas find_named_dofs_idx_local returns indices in Genesis kinematic-tree order (breadth-first in genesis 1.1.x, depth-first in 1.2.0) — use the latter for Genesis-level API calls, not for indexing into get_jacobian columns or the default_dofs_* property tensors. get_jacobian already reorders its columns Genesis->dofs_name, so no manual reordering is needed. find_named_dofs resolves names into both spaces at once, and get_default_dofs_* reads the property tensors by name (eden.entities.dofs_default_mixin).

For actuated entities under the explicit Euler integrator, post_build adds kd * substep_dt to the armature for numerical stability, so the effective armature differs from the configured value. Genesis' default approximate_implicitfast and MuJoCo's implicitfast both integrate the PD controller's damping implicitly and get no bump — _needs_explicit_damping_armature is the gate. Read get_dofs_armature for what the solver actually holds. "The PD controller" is Genesis' own (position/velocity control): a torque-mode action term (control_dofs_force) computes its damping in Python, which is explicit under every integrator.

Attributes

LINK_REF_FRAMES: dict[str, link_ref_frame] = {'link_origin': gs.link_ref_frame.link_origin, 'link_com': gs.link_ref_frame.link_COM}

Classes

StagedStateWrite

StagedStateWrite()

Several rigid entities' state writes on one envs_idx, applied as one Genesis call per kind.

Why. Every Genesis state setter resets the selected envs' collider and constraint state and, unless told to skip it, runs forward kinematics over every link of those envs, whichever entity it wrote. N entities written one by one pay N of each where one would do.

What. set_dofs_pos / set_dofs_vel / set_qpos stand in for the :class:RigidEntity setters of the same names: each only records its entity's values, and :meth:apply writes them all. The values mean what they mean to those setters (set_dofs_pos writes a DOF's state as Genesis holds it, set_qpos the coordinate itself), but nothing here zeroes a velocity on the side: stage the zeros where the entity setter's zero_velocity=True would. A free or ball joint's rotation DOFs are Euler angles, so set_dofs_pos takes all three of them or none.

Order. :meth:apply writes per solver (a kinematic-material entity lives in its own):

  1. velocities, skip_forward=True, so the forward pass after them sees the fresh ones;
  2. positions, as one qpos write and its forward pass. A staged DOF position becomes the qpos Genesis set_dofs_position would make of it (qpos0 + value for a hinge or slide, the quaternion of a free or ball joint's Euler angles), placed before the staged qpos, so a qpos staged on the same coordinate wins.

Nothing goes through Genesis set_dofs_position. That setter re-derives every entity's qpos in the selected envs from its DOF state, which holds a free joint's orientation as Euler angles, so a quaternion written earlier, by this write or by any before it, would come back as -q. Here only the joints a set_dofs_pos names are converted.

Envs. envs_idx is int env ids or the bool mask they come from; a staged value has one row per selected env, in ascending env order for a mask — or, with a mask, every env's row, the mask picking which are written (stage every value of the write that way, then). A mask is what Genesis' setters take on the device (masked_scatter_, or torch.where for every env's row): handed ids they would write their error flag through them, a GPU->CPU sync per setter. Every env's row needs no nonzero() at all, so a write that may select no env can run unconditionally (the motion-end resync); where Genesis cannot take the rows on the device (hibernation, an index tensor selection) the selected ones are gathered here.

Indices are entity-local and on the host (a range or ints), since they are read in Python on every call: resolve them once, in build(). An index staged twice keeps the later value.

Source code in eden/entities/rigid.py
def __init__(self) -> None:
    self._stages: dict[object, _SolverStage] = {}

Methods:

check_dofs_pos staticmethod
check_dofs_pos(entity: RigidEntity, dofs_idx_local) -> None

Raise what :meth:set_dofs_pos would raise for dofs_idx_local; call it in build().

Source code in eden/entities/rigid.py
@staticmethod
def check_dofs_pos(entity: RigidEntity, dofs_idx_local) -> None:
    """Raise what :meth:`set_dofs_pos` would raise for ``dofs_idx_local``; call it in ``build()``."""
    _dofs_pos_plan(entity, dofs_idx_local)
set_dofs_pos
set_dofs_pos(entity: RigidEntity, position: Tensor, dofs_idx_local) -> None

Stage :meth:RigidEntity.set_dofs_pos (zero_velocity=False) for entity.

Source code in eden/entities/rigid.py
def set_dofs_pos(self, entity: RigidEntity, position: torch.Tensor, dofs_idx_local) -> None:
    """Stage :meth:`RigidEntity.set_dofs_pos` (``zero_velocity=False``) for ``entity``."""
    self._refuse_ipc_articulation(entity, "set_dofs_pos")
    plan = _dofs_pos_plan(entity, dofs_idx_local)
    stage = self._stage_of(entity)
    scalar_columns, scalar_qs = plan.scalar
    if scalar_qs:
        stage.dofs_pos.append((scalar_qs, _select(position, scalar_columns)))
    translation_columns, translation_qs = plan.translations
    if translation_qs:
        stage.dofs_qpos.append((translation_qs, _select(position, translation_columns)))
    for columns, qs in plan.rotations:
        stage.dofs_qpos.append((qs, gu.xyz_to_quat(_select(position, columns))))
set_dofs_vel
set_dofs_vel(entity: RigidEntity, velocity: Tensor, dofs_idx_local) -> None

Stage :meth:RigidEntity.set_dofs_vel for entity; :meth:apply skips its forward pass.

Source code in eden/entities/rigid.py
def set_dofs_vel(self, entity: RigidEntity, velocity: torch.Tensor, dofs_idx_local) -> None:
    """Stage :meth:`RigidEntity.set_dofs_vel` for ``entity``; :meth:`apply` skips its forward pass."""
    self._stage_of(entity).dofs_vel.append((_global_indices(entity.dof_start, dofs_idx_local), velocity))
set_qpos
set_qpos(entity: RigidEntity, qpos: Tensor, qs_idx_local) -> None

Stage :meth:RigidEntity.set_qpos (zero_velocity=False) for entity.

Source code in eden/entities/rigid.py
def set_qpos(self, entity: RigidEntity, qpos: torch.Tensor, qs_idx_local) -> None:
    """Stage :meth:`RigidEntity.set_qpos` (``zero_velocity=False``) for ``entity``."""
    self._refuse_ipc_articulation(entity, "set_qpos")
    self._stage_of(entity).qpos.append((_global_indices(entity._entity.q_start, qs_idx_local), qpos))
apply
apply(envs_idx: Tensor) -> None

Write everything staged to envs_idx (int env ids or a bool mask), in the class docstring's order.

Empties the stage.

Source code in eden/entities/rigid.py
def apply(self, envs_idx: torch.Tensor) -> None:
    """Write everything staged to ``envs_idx`` (int env ids or a bool mask), in the class docstring's order.

    Empties the stage.
    """
    for solver, stage in self._stages.items():
        if stage.dofs_vel:
            velocity, dofs_idx = self._join(solver, stage.dofs_vel)
            selection = _genesis_selection(dofs_idx)
            on_device = isinstance(selection, range) and _solver_takes_every_row(solver)
            velocity, envs = _mask_rows(velocity, envs_idx, on_device=on_device)
            solver.set_dofs_velocity(velocity, selection, envs, skip_forward=True)
        positions = [*stage.dofs_qpos, *stage.qpos]
        if stage.dofs_pos:
            offsets, qs_idx = self._join(solver, stage.dofs_pos)
            qpos0 = _qpos0(solver)
            columns = _device_index(solver, tuple(qs_idx), qpos0.device)
            if _every_env_row(offsets, envs_idx):
                base = qpos0.index_select(1, columns)
            else:
                # The selected envs' rows, through their ids: free for the auto-reset's mask
                # (`sharing_env_indices`).
                base = qpos0[env_indices(envs_idx).unsqueeze(-1), columns]
            positions = [(tuple(qs_idx), offsets + base), *positions]
        if positions:
            qpos, qs_idx = self._join(solver, positions)
            selection = _genesis_selection(qs_idx)
            on_device = isinstance(selection, range) and _solver_takes_every_row(solver)
            qpos, envs = _mask_rows(qpos, envs_idx, on_device=on_device)
            solver.set_qpos(qpos, selection, envs, skip_forward=False)
    self._stages.clear()

Functions:

resolve_link_ref_frame(ref: str | link_ref_frame) -> link_ref_frame

Resolve a reference-frame name (or an already-resolved member) to gs.link_ref_frame.

PARAMETER DESCRIPTION
ref

One of "link_origin" / "link_com", or a gs.link_ref_frame member.

TYPE: str | link_ref_frame

RETURNS DESCRIPTION
link_ref_frame

The resolved enum member.

Source code in eden/entities/rigid.py
def resolve_link_ref_frame(ref: str | gs.link_ref_frame) -> gs.link_ref_frame:
    """Resolve a reference-frame name (or an already-resolved member) to ``gs.link_ref_frame``.

    Parameters
    ----------
    ref : str | gs.link_ref_frame
        One of ``"link_origin"`` / ``"link_com"``, or a ``gs.link_ref_frame`` member.

    Returns
    -------
    gs.link_ref_frame
        The resolved enum member.
    """
    if isinstance(ref, gs.link_ref_frame):
        return ref
    try:
        return LINK_REF_FRAMES[ref]
    except KeyError:
        raise ValueError(f"Unknown link reference frame {ref!r}; expected one of {sorted(LINK_REF_FRAMES)}.") from None

Named DOF access

find_named_dofs(names) returns a DofSelection: hand its .columns to tensors indexed in the entity's configured DOF order and its .dofs_idx_local to Genesis calls.

Name-based access to a rigid entity's DOFs and its configured per-DOF properties.

Two index spaces address an entity's DOFs, and only one of them indexes the property tensors:

  • column — position in the entity's dofs_name. The configured property tensors (default_dofs_pos, default_dofs_kp, soft_dofs_pos_limits, …) are laid out this way, and so is every full-width get_dofs_*() read.
  • local — Genesis' DOF index within the entity: what find_named_dofs_idx_local returns and what every dofs_idx_local= argument takes. A floating base offsets it by six, an uncontrolled joint shifts it, and the kinematic tree can permute it. A local index used as a column therefore reads another joint's value, or, once it passes the last column, out of bounds (an asynchronous CUDA assert, far from the call site).

:meth:DofDefaultsMixin.find_named_dofs resolves names into both spaces at once, as a :class:DofSelection whose fields say which space they belong to, and the get_default_dofs_* getters read the property tensors by name — so no bare index from either space passes through the caller's hands.

Classes

DofSelection dataclass

DofSelection(entity_name: str, names: tuple[str, ...], columns: Tensor, dofs_idx_local: Tensor)

Some of an entity's controlled DOFs, resolved into both index spaces at once.

Made by :meth:DofDefaultsMixin.find_named_dofs, in build(), and kept. The three fields list the same DOFs in the same order, one entry per DOF (a ball joint's name repeats once per DOF):

.. code-block:: python

dofs = robot.find_named_dofs([".*_knee_joint"])          # in build()
robot.soft_dofs_pos_limits[:, dofs.columns]          # a property tensor: columns
robot.get_dofs_pos(dofs_idx_local=dofs.dofs_idx_local)  # a Genesis-level call: local indices
robot.get_default_dofs_kp(dofs)                      # a getter takes the selection itself

Both tensors live on the entity's device; len(dofs) is the number of DOFs.

ATTRIBUTE DESCRIPTION
entity_name

The entity it was resolved on; a getter refuses a selection from another one, whose columns would address that entity's layout.

TYPE: str

names

The joint each DOF belongs to.

TYPE: tuple[str, ...]

columns

long; positions in the entity's dofs_name. Index default_dofs_*, soft_dofs_pos_limits or a full-width get_dofs_*() read with it.

TYPE: Tensor

dofs_idx_local

Genesis' local indices of the same DOFs. Pass it as dofs_idx_local=, and nowhere else.

TYPE: Tensor

Attributes

entity_name instance-attribute
entity_name: str
names instance-attribute
names: tuple[str, ...]
columns instance-attribute
columns: Tensor
dofs_idx_local instance-attribute
dofs_idx_local: Tensor

Methods:

DofDefaultsMixin

Name-based access to the controlled DOFs and the configured per-DOF property tensors.

:meth:find_named_dofs resolves names once, into a :class:DofSelection that carries the DOFs' columns and local indices together. get_default_dofs_<property> (and :meth:get_soft_dofs_pos_limits) is get_ plus the config field it reads. It takes names, or a selection, where the get_dofs_* getters take dofs_idx_local, and the same envs_idx:

.. code-block:: python

robot.get_default_dofs_kp()                         # (num_envs, num_dofs), dofs_name order
robot.get_default_dofs_kp(["left_knee_joint"])      # (num_envs, 1)
robot.get_default_dofs_pos(envs_idx=[0])            # (1, num_dofs)
robot.get_default_dofs_pos_limits(".*_knee_joint")  # (num_envs, 2, 2): lower, upper

The default_* value is what the entity was configured with, resolved at build; the get_dofs_* getter beside it reads what the solver holds now. They differ wherever something writes the solver afterwards: domain randomization, the explicit-integrator armature bump, a deployment.

Selection. dofs_name takes names, globs or regexes (None for every DOF), matched against the joints in the entity's dofs_name — the controlled ones, which are the only joints that have a column. A free base or a passive joint is not one of them, and raises. A key that is exactly a joint's name — any joint's, controlled or not — selects that joint literally, metacharacters and all, so a passive pin.1 never selects a controlled pin_1. The result follows the query's order, and a joint with several DOFs (a ball joint) selects all of them, in order; name each joint once.

Shape. Always (n_envs, n_selected[, 2]), with envs_idx narrowing the env axis exactly as it does for the get_dofs_* info getters. The stored tensors are not that uniform: one row or one per env, depending on the property, whether the config set it, EnvOptions.batch_dofs_info and whether the entity is heterogeneous; and the rows can differ (a heterogeneous entity, RandomizeStartupDofsPosBias) — so a caller that read row 0 of one was reading env 0 without saying so.

Ownership. Every read is a copy, never a view of the stored tensor.

Hot paths and writes. A getter allocates its (n_envs, n) copy on every call, so it is for build(). To write into a property tensor, or to read one every step while keeping its stored layout (one broadcast row stays one row), index it with a :meth:find_named_dofs selection's columns.

The host class provides dofs_name (resolved: one entry per DOF), dofs_idx_local, dofs_idx_map, find_named_dofs_idx_local, joints, device, is_built, name, the property attributes, and _dof_defaults_num_envs.

Methods:

find_named_dofs
find_named_dofs(dofs_name: str | Sequence[str] | None = None) -> DofSelection

Resolve names to a :class:DofSelection: the selected DOFs' columns and local indices.

One resolution feeds both, so the two always describe the same DOFs, in the same order. Selection follows the class note: only the controlled joints (those in :attr:dofs_name) can be selected, through the same simulator-side resolver find_named_dofs_idx_local uses, so a query selects the same joints in both. Resolve in build(), not every step.

PARAMETER DESCRIPTION
dofs_name

Names, globs or regexes of joints in :attr:dofs_name. None selects every controlled DOF, in column order.

TYPE: str | Sequence[str] | None DEFAULT: None

RETURNS DESCRIPTION
DofSelection

One name, column and local index per selected DOF.

Source code in eden/entities/dofs_default_mixin.py
@gs.assert_built
def find_named_dofs(self, dofs_name: str | Sequence[str] | None = None) -> DofSelection:
    """Resolve names to a :class:`DofSelection`: the selected DOFs' columns *and* local indices.

    One resolution feeds both, so the two always describe the same DOFs, in the same order.
    Selection follows the class note: only the controlled joints (those in :attr:`dofs_name`) can
    be selected, through the same simulator-side resolver ``find_named_dofs_idx_local`` uses, so a
    query selects the same joints in both. Resolve in ``build()``, not every step.

    Parameters
    ----------
    dofs_name : str | Sequence[str] | None
        Names, globs or regexes of joints in :attr:`dofs_name`. ``None`` selects every
        controlled DOF, in column order.

    Returns
    -------
    DofSelection
        One name, column and local index per selected DOF.
    """
    if dofs_name is None:
        # Every controlled DOF, in column order: what ``dofs_name`` and ``dofs_idx_local`` already hold. An entity
        # that resolved no DOFs has an empty ``dofs_idx_map``, and its ``dofs_name`` is then still the raw config.
        names = list(self.dofs_name) if self.dofs_idx_map else []
        dofs_idx_local = self.dofs_idx_local.tolist() if names else []
    else:
        names, dofs_idx_local = self._resolve_controlled_dofs(dofs_name)
    # ``dofs_idx_map`` takes a controlled DOF's local index to its column.
    columns = [self.dofs_idx_map[int(i)] for i in dofs_idx_local]
    return DofSelection(
        entity_name=self.name,
        names=tuple(names),
        columns=torch.tensor(columns, dtype=torch.long, device=self.device),
        dofs_idx_local=torch.tensor(dofs_idx_local, dtype=gs.tc_int, device=self.device),
    )
get_default_dofs_pos
get_default_dofs_pos(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured default joint positions, (n_envs, n_dofs). See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_pos(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured default joint positions, ``(n_envs, n_dofs)``. See the class note."""
    return self._get_default_dofs("default_dofs_pos", dofs_name, envs_idx)
get_default_dofs_vel
get_default_dofs_vel(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured default joint velocities, (n_envs, n_dofs). See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_vel(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured default joint velocities, ``(n_envs, n_dofs)``. See the class note."""
    return self._get_default_dofs("default_dofs_vel", dofs_name, envs_idx)
get_default_dofs_kp
get_default_dofs_kp(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured PD position gains, (n_envs, n_dofs); actuated entities only. See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_kp(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured PD position gains, ``(n_envs, n_dofs)``; actuated entities only. See the class note."""
    return self._get_default_dofs("default_dofs_kp", dofs_name, envs_idx)
get_default_dofs_kd
get_default_dofs_kd(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured PD velocity gains, (n_envs, n_dofs); actuated entities only. See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_kd(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured PD velocity gains, ``(n_envs, n_dofs)``; actuated entities only. See the class note."""
    return self._get_default_dofs("default_dofs_kd", dofs_name, envs_idx)
get_default_dofs_stiffness
get_default_dofs_stiffness(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured joint stiffness, (n_envs, n_dofs). See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_stiffness(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured joint stiffness, ``(n_envs, n_dofs)``. See the class note."""
    return self._get_default_dofs("default_dofs_stiffness", dofs_name, envs_idx)
get_default_dofs_damping
get_default_dofs_damping(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured joint damping, (n_envs, n_dofs). See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_damping(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured joint damping, ``(n_envs, n_dofs)``. See the class note."""
    return self._get_default_dofs("default_dofs_damping", dofs_name, envs_idx)
get_default_dofs_armature
get_default_dofs_armature(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured armature, (n_envs, n_dofs), before any explicit-integrator bump. See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_armature(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured armature, ``(n_envs, n_dofs)``, before any explicit-integrator bump. See the class note."""
    return self._get_default_dofs("default_dofs_armature", dofs_name, envs_idx)
get_default_dofs_frictionloss
get_default_dofs_frictionloss(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured joint friction loss, (n_envs, n_dofs). See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_frictionloss(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured joint friction loss, ``(n_envs, n_dofs)``. See the class note."""
    return self._get_default_dofs("default_dofs_frictionloss", dofs_name, envs_idx)
get_default_dofs_pos_limits
get_default_dofs_pos_limits(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured hard position limits, (n_envs, n_dofs, 2) as (lower, upper). See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_pos_limits(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured hard position limits, ``(n_envs, n_dofs, 2)`` as (lower, upper). See the class note."""
    return self._get_default_dofs("default_dofs_pos_limits", dofs_name, envs_idx)
get_soft_dofs_pos_limits
get_soft_dofs_pos_limits(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Soft position limits, (n_envs, n_dofs, 2) as (lower, upper); unbounded DOFs keep their hard range.

See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_soft_dofs_pos_limits(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Soft position limits, ``(n_envs, n_dofs, 2)`` as (lower, upper); unbounded DOFs keep their hard range.

    See the class note.
    """
    return self._get_default_dofs("soft_dofs_pos_limits", dofs_name, envs_idx)
get_default_dofs_force_limits
get_default_dofs_force_limits(dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None) -> Tensor

Configured symmetric effort limits, (n_envs, n_dofs). See the class note.

Source code in eden/entities/dofs_default_mixin.py
def get_default_dofs_force_limits(
    self, dofs_name: str | Sequence[str] | DofSelection | None = None, envs_idx=None
) -> torch.Tensor:
    """Configured symmetric effort limits, ``(n_envs, n_dofs)``. See the class note."""
    return self._get_default_dofs("default_dofs_force_limits", dofs_name, envs_idx)

Support surfaces

The support-surface queries (current_support and the kernels behind it) that RigidEntity.place_on_to and the placement events use. RigidEntity inherits them.

SupportSurfaceMixin

Mixin for support surface preparation. Used for penetration free object placement.

Attributes

entity property

entity: Entity

solver property

solver

Methods:

current_support

current_support(minimum_clearance: float = 0.1, require_roof: bool = False, gravity: Tensor | None = None, gravity_tolerance: float = 0.1, envs_idx: slice | Tensor | None = None) -> list[ExtendedSupportData]

Get the valid current support data at the entity's current pose.

PARAMETER DESCRIPTION
minimum_clearance

minimum clearance to consider for support surface

TYPE: float DEFAULT: 0.1

require_roof

if the support has to be covered (e.g., inside shelf, microwave, etc.)

TYPE: bool DEFAULT: False

gravity

a normalized gravity vector indicating the gravity direction in world coordinate system that the support surface is holding against. None means -z.

TYPE: Tensor | None DEFAULT: None

gravity_tolerance

tolerance for gravity direction alignment

TYPE: float DEFAULT: 0.1

envs_idx

the indices of the environments to query. If None, all environments are considered. Defaults to None.

TYPE: slice | Tensor | None DEFAULT: None

Source code in eden/entities/support_mixin.py
def current_support(
    self,
    minimum_clearance: float = 0.1,
    require_roof: bool = False,
    gravity: torch.Tensor | None = None,
    gravity_tolerance: float = 0.1,
    envs_idx: (slice | torch.Tensor) | None = None,
) -> list[ExtendedSupportData]:
    """
    Get the valid current support data at the entity's current pose.

    Parameters
    ----------
    minimum_clearance: float
        minimum clearance to consider for support surface
    require_roof: bool
        if the support has to be covered (e.g., inside shelf, microwave, etc.)
    gravity: torch.Tensor | None
        a normalized gravity vector indicating the gravity direction in world coordinate system
        that the support surface is holding against. ``None`` means ``-z``.
    gravity_tolerance: float
        tolerance for gravity direction alignment
    envs_idx: None | slice | torch.Tensor, optional
        the indices of the environments to query. If None, all environments are considered. Defaults to None.
    """
    if gravity is None:
        gravity = torch.tensor([0.0, 0.0, -1.0], device=gs.device)
    # relative=False: world frame (consistent with get_AABB and the pre-1.1.1 default).
    _pos_cur = self._entity.get_pos(envs_idx=envs_idx, relative=False)
    _quat_cur = self._entity.get_quat(envs_idx=envs_idx, relative=False)
    _T_cur = trans_quat_to_T(_pos_cur, _quat_cur)

    if envs_idx is not None and self._T_reference_inv.shape[0] != 1:
        T_revert = _T_cur @ self._T_reference_inv[envs_idx]
    else:
        T_revert = _T_cur @ self._T_reference_inv

    res: list[ExtendedSupportData] = []
    for d in self._support_data_list:
        if minimum_clearance > 0 or require_roof:
            if require_roof and d.clearance == math.inf:
                continue
            if d.clearance < minimum_clearance:
                continue

        normal = transform_by_R(d.normal, T_revert[..., :3, :3])
        normal = normal / torch.norm(normal, dim=-1, keepdim=True)

        # check if the normal is aligned with the opposite gravity direction
        normalized_gravity = gravity / torch.norm(gravity, dim=-1, keepdim=True)
        valid_mask = torch.isclose(
            (normal * -normalized_gravity).sum(dim=-1),
            torch.ones(normal.shape[0], device=gs.device, dtype=gs.tc_float),
            atol=gravity_tolerance,
        )
        res.append(
            ExtendedSupportData(
                polygon=d.polygon,
                transform=(
                    T_revert @ d.transform[envs_idx]
                    if envs_idx is not None and d.transform.shape[0] > 1
                    else T_revert @ d.transform
                ),  # (B, 4, 4)
                normal=normal,  # (B, 3)
                valid_mask=valid_mask,  # (B, )
                sample_points=d.sample_points,
                num_sample_points=d.num_sample_points,
            )
        )
    return res

Terrain

Terrain

Terrain(env, options)

Bases: RigidEntity

A special Entity that represents the terrain.

Source code in eden/entities/terrain.py
def __init__(self, env, options):
    super().__init__(env, options)
    if self._options.use_checker_surface:
        self.surface = self._create_checker_surface()

Attributes

surface instance-attribute

surface = self._create_checker_surface()

subterrain_origins property

subterrain_origins: Tensor

(rows, cols, 3) world-frame subterrain cell centers (z from the height field).

goals property

goals: Tensor

(rows, cols, max_goals, 3) world-frame course waypoints per subterrain cell.

Cells with fewer goals are padded by repeating their last real goal (see :attr:goals_count).

goals_count property

goals_count: Tensor

(rows, cols) number of real (unpadded) goals per subterrain cell.

slope_vectors property

slope_vectors: Tensor

(rows, cols, 3) normalized per-cell slope directions (zeros where not applicable).

height_field_units property

height_field_units

The stitched (X, Y) height field in vertical_scale units (numpy, dim0 = x).

horizontal_scale property

horizontal_scale: float

vertical_scale property

vertical_scale: float

bounds property

bounds: tuple[float, float, float, float]

Return the terrain limits as an (x_min, x_max, y_min, y_max) tuple.

RETURNS DESCRIPTION
tuple[float, float, float, float]

The terrain bounds in xxyy format (matching _bounds).

Methods:

pre_build

pre_build() -> None
Source code in eden/entities/terrain.py
def pre_build(self) -> None:
    super().pre_build()
    (terrain_geom,) = self._entity.geoms

    assert "height_field" in terrain_geom.metadata
    height_field = terrain_geom.metadata["height_field"]
    self._height_field = torch.as_tensor(height_field, device=gs.device, dtype=gs.tc_float)
    assert self._height_field.ndim == 2, f"Height field must be 2D, but got {self._height_field.shape}"
    self._height_field *= self._entity.morph.vertical_scale

    # NOTE: reshape from (width, height) to (height, width) for grid_sample calculation
    # NOTE: we only need one copy since all environments share the same terrain
    self._height_field = self._height_field.T
    # TODO: heterogeneous terrain support
    self._height_field = self._height_field[None, None, :, :].expand(self._env.num_envs, -1, -1, -1)

post_build

post_build() -> None
Source code in eden/entities/terrain.py
def post_build(self) -> None:
    super().post_build()

    # relative=False: world-frame origin (matching the pre-1.1.1 Genesis default).
    self._origin = self._entity.get_pos(relative=False)[0]

    # get_height returns WORLD z: the morph pos z physically raises the surface,
    # so height queries must carry the same offset (the offline exporter's npz
    # already carries origin z the same way). Offset the underlying 2D grid and
    # re-expand so the per-env dimension stays a zero-copy view.
    offset_field = self._height_field[0, 0] + self._origin[2]
    self._height_field = offset_field[None, None, :, :].expand(self._env.num_envs, -1, -1, -1)

    # Calculate total terrain size from subterrain configuration
    subterrain_size = self._entity.morph.subterrain_size
    n_subterrains = self._entity.morph.n_subterrains

    total_x_size = subterrain_size[0] * n_subterrains[0]
    total_y_size = subterrain_size[1] * n_subterrains[1]
    self._size = (total_x_size, total_y_size)

    # Calculate bounds from origin and size
    x_min = self._origin[0]
    y_min = self._origin[1]
    x_max = x_min + total_x_size
    y_max = y_min + total_y_size
    self._bounds = (x_min, x_max, y_min, y_max)

    self._bounds_min_buffer = torch.tensor([x_min, y_min], device=gs.device, dtype=gs.tc_float)
    self._bounds_inv_range_buffer = torch.tensor(
        [1.0 / (x_max - x_min), 1.0 / (y_max - y_min)],
        device=gs.device,
        dtype=gs.tc_float,
    )

    self._build_subterrain_buffers()

export_height_field

export_height_field(path: str) -> None

Export this terrain's height field (in meters) to path.

Writes the versioned .npz format defined in :mod:eden.utils.terrains.io, usable by offline tools (e.g. the TCRS terrain-adaptive motion retargeter) without rebuilding the scene.

Source code in eden/entities/terrain.py
def export_height_field(self, path: str) -> None:
    """Export this terrain's height field (in meters) to ``path``.

    Writes the versioned ``.npz`` format defined in
    :mod:`eden.utils.terrains.io`, usable by offline tools (e.g. the TCRS
    terrain-adaptive motion retargeter) without rebuilding the scene.
    """
    from eden.utils.terrains.io import export_height_field

    if not hasattr(self, "_origin"):
        raise RuntimeError("Terrain.export_height_field requires a built scene — call env.build() first.")

    (terrain_geom,) = self._entity.geoms
    height_field_m = terrain_geom.metadata["height_field"] * self._entity.morph.vertical_scale
    export_height_field(
        path,
        height_field_m=height_field_m,
        horizontal_scale=self._entity.morph.horizontal_scale,
        origin=tuple(self._origin.tolist()),
    )

get_height

get_height(grid: Tensor, normalize: bool = True) -> Tensor

Get the height of the terrain at the given xy coordinates.

PARAMETER DESCRIPTION
grid

The grid of xy coordinates to sample the height from: world xy (m) if normalize is True, else already normalized to the terrain bounds as [-1, 1].

TYPE: (Tensor, shape(B, h_out, w_out, 2))

normalize

Whether to normalize world xy to [-1, 1] here (into a new tensor; grid is not modified). Defaults to True.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
height

The world-frame z of the terrain surface at the given xy coordinates.

TYPE: (Tensor, shape(B, h_out, w_out))

Source code in eden/entities/terrain.py
def get_height(self, grid: torch.Tensor, normalize: bool = True) -> torch.Tensor:
    """
    Get the height of the terrain at the given xy coordinates.

    Parameters
    ----------
    grid : torch.Tensor, shape (B, h_out, w_out, 2)
        The grid of xy coordinates to sample the height from: world xy (m) if ``normalize`` is True, else
        already normalized to the terrain bounds as [-1, 1].
    normalize : bool, optional
        Whether to normalize world xy to [-1, 1] here (into a new tensor; ``grid`` is not modified).
        Defaults to True.

    Returns
    -------
    height : torch.Tensor, shape (B, h_out, w_out)
        The world-frame z of the terrain surface at the given xy coordinates.
    """
    if normalize:
        # Normalize coordinates to [-1, 1] range expected by grid_sample
        # norm_xy = 2 * (xy - mins) / (maxs - mins) - 1
        grid = 2 * (grid - self._bounds_min_buffer) * self._bounds_inv_range_buffer - 1.0

    # Border padding mode isn't supported on Mac GPU (mps)
    # https://github.com/pytorch/pytorch/issues/125098
    interpolated = F.grid_sample(
        self._height_field,  # B, 1, height, width
        grid,  # B, h_out, w_out, 2
        mode="bilinear",
        padding_mode="border" if gs.device.type != "mps" else "zeros",
        align_corners=True,
    )  # B, 1, h_out, w_out

    return interpolated.squeeze(1)  # B, h_out, w_out

Deformables

set_pos() and set_quat() raise on a ParticleEntity (MPM, SPH, PBD), and set_quat() raises on a FEMEntity: bake the pose into the morph when you create the entity.

ParticleEntity

ParticleEntity(env: EnvBase, options: EntityOptions)

Bases: Entity

Entity subclass for particle-based materials (MPM, SPH, PBD).

Particle entities have no DOFs, links, or orientation control. Position and rotation must be baked into the morph at creation time.

Source code in eden/entities/base.py
def __init__(self, env: EnvBase, options: EntityOptions):
    self._options = options
    self._env = env
    self._is_attached = False
    self._attachment_parent: Entity | None = None
    self.is_heterogeneous = isinstance(self._options, GroupedEntityOptions)
    # Heterogeneous only: each env's own variant default pose, (num_envs, 3) / (num_envs, 4).
    self._variant_default_root_pos: torch.Tensor | None = None
    self._variant_default_root_quat: torch.Tensor | None = None
    # Heterogeneous only: the variant each env holds, (num_envs,) long, and the first env holding each variant
    # present (all of them, unless there are fewer envs than variants).
    self._env_variant_idx: torch.Tensor | None = None
    self._variant_first_envs: list[int] = []
    # Heterogeneous only: each present variant's value for a per-variant field they differ in; a field
    # they all resolve alike is mirrored as that one value instead. And each variant's material, present or
    # not, so the checks on it do not depend on the env count.
    self._variant_fields: dict[str, list] = {}
    self._variant_materials: list[MaterialLike] | None = None

    # Create morph from options if not already set
    if not self.is_heterogeneous:
        self._create_morph_from_options()
    else:
        self._create_grouped_morphs_from_options()

    # Mirror options onto the instance. Entity does not delegate __init__ to
    # ConfigurableMixin -- see mirror_options_onto's docstring for why, and for the
    # aliasing this protects against. Unlike the mixin, Entity sets no ``_uid``: its
    # ``uid`` is the wrapped Genesis entity's, so there is nothing to seed here.
    # SupportSurfaceMixin's attributes need no skip list -- get_parameter_names() walks
    # only ConfigurableMixin bases, so they never show up here.
    mirror_options_onto(self, self._options)
    if self.is_heterogeneous:
        self._resolve_variant_fields()

    # forward_vec is derived from the options' front direction (single source of truth);
    # entities without a front (e.g. primitives) keep the class default.
    self.forward_vec = getattr(self._options, "front", self.forward_vec)
    self.forward_vec = torch.tensor(self.forward_vec, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_pos = torch.tensor(self.default_root_pos, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_quat = torch.tensor(
        self.default_root_quat,
        dtype=gs.tc_float,
        device=self.device,
    ).unsqueeze(0)

Methods:

set_pos

set_pos(pos, envs_idx=None) -> None

Particle entities do not support set_pos after build.

RAISES DESCRIPTION
NotImplementedError

Always. Bake position into the morph (e.g. gs.morphs.Mesh(pos=...)) instead.

Source code in eden/entities/particle.py
def set_pos(self, pos, envs_idx=None) -> None:
    """Particle entities do not support set_pos after build.

    Raises
    ------
    NotImplementedError
        Always. Bake position into the morph (e.g. ``gs.morphs.Mesh(pos=...)``) instead.
    """
    raise NotImplementedError(
        f"ParticleEntity '{self.name}' does not support set_pos. "
        "Bake position into the morph (e.g. gs.morphs.Mesh(pos=...)) instead."
    )

set_quat

set_quat(quat, envs_idx=None) -> None

Particle entities do not support set_quat after build.

RAISES DESCRIPTION
NotImplementedError

Always. Bake rotation into the morph (e.g. gs.morphs.Mesh(euler=...)) instead.

Source code in eden/entities/particle.py
def set_quat(self, quat, envs_idx=None) -> None:
    """Particle entities do not support set_quat after build.

    Raises
    ------
    NotImplementedError
        Always. Bake rotation into the morph (e.g. ``gs.morphs.Mesh(euler=...)``) instead.
    """
    raise NotImplementedError(
        f"ParticleEntity '{self.name}' does not support set_quat. "
        "Bake rotation into the morph (e.g. gs.morphs.Mesh(euler=...)) instead."
    )

get_mass

get_mass()

Return the total particle mass from Genesis' get_mass.

Falls back to 0, with a warning, when the Genesis entity's get_mass raises TypeError.

Source code in eden/entities/particle.py
def get_mass(self):
    """Return the total particle mass from Genesis' ``get_mass``.

    Falls back to 0, with a warning, when the Genesis entity's ``get_mass`` raises ``TypeError``.
    """
    try:
        return self._entity.get_mass()
    except TypeError:
        en.logger.warning(
            f"ParticleEntity '{self.name}': get_mass() not supported by Genesis particle entity. Returning 0."
        )
        return 0

get_particles_in_bbox

get_particles_in_bbox(lower, upper)

Return a mask of particles whose rest position lies in an axis-aligned box.

Thin wrapper around the Genesis MPM entity's get_particles_in_bbox. Handy for selecting a subset of particles to constrain (see set_particle_constraints).

PARAMETER DESCRIPTION
lower

Lower/upper corners of the bounding box, in world coordinates.

TYPE: array_like[float, float, float]

upper

Lower/upper corners of the bounding box, in world coordinates.

TYPE: array_like[float, float, float]

Source code in eden/entities/particle.py
def get_particles_in_bbox(self, lower, upper):
    """Return a mask of particles whose rest position lies in an axis-aligned box.

    Thin wrapper around the Genesis MPM entity's ``get_particles_in_bbox``. Handy
    for selecting a subset of particles to constrain (see ``set_particle_constraints``).

    Parameters
    ----------
    lower, upper : array_like[float, float, float]
        Lower/upper corners of the bounding box, in world coordinates.
    """
    return self._entity.get_particles_in_bbox(lower, upper)

set_particle_constraints

set_particle_constraints(mask, link_idx, **kwargs)

Attach the masked particles to a rigid link (soft spring constraint).

Thin wrapper around the Genesis MPM entity's set_particle_constraints, so an MPM soft body can be welded to a moving rigid body without reaching through entity.entity.

PARAMETER DESCRIPTION
mask

Boolean/index mask of particles to constrain (e.g. from get_particles_in_bbox).

TYPE: array_like

link_idx

Global index of the rigid link to follow (e.g. rigid.links[0].idx).

TYPE: int

**kwargs

Forwarded to Genesis (e.g. stiffness).

DEFAULT: {}

Source code in eden/entities/particle.py
def set_particle_constraints(self, mask, link_idx, **kwargs):
    """Attach the masked particles to a rigid link (soft spring constraint).

    Thin wrapper around the Genesis MPM entity's ``set_particle_constraints``, so
    an MPM soft body can be welded to a moving rigid body without reaching through
    ``entity.entity``.

    Parameters
    ----------
    mask : array_like
        Boolean/index mask of particles to constrain (e.g. from ``get_particles_in_bbox``).
    link_idx : int
        Global index of the rigid link to follow (e.g. ``rigid.links[0].idx``).
    **kwargs
        Forwarded to Genesis (e.g. ``stiffness``).
    """
    self._entity.set_particle_constraints(mask, link_idx, **kwargs)

FEMEntity

FEMEntity(env: EnvBase, options: EntityOptions)

Bases: Entity

Entity subclass for FEM-based deformable materials (Elastic, Cloth, Muscle).

FEM entities use vertex-based state (positions and velocities of mesh vertices) rather than rigid-body state. They have no DOFs, links, or joint-based control.

The Genesis FEMEntity does not expose get_pos/get_vel/get_quat/get_ang, so this class overrides those methods to provide a compatible interface (mean vertex position, mean velocity, identity quaternion, zero angular velocity).

Source code in eden/entities/base.py
def __init__(self, env: EnvBase, options: EntityOptions):
    self._options = options
    self._env = env
    self._is_attached = False
    self._attachment_parent: Entity | None = None
    self.is_heterogeneous = isinstance(self._options, GroupedEntityOptions)
    # Heterogeneous only: each env's own variant default pose, (num_envs, 3) / (num_envs, 4).
    self._variant_default_root_pos: torch.Tensor | None = None
    self._variant_default_root_quat: torch.Tensor | None = None
    # Heterogeneous only: the variant each env holds, (num_envs,) long, and the first env holding each variant
    # present (all of them, unless there are fewer envs than variants).
    self._env_variant_idx: torch.Tensor | None = None
    self._variant_first_envs: list[int] = []
    # Heterogeneous only: each present variant's value for a per-variant field they differ in; a field
    # they all resolve alike is mirrored as that one value instead. And each variant's material, present or
    # not, so the checks on it do not depend on the env count.
    self._variant_fields: dict[str, list] = {}
    self._variant_materials: list[MaterialLike] | None = None

    # Create morph from options if not already set
    if not self.is_heterogeneous:
        self._create_morph_from_options()
    else:
        self._create_grouped_morphs_from_options()

    # Mirror options onto the instance. Entity does not delegate __init__ to
    # ConfigurableMixin -- see mirror_options_onto's docstring for why, and for the
    # aliasing this protects against. Unlike the mixin, Entity sets no ``_uid``: its
    # ``uid`` is the wrapped Genesis entity's, so there is nothing to seed here.
    # SupportSurfaceMixin's attributes need no skip list -- get_parameter_names() walks
    # only ConfigurableMixin bases, so they never show up here.
    mirror_options_onto(self, self._options)
    if self.is_heterogeneous:
        self._resolve_variant_fields()

    # forward_vec is derived from the options' front direction (single source of truth);
    # entities without a front (e.g. primitives) keep the class default.
    self.forward_vec = getattr(self._options, "front", self.forward_vec)
    self.forward_vec = torch.tensor(self.forward_vec, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_pos = torch.tensor(self.default_root_pos, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_quat = torch.tensor(
        self.default_root_quat,
        dtype=gs.tc_float,
        device=self.device,
    ).unsqueeze(0)

Attributes

n_vertices property

n_vertices: int

n_elements property

n_elements: int

Methods:

get_pos

get_pos(envs_idx=None, *, relative: bool = False)

Return mean vertex position (geometric centroid), shape (B, 3).

Note: this is the unweighted mean of vertex positions, not the mass-weighted center of mass. relative must be False (see :meth:~eden.entities.base.Entity._reject_relative).

Source code in eden/entities/fem.py
def get_pos(self, envs_idx=None, *, relative: bool = False):
    """Return mean vertex position (geometric centroid), shape (B, 3).

    Note: this is the unweighted mean of vertex positions, not the
    mass-weighted center of mass. ``relative`` must be False (see
    :meth:`~eden.entities.base.Entity._reject_relative`).
    """
    self._reject_relative(relative, "get_pos")
    state = self._entity.get_state()
    pos = state.pos  # (B, n_vertices, 3)
    centroid = pos.mean(dim=1)  # (B, 3)
    if envs_idx is not None:
        return centroid[envs_idx]
    return centroid

get_vel

get_vel(envs_idx=None, *, frame: Literal['world', 'body'] = 'world')

Return mean vertex velocity, shape (B, 3). Only world frame is supported.

Source code in eden/entities/fem.py
def get_vel(self, envs_idx=None, *, frame: Literal["world", "body"] = "world"):
    """Return mean vertex velocity, shape (B, 3). Only world frame is supported."""
    if frame != "world":
        raise ValueError(f"FEMEntity only supports frame='world', got '{frame}'.")
    state = self._entity.get_state()
    vel = state.vel  # (B, n_vertices, 3)
    mean_vel = vel.mean(dim=1)  # (B, 3)
    if envs_idx is not None:
        return mean_vel[envs_idx]
    return mean_vel

get_quat

get_quat(envs_idx=None, *, relative: bool = False)

Return identity quaternion (wxyz) for all envs, shape (B, 4).

FEM entities have no rigid-body orientation. Returns a constant identity quaternion for interface compatibility. relative must be False (see :meth:~eden.entities.base.Entity._reject_relative).

Source code in eden/entities/fem.py
def get_quat(self, envs_idx=None, *, relative: bool = False):
    """Return identity quaternion (wxyz) for all envs, shape (B, 4).

    FEM entities have no rigid-body orientation. Returns a constant identity
    quaternion for interface compatibility. ``relative`` must be False (see
    :meth:`~eden.entities.base.Entity._reject_relative`).
    """
    self._reject_relative(relative, "get_quat")
    if self._identity_quat is None or self._identity_quat.shape[0] != self._env.num_envs:
        self._identity_quat = (
            torch.tensor([1.0, 0.0, 0.0, 0.0], device=self.device).expand(self._env.num_envs, -1).contiguous()
        )
    if envs_idx is not None:
        return self._identity_quat[envs_idx]
    return self._identity_quat

get_ang

get_ang(envs_idx=None, *, frame: Literal['world', 'body'] = 'world')

Return zero angular velocity for all envs, shape (B, 3). Only world frame is supported.

Source code in eden/entities/fem.py
def get_ang(self, envs_idx=None, *, frame: Literal["world", "body"] = "world"):
    """Return zero angular velocity for all envs, shape (B, 3). Only world frame is supported."""
    if frame != "world":
        raise ValueError(f"FEMEntity only supports frame='world', got '{frame}'.")
    if self._zero_ang is None or self._zero_ang.shape[0] != self._env.num_envs:
        self._zero_ang = torch.zeros(self._env.num_envs, 3, device=self.device)
    if envs_idx is not None:
        return self._zero_ang[envs_idx]
    return self._zero_ang

set_com_offset

set_com_offset(offset, envs_idx=None)

Place the soft body's undeformed build-time shape, translated by offset.

Every vertex lands at its build-time position plus offset, so the body is reset to its undeformed shape (any current deformation is discarded), and the offset is always measured from the build-time pose: repeated calls do not accumulate, like :meth:~eden.entities.rigid.RigidEntity.set_COM_shift is measured from the nominal COM. For an absolute centroid position use :meth:set_pos.

PARAMETER DESCRIPTION
offset

(3,) (applied to all envs) or (B, 3) (per-env) — an offset from the build-time pose, not an absolute world position. Moved to the sim device, so a host tensor, array or list is fine.

TYPE: array_like

envs_idx

Per-env addressing is unsupported by Genesis FEM entities; must be None.

TYPE: None DEFAULT: None

RAISES DESCRIPTION
ValueError

If offset is not rank-1/2 with a trailing dim of 3, or looks like per-vertex data (a (n_vertices, 3) array — use :meth:set_vertex_positions). The (num_envs, 3) vs (n_vertices, 3) shapes only collide when num_envs == n_vertices; then the per-env offset reading this method is named for is taken. (Genesis set_position, and so :meth:set_pos, reads that shape the other way, as per-vertex positions.)

Source code in eden/entities/fem.py
def set_com_offset(self, offset, envs_idx=None):
    """Place the soft body's undeformed build-time shape, translated by ``offset``.

    Every vertex lands at its build-time position plus ``offset``, so the
    body is reset to its **undeformed** shape (any current deformation is
    discarded), and the offset is always measured from the build-time pose:
    repeated calls do not accumulate, like
    :meth:`~eden.entities.rigid.RigidEntity.set_COM_shift` is measured from
    the nominal COM. For an absolute centroid position use :meth:`set_pos`.

    Parameters
    ----------
    offset : array_like
        ``(3,)`` (applied to all envs) or ``(B, 3)`` (per-env) — an offset
        **from the build-time pose**, not an absolute world position. Moved
        to the sim device, so a host tensor, array or list is fine.
    envs_idx : None
        Per-env addressing is unsupported by Genesis FEM entities; must be None.

    Raises
    ------
    ValueError
        If ``offset`` is not rank-1/2 with a trailing dim of 3, or looks like
        per-vertex data (a ``(n_vertices, 3)`` array — use
        :meth:`set_vertex_positions`). The ``(num_envs, 3)`` vs
        ``(n_vertices, 3)`` shapes only collide when ``num_envs ==
        n_vertices``; then the per-env offset reading this method is named
        for is taken. (Genesis ``set_position``, and so :meth:`set_pos`,
        reads that shape the other way, as per-vertex positions.)
    """
    init_positions = self._entity.init_positions
    offset = torch.as_tensor(offset, dtype=init_positions.dtype, device=init_positions.device)
    # A (n_vertices, 3) array is per-vertex data, not an offset. The shape
    # only collides with a per-env (num_envs, 3) offset when the two counts
    # are equal, in which case we cannot tell them apart and take the offset
    # reading (Genesis would take the per-vertex one; see the forward below).
    n_vertices_differs_from_envs = self.n_vertices != self._env.num_envs
    looks_like_vertices = offset.ndim == 2 and offset.shape[0] == self.n_vertices and n_vertices_differs_from_envs
    if offset.shape[-1] != 3 or offset.ndim > 2 or looks_like_vertices:
        raise ValueError(
            f"set_com_offset expects (3,) or (num_envs, 3); got {tuple(offset.shape)}. "
            f"For absolute per-vertex positions (vertex dim {self.n_vertices}) use set_vertex_positions()."
        )
    # Genesis' own rank-1/2 branch adds the value to the *COM-subtracted* build-time vertices, i.e. it takes an
    # absolute centroid (that is ``set_pos``). Add the offset to the build-time vertices instead and forward
    # absolute per-vertex positions: (3,) -> (n_vertices, 3) tiled, (B, 3) -> (B, n_vertices, 3). Neither shape is
    # ambiguous to Genesis, so the num_envs == n_vertices collision never reaches its rank dispatch.
    self._set_position(init_positions + offset.unsqueeze(-2), envs_idx)

set_vertex_positions

set_vertex_positions(pos, envs_idx=None)

Set absolute per-vertex world positions of the soft body.

This is the rank-2 / rank-3 case of Genesis set_position. Prefer this over :meth:set_pos when you have full vertex coordinates, so the absolute-vertex (vs. centroid) semantics is explicit at the call site.

PARAMETER DESCRIPTION
pos

(n_vertices, 3) (tiled to all envs) or (B, n_vertices, 3) (full batched) absolute per-vertex positions.

TYPE: Tensor

envs_idx

Per-env addressing is unsupported by Genesis FEM entities; must be None.

TYPE: None DEFAULT: None

RAISES DESCRIPTION
ValueError

If pos is not rank-2/3 with a trailing dim of 3 and a vertex dimension equal to n_vertices (for a COM offset use :meth:set_com_offset).

Source code in eden/entities/fem.py
def set_vertex_positions(self, pos, envs_idx=None):
    """Set absolute per-vertex world positions of the soft body.

    This is the rank-2 / rank-3 case of Genesis ``set_position``. Prefer
    this over :meth:`set_pos` when you have full vertex coordinates, so the
    absolute-vertex (vs. centroid) semantics is explicit at the call site.

    Parameters
    ----------
    pos : torch.Tensor
        ``(n_vertices, 3)`` (tiled to all envs) or ``(B, n_vertices, 3)``
        (full batched) absolute per-vertex positions.
    envs_idx : None
        Per-env addressing is unsupported by Genesis FEM entities; must be None.

    Raises
    ------
    ValueError
        If ``pos`` is not rank-2/3 with a trailing dim of 3 and a vertex
        dimension equal to ``n_vertices`` (for a COM offset use
        :meth:`set_com_offset`).
    """
    # For both accepted shapes the vertex count is the second-to-last axis;
    # ndim outside {2, 3} (a bare offset or a too-deep batch) has no vertex axis.
    vertex_dim = pos.shape[-2] if pos.ndim in (2, 3) else None
    if pos.shape[-1] != 3 or vertex_dim != self.n_vertices:
        raise ValueError(
            f"set_vertex_positions expects (n_vertices={self.n_vertices}, 3) or "
            f"(B, n_vertices, 3); got {tuple(pos.shape)}. For a COM offset use set_com_offset()."
        )
    self._set_position(pos, envs_idx)

set_pos

set_pos(pos, envs_idx=None)

Set FEM state via Genesis set_position (shape-dispatched).

Genesis interprets the tensor rank as either a centroid position or absolute per-vertex positions; this method only forwards. Because that dual meaning is invisible at the call site, prefer the explicit :meth:set_com_offset / :meth:set_vertex_positions instead. Kept for compatibility with the generic :class:Entity interface.

Accepted shapes:

  • (3,) — world position of the vertex centroid, applied to all envs.
  • (n_vertices, 3) — absolute per-vertex positions, tiled to all envs.
  • (B, 3) — per-env world positions of the vertex centroid.
  • (B, n_vertices, 3) — full batched per-vertex absolute positions.

(3,) and (B, 3) are absolute: they place the undeformed build-time shape so that its vertex centroid sits at that world position, which is what :meth:get_pos reads back. For an offset from the build-time pose use :meth:set_com_offset.

When num_envs == n_vertices the (B, 3) and (n_vertices, 3) shapes coincide, and Genesis checks (n_vertices, 3) first: a per-env centroid tensor is then silently read as per-vertex positions, collapsing the body. Use :meth:set_vertex_positions or :meth:set_com_offset, which name their meaning, in that case.

PARAMETER DESCRIPTION
pos

Position tensor (see shapes above).

TYPE: Tensor

envs_idx

Not supported by Genesis FEM entities. Must be None.

TYPE: None DEFAULT: None

RAISES DESCRIPTION
NotImplementedError

If envs_idx is not None.

Source code in eden/entities/fem.py
def set_pos(self, pos, envs_idx=None):
    """Set FEM state via Genesis ``set_position`` (shape-dispatched).

    Genesis interprets the tensor rank as either a centroid position or
    absolute per-vertex positions; this method only forwards. Because that dual
    meaning is invisible at the call site, **prefer the explicit
    :meth:`set_com_offset` / :meth:`set_vertex_positions`** instead. Kept for
    compatibility with the generic :class:`Entity` interface.

    Accepted shapes:

    - ``(3,)`` — world position of the vertex centroid, applied to all envs.
    - ``(n_vertices, 3)`` — absolute per-vertex positions, tiled to all envs.
    - ``(B, 3)`` — per-env world positions of the vertex centroid.
    - ``(B, n_vertices, 3)`` — full batched per-vertex absolute positions.

    ``(3,)`` and ``(B, 3)`` are **absolute**: they place the undeformed
    build-time shape so that its vertex centroid sits at that world
    position, which is what :meth:`get_pos` reads back. For an offset from
    the build-time pose use :meth:`set_com_offset`.

    When ``num_envs == n_vertices`` the ``(B, 3)`` and ``(n_vertices, 3)``
    shapes coincide, and Genesis checks ``(n_vertices, 3)`` first: a
    per-env centroid tensor is then silently read as per-vertex positions,
    collapsing the body. Use :meth:`set_vertex_positions` or
    :meth:`set_com_offset`, which name their meaning, in that case.

    Parameters
    ----------
    pos : torch.Tensor
        Position tensor (see shapes above).
    envs_idx : None
        Not supported by Genesis FEM entities. Must be None.

    Raises
    ------
    NotImplementedError
        If envs_idx is not None.
    """
    self._set_position(pos, envs_idx)

set_quat

set_quat(quat, envs_idx=None)

FEM entities do not support set_quat.

RAISES DESCRIPTION
NotImplementedError

Always. Bake rotation into the morph (e.g. gs.morphs.Mesh(euler=...)) instead.

Source code in eden/entities/fem.py
def set_quat(self, quat, envs_idx=None):
    """FEM entities do not support set_quat.

    Raises
    ------
    NotImplementedError
        Always. Bake rotation into the morph (e.g. ``gs.morphs.Mesh(euler=...)``) instead.
    """
    raise NotImplementedError(
        f"FEMEntity '{self.name}' does not support set_quat. "
        "Bake rotation into the morph (e.g. gs.morphs.Mesh(euler=...)) instead."
    )

get_mass

get_mass()

Return FEM entity mass. Falls back to 0 if Genesis does not support it.

Source code in eden/entities/fem.py
def get_mass(self):
    """Return FEM entity mass. Falls back to 0 if Genesis does not support it."""
    try:
        return self._entity.get_mass()
    except (TypeError, AttributeError):
        en.logger.warning(f"FEMEntity '{self.name}': get_mass() not supported by Genesis FEM entity. Returning 0.")
        return 0

get_vertex_positions

get_vertex_positions(envs_idx=None)

Return per-vertex positions, shape (B, n_vertices, 3).

Source code in eden/entities/fem.py
def get_vertex_positions(self, envs_idx=None):
    """Return per-vertex positions, shape (B, n_vertices, 3)."""
    state = self._entity.get_state()
    pos = state.pos
    if envs_idx is not None:
        return pos[envs_idx]
    return pos

get_vertex_velocities

get_vertex_velocities(envs_idx=None)

Return per-vertex velocities, shape (B, n_vertices, 3).

Source code in eden/entities/fem.py
def get_vertex_velocities(self, envs_idx=None):
    """Return per-vertex velocities, shape (B, n_vertices, 3)."""
    state = self._entity.get_state()
    vel = state.vel
    if envs_idx is not None:
        return vel[envs_idx]
    return vel

set_actuation

set_actuation(actu)

Set muscle actuation signal (Muscle material only).

PARAMETER DESCRIPTION
actu

Actuation tensor. Accepted shapes: - (): scalar for all groups - (n_groups,): per-group actuation - (B, n_groups): batched per-group actuation

TYPE: Tensor

Source code in eden/entities/fem.py
def set_actuation(self, actu):
    """Set muscle actuation signal (Muscle material only).

    Parameters
    ----------
    actu : torch.Tensor
        Actuation tensor. Accepted shapes:
        - (): scalar for all groups
        - (n_groups,): per-group actuation
        - (B, n_groups): batched per-group actuation
    """
    self._entity.set_actuation(actu)

set_muscle

set_muscle(muscle_group=None, muscle_direction=None)

Set muscle group IDs and/or fiber directions (Muscle material only).

PARAMETER DESCRIPTION
muscle_group

Shape (n_elements,) — muscle group ID per element.

TYPE: array_like DEFAULT: None

muscle_direction

Shape (n_elements, 3) — unit direction vectors for muscle forces.

TYPE: array_like DEFAULT: None

Source code in eden/entities/fem.py
def set_muscle(self, muscle_group=None, muscle_direction=None):
    """Set muscle group IDs and/or fiber directions (Muscle material only).

    Parameters
    ----------
    muscle_group : array_like, optional
        Shape (n_elements,) — muscle group ID per element.
    muscle_direction : array_like, optional
        Shape (n_elements, 3) — unit direction vectors for muscle forces.
    """
    self._entity.set_muscle(muscle_group=muscle_group, muscle_direction=muscle_direction)

set_vertex_constraints

set_vertex_constraints(verts_idx_local, target_poss=None, link=None, **kwargs)

Pin vertices to target positions or to a rigid link.

Thin wrapper around Genesis FEMEntity.set_vertex_constraints.

PARAMETER DESCRIPTION
verts_idx_local

Local vertex indices to constrain.

TYPE: array_like

target_poss

Target positions (len(verts_idx), 3). If None, uses current positions.

TYPE: array_like DEFAULT: None

link

Rigid link for vertices to follow.

TYPE: RigidLink DEFAULT: None

**kwargs

Additional keyword arguments forwarded to Genesis (e.g., is_soft_constraint, stiffness, envs_idx).

DEFAULT: {}

Source code in eden/entities/fem.py
def set_vertex_constraints(self, verts_idx_local, target_poss=None, link=None, **kwargs):
    """Pin vertices to target positions or to a rigid link.

    Thin wrapper around Genesis FEMEntity.set_vertex_constraints.

    Parameters
    ----------
    verts_idx_local : array_like
        Local vertex indices to constrain.
    target_poss : array_like, optional
        Target positions (len(verts_idx), 3). If None, uses current positions.
    link : RigidLink, optional
        Rigid link for vertices to follow.
    **kwargs
        Additional keyword arguments forwarded to Genesis (e.g., is_soft_constraint,
        stiffness, envs_idx).
    """
    self._entity.set_vertex_constraints(verts_idx_local, target_poss=target_poss, link=link, **kwargs)

Mochi entities

Entities simulated by the Eden-hosted MochiSolver. A Mochi scene is all-Mochi; see Engine and the physics guide.

MochiRigidEntity

MochiRigidEntity(env: EnvBase, options: EntityOptions)

Bases: RigidEntity

A rigid or articulated body simulated by the MochiSolver.

Everything :class:RigidEntity offers works unchanged except what the solver never reads: drive-force ranges, joint friction loss and constraint-solver parameters (rejected at options validation), link mass/inertia edits after build (set_mass raises), support surfaces and AABB queries. Contact behaviour is a material property (friction, penalty_coefficient, ...) and can be re-tuned per link with :meth:set_contact_params.

Source code in eden/entities/base.py
def __init__(self, env: EnvBase, options: EntityOptions):
    self._options = options
    self._env = env
    self._is_attached = False
    self._attachment_parent: Entity | None = None
    self.is_heterogeneous = isinstance(self._options, GroupedEntityOptions)
    # Heterogeneous only: each env's own variant default pose, (num_envs, 3) / (num_envs, 4).
    self._variant_default_root_pos: torch.Tensor | None = None
    self._variant_default_root_quat: torch.Tensor | None = None
    # Heterogeneous only: the variant each env holds, (num_envs,) long, and the first env holding each variant
    # present (all of them, unless there are fewer envs than variants).
    self._env_variant_idx: torch.Tensor | None = None
    self._variant_first_envs: list[int] = []
    # Heterogeneous only: each present variant's value for a per-variant field they differ in; a field
    # they all resolve alike is mirrored as that one value instead. And each variant's material, present or
    # not, so the checks on it do not depend on the env count.
    self._variant_fields: dict[str, list] = {}
    self._variant_materials: list[MaterialLike] | None = None

    # Create morph from options if not already set
    if not self.is_heterogeneous:
        self._create_morph_from_options()
    else:
        self._create_grouped_morphs_from_options()

    # Mirror options onto the instance. Entity does not delegate __init__ to
    # ConfigurableMixin -- see mirror_options_onto's docstring for why, and for the
    # aliasing this protects against. Unlike the mixin, Entity sets no ``_uid``: its
    # ``uid`` is the wrapped Genesis entity's, so there is nothing to seed here.
    # SupportSurfaceMixin's attributes need no skip list -- get_parameter_names() walks
    # only ConfigurableMixin bases, so they never show up here.
    mirror_options_onto(self, self._options)
    if self.is_heterogeneous:
        self._resolve_variant_fields()

    # forward_vec is derived from the options' front direction (single source of truth);
    # entities without a front (e.g. primitives) keep the class default.
    self.forward_vec = getattr(self._options, "front", self.forward_vec)
    self.forward_vec = torch.tensor(self.forward_vec, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_pos = torch.tensor(self.default_root_pos, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_quat = torch.tensor(
        self.default_root_quat,
        dtype=gs.tc_float,
        device=self.device,
    ).unsqueeze(0)

Methods:

set_mass

set_mass(mass)
Source code in eden/entities/mochi.py
def set_mass(self, mass):
    raise NotImplementedError(
        f"MochiRigidEntity '{self.name}': link masses are fixed at build by the MochiSolver. Set "
        f"`MochiRigidMaterialOptions.rho` or give the asset an explicit mass instead."
    )

get_contacts

get_contacts(with_entity: Entity | None = None, exclude_self_contact: bool = False, is_padded: bool = False)

Contact records of this body, optionally filtered to the pair with with_entity.

Requires MochiOptions.record_contacts (the default). Each record carries both sides' entities/links/geoms (-1 where a side is deformable), the deformable-side vertices and barycentric weights, position, unit normal (away from side B), signed distance, force on A and quadrature weight. Shapes are (n_envs, n_contacts, ...) when batched.

PARAMETER DESCRIPTION
with_entity

Restrict to contacts with this (Eden) entity. Defaults to None (all partners).

TYPE: Entity DEFAULT: None

exclude_self_contact

Drop contacts between two links of this entity. Defaults to False.

TYPE: bool DEFAULT: False

is_padded

Return fixed-size padded arrays with a valid_mask, the invalid slots' floating-point fields zeroed; taken regardless while the env's graded phase runs under a CUDA graph (see :meth:~eden.entities.base.Entity._query_contacts). Defaults to False.

TYPE: bool DEFAULT: False

Source code in eden/entities/mochi.py
def get_contacts(
    self, with_entity: Entity | None = None, exclude_self_contact: bool = False, is_padded: bool = False
):
    """Contact records of this body, optionally filtered to the pair with ``with_entity``.

    Requires ``MochiOptions.record_contacts`` (the default). Each record carries both sides'
    entities/links/geoms (-1 where a side is deformable), the deformable-side vertices and
    barycentric weights, position, unit normal (away from side B), signed distance, force on
    A and quadrature weight. Shapes are ``(n_envs, n_contacts, ...)`` when batched.

    Parameters
    ----------
    with_entity : Entity, optional
        Restrict to contacts with this (Eden) entity. Defaults to None (all partners).
    exclude_self_contact : bool, optional
        Drop contacts between two links of this entity. Defaults to False.
    is_padded : bool, optional
        Return fixed-size padded arrays with a ``valid_mask``, the invalid slots' floating-point fields zeroed;
        taken regardless while the env's graded phase runs under a CUDA graph (see
        :meth:`~eden.entities.base.Entity._query_contacts`). Defaults to False.
    """
    partner = None if with_entity is None else with_entity._entity
    return self._query_contacts(
        lambda padded: self._entity.get_contacts(
            with_entity=partner, exclude_self_contact=exclude_self_contact, is_padded=padded
        ),
        is_padded,
    )

set_contact_params

set_contact_params(*, links: str | list[str] | None = None, **params) -> None

Re-tune the material's contact parameters after build, for all links or a named subset.

PARAMETER DESCRIPTION
links

Link names or regex patterns (resolved with :meth:find_named_links_idx_local). Defaults to None (every link).

TYPE: str or list[str] DEFAULT: None

**params

Any of penalty_coefficient, friction, penalty_smoothing_half_distance, penalty_threshold, friction_falloff_vel, viscous_friction, normal_viscous_damping.

DEFAULT: {}

Source code in eden/entities/mochi.py
def set_contact_params(self, *, links: str | list[str] | None = None, **params) -> None:
    """Re-tune the material's contact parameters after build, for all links or a named subset.

    Parameters
    ----------
    links : str or list[str], optional
        Link names or regex patterns (resolved with :meth:`find_named_links_idx_local`).
        Defaults to None (every link).
    **params
        Any of ``penalty_coefficient``, ``friction``, ``penalty_smoothing_half_distance``,
        ``penalty_threshold``, ``friction_falloff_vel``, ``viscous_friction``,
        ``normal_viscous_damping``.
    """
    links_idx_local = None
    if links is not None:
        _, links_idx_local = self.find_named_links_idx_local(links, preserve_order=True)
    self._entity.set_contact_params(links_idx_local=links_idx_local, **params)

set_friction

set_friction(friction: float) -> None

Set the Coulomb friction coefficient of every link.

Source code in eden/entities/mochi.py
def set_friction(self, friction: float) -> None:
    """Set the Coulomb friction coefficient of every link."""
    self._entity.set_friction(friction)

set_has_gravity

set_has_gravity(has_gravity: bool) -> None

Switch gravity on or off for this body.

Source code in eden/entities/mochi.py
def set_has_gravity(self, has_gravity: bool) -> None:
    """Switch gravity on or off for this body."""
    self._entity.set_has_gravity(has_gravity)

set_contact_enabled

set_contact_enabled(other: Entity, enabled: bool = True) -> None

Enable or disable contact between this entity and other (post-build, symmetric).

Source code in eden/entities/mochi.py
def set_contact_enabled(self, other: Entity, enabled: bool = True) -> None:
    """Enable or disable contact between this entity and ``other`` (post-build, symmetric)."""
    self._env.mochi_solver.enable_entity_contact(self._entity, other._entity, enabled)

MochiSoftEntity

MochiSoftEntity(env: EnvBase, options: EntityOptions)

Bases: Entity

A tetrahedral solid, thin shell or rod simulated by the MochiSolver.

Vertex-based like :class:FEMEntity: no links, DOFs or rigid pose. get_pos is the vertex centroid, get_quat the identity, and set_quat raises — bake the orientation into default_root_quat (the morph) instead. Four calls are pre-build only because they change what the solver assembles: :meth:attach_to_link, :meth:set_visual_mesh, :meth:set_contact_skin and :meth:set_rest_positions. Register them with env.register_pre_build_hook so they run after scene.add_entity (when :attr:init_positions exists) and before scene.build()::

env.register_pre_build_hook(lambda: finger.attach_to_link(gripper, "finger_link", verts_idx))
env.build()

:meth:set_visual_mesh goes through the morph transform, so it must precede :meth:set_rest_positions (it raises otherwise). :meth:set_contact_skin cannot be combined with :meth:set_rest_positions in either order (both raise). :meth:attach_to_link only records indices and can go anywhere in the hook.

Source code in eden/entities/base.py
def __init__(self, env: EnvBase, options: EntityOptions):
    self._options = options
    self._env = env
    self._is_attached = False
    self._attachment_parent: Entity | None = None
    self.is_heterogeneous = isinstance(self._options, GroupedEntityOptions)
    # Heterogeneous only: each env's own variant default pose, (num_envs, 3) / (num_envs, 4).
    self._variant_default_root_pos: torch.Tensor | None = None
    self._variant_default_root_quat: torch.Tensor | None = None
    # Heterogeneous only: the variant each env holds, (num_envs,) long, and the first env holding each variant
    # present (all of them, unless there are fewer envs than variants).
    self._env_variant_idx: torch.Tensor | None = None
    self._variant_first_envs: list[int] = []
    # Heterogeneous only: each present variant's value for a per-variant field they differ in; a field
    # they all resolve alike is mirrored as that one value instead. And each variant's material, present or
    # not, so the checks on it do not depend on the env count.
    self._variant_fields: dict[str, list] = {}
    self._variant_materials: list[MaterialLike] | None = None

    # Create morph from options if not already set
    if not self.is_heterogeneous:
        self._create_morph_from_options()
    else:
        self._create_grouped_morphs_from_options()

    # Mirror options onto the instance. Entity does not delegate __init__ to
    # ConfigurableMixin -- see mirror_options_onto's docstring for why, and for the
    # aliasing this protects against. Unlike the mixin, Entity sets no ``_uid``: its
    # ``uid`` is the wrapped Genesis entity's, so there is nothing to seed here.
    # SupportSurfaceMixin's attributes need no skip list -- get_parameter_names() walks
    # only ConfigurableMixin bases, so they never show up here.
    mirror_options_onto(self, self._options)
    if self.is_heterogeneous:
        self._resolve_variant_fields()

    # forward_vec is derived from the options' front direction (single source of truth);
    # entities without a front (e.g. primitives) keep the class default.
    self.forward_vec = getattr(self._options, "front", self.forward_vec)
    self.forward_vec = torch.tensor(self.forward_vec, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_pos = torch.tensor(self.default_root_pos, dtype=gs.tc_float, device=self.device).unsqueeze(0)
    self.default_root_quat = torch.tensor(
        self.default_root_quat,
        dtype=gs.tc_float,
        device=self.device,
    ).unsqueeze(0)

Attributes

init_positions property

init_positions: ndarray

Rest vertex positions in the world frame, shape (n_vertices, 3) (available once added to the scene).

elems property

elems: ndarray

Element connectivity: (n, 4) tetrahedra, (n, 3) shell triangles or (n, 2) rod segments.

n_vertices property

n_vertices: int

n_elements property

n_elements: int

is_shell property

is_shell: bool

is_rod property

is_rod: bool

Methods:

get_pos

get_pos(envs_idx=None)

Mean vertex position (unweighted centroid), shape (B, 3).

Source code in eden/entities/mochi.py
def get_pos(self, envs_idx=None):
    """Mean vertex position (unweighted centroid), shape ``(B, 3)``."""
    pos = self.get_vertex_positions()
    centroid = pos.mean(dim=1)
    return centroid if envs_idx is None else centroid[envs_idx]

get_vel

get_vel(envs_idx=None, *, frame: Literal['world', 'body'] = 'world')

Mean vertex velocity, shape (B, 3). Only the world frame is defined.

Source code in eden/entities/mochi.py
def get_vel(self, envs_idx=None, *, frame: Literal["world", "body"] = "world"):
    """Mean vertex velocity, shape ``(B, 3)``. Only the world frame is defined."""
    if frame != "world":
        raise ValueError(f"MochiSoftEntity only supports frame='world', got '{frame}'.")
    vel = self.get_vertex_velocities().mean(dim=1)
    return vel if envs_idx is None else vel[envs_idx]

get_quat

get_quat(envs_idx=None)

Identity quaternion (wxyz) for every env, shape (B, 4): a soft body has no rigid orientation.

Source code in eden/entities/mochi.py
def get_quat(self, envs_idx=None):
    """Identity quaternion (wxyz) for every env, shape ``(B, 4)``: a soft body has no rigid orientation."""
    if self._identity_quat is None or self._identity_quat.shape[0] != self._env.num_envs:
        self._identity_quat = (
            torch.tensor([1.0, 0.0, 0.0, 0.0], device=self.device).expand(self._env.num_envs, -1).contiguous()
        )
    return self._identity_quat if envs_idx is None else self._identity_quat[envs_idx]

get_ang

get_ang(envs_idx=None, *, frame: Literal['world', 'body'] = 'world')

Zero angular velocity for every env, shape (B, 3).

Source code in eden/entities/mochi.py
def get_ang(self, envs_idx=None, *, frame: Literal["world", "body"] = "world"):
    """Zero angular velocity for every env, shape ``(B, 3)``."""
    if frame != "world":
        raise ValueError(f"MochiSoftEntity only supports frame='world', got '{frame}'.")
    if self._zero_ang is None or self._zero_ang.shape[0] != self._env.num_envs:
        self._zero_ang = torch.zeros(self._env.num_envs, 3, device=self.device)
    return self._zero_ang if envs_idx is None else self._zero_ang[envs_idx]

set_pos

set_pos(pos, envs_idx=None)

Move the body with Genesis set_position semantics.

pos is a (3,) / (B, 3) offset of the rest positions, or full (n_vertices, 3) / (B, n_vertices, 3) vertex positions. Because that dual meaning is invisible at the call site, prefer the explicit :meth:set_com_offset / :meth:set_vertex_positions.

Source code in eden/entities/mochi.py
def set_pos(self, pos, envs_idx=None):
    """Move the body with Genesis ``set_position`` semantics.

    ``pos`` is a ``(3,)`` / ``(B, 3)`` offset of the rest positions, or full ``(n_vertices, 3)`` /
    ``(B, n_vertices, 3)`` vertex positions. Because that dual meaning is invisible at the call site,
    prefer the explicit :meth:`set_com_offset` / :meth:`set_vertex_positions`.
    """
    self._entity.set_position(pos, envs_idx=envs_idx)

set_com_offset

set_com_offset(offset, envs_idx=None)

Translate the body by an offset of its rest positions, shape (3,) or (B, 3).

Source code in eden/entities/mochi.py
def set_com_offset(self, offset, envs_idx=None):
    """Translate the body by an offset of its rest positions, shape ``(3,)`` or ``(B, 3)``."""
    offset = torch.as_tensor(offset)
    if offset.shape[-1] != 3 or offset.ndim > 2 or (offset.ndim == 2 and offset.shape[0] == self.n_vertices):
        raise ValueError(
            f"set_com_offset expects (3,) or (num_envs, 3); got {tuple(offset.shape)}. For absolute "
            f"per-vertex positions (vertex dim {self.n_vertices}) use set_vertex_positions()."
        )
    self._entity.set_position(offset, envs_idx=envs_idx)

set_vertex_positions

set_vertex_positions(pos, envs_idx=None)

Set absolute world vertex positions, shape (n_vertices, 3) or (B, n_vertices, 3).

Source code in eden/entities/mochi.py
def set_vertex_positions(self, pos, envs_idx=None):
    """Set absolute world vertex positions, shape ``(n_vertices, 3)`` or ``(B, n_vertices, 3)``."""
    pos = torch.as_tensor(pos)
    if pos.ndim < 2 or pos.shape[-2] != self.n_vertices or pos.shape[-1] != 3:
        raise ValueError(
            f"set_vertex_positions expects ({self.n_vertices}, 3) or (num_envs, {self.n_vertices}, 3); "
            f"got {tuple(pos.shape)}."
        )
    self._entity.set_position(pos, envs_idx=envs_idx)

set_quat

set_quat(quat, envs_idx=None)
Source code in eden/entities/mochi.py
def set_quat(self, quat, envs_idx=None):
    raise NotImplementedError(
        f"MochiSoftEntity '{self.name}' does not support set_quat. Bake the orientation into "
        f"`default_root_quat` (the morph) instead."
    )

get_mass

get_mass()

Total mass (kg) from the material density and the rest geometry.

Source code in eden/entities/mochi.py
def get_mass(self):
    """Total mass (kg) from the material density and the rest geometry."""
    return torch.as_tensor(float(self._entity.mass), dtype=gs.tc_float, device=self.device)

set_mass

set_mass(mass)
Source code in eden/entities/mochi.py
def set_mass(self, mass):
    raise NotImplementedError(
        f"MochiSoftEntity '{self.name}': mass follows the material density; set `rho` on the material."
    )

get_vertex_positions

get_vertex_positions(envs_idx=None) -> Tensor

World vertex positions, shape (B, n_vertices, 3).

Source code in eden/entities/mochi.py
def get_vertex_positions(self, envs_idx=None) -> torch.Tensor:
    """World vertex positions, shape ``(B, n_vertices, 3)``."""
    return self._with_env_axis(self._entity.get_vertices_position(envs_idx))

get_vertex_velocities

get_vertex_velocities(envs_idx=None) -> Tensor

Vertex velocities, shape (B, n_vertices, 3).

Source code in eden/entities/mochi.py
def get_vertex_velocities(self, envs_idx=None) -> torch.Tensor:
    """Vertex velocities, shape ``(B, n_vertices, 3)``."""
    return self._with_env_axis(self._entity.get_vertices_velocity(envs_idx))

get_vertex_contact_forces

get_vertex_contact_forces(envs_idx=None) -> Tensor

Net contact force on each vertex, shape (B, n_vertices, 3).

Source code in eden/entities/mochi.py
def get_vertex_contact_forces(self, envs_idx=None) -> torch.Tensor:
    """Net contact force on each vertex, shape ``(B, n_vertices, 3)``."""
    return self._with_env_axis(self._entity.get_vertices_contact_force(envs_idx))

set_vertex_velocities

set_vertex_velocities(vel, envs_idx=None) -> None

Set one velocity for every vertex ((3,) / (B, 3)) or one per vertex ((n_vertices, 3) / (B, n_vertices, 3)).

Source code in eden/entities/mochi.py
def set_vertex_velocities(self, vel, envs_idx=None) -> None:
    """Set one velocity for every vertex (``(3,)`` / ``(B, 3)``) or one per vertex (``(n_vertices, 3)`` /
    ``(B, n_vertices, 3)``).
    """
    self._entity.set_velocity(vel, envs_idx=envs_idx)

set_vertices_fixed

set_vertices_fixed(verts_idx_local, is_fixed: bool = True, envs_idx=None) -> None

Pin (or release) vertices at their current positions (a Dirichlet condition).

Source code in eden/entities/mochi.py
def set_vertices_fixed(self, verts_idx_local, is_fixed: bool = True, envs_idx=None) -> None:
    """Pin (or release) vertices at their current positions (a Dirichlet condition)."""
    self._entity.set_vertices_fixed(verts_idx_local, is_fixed=is_fixed, envs_idx=envs_idx)

set_vertices_target

set_vertices_target(verts_idx_local, pos, envs_idx=None) -> None

Drive pinned vertices to pos ((n, 3) or (B, n, 3)), reached exactly at the end of the next step.

The vertices stay fixed afterwards until :meth:set_vertices_fixed releases them.

Source code in eden/entities/mochi.py
def set_vertices_target(self, verts_idx_local, pos, envs_idx=None) -> None:
    """Drive pinned vertices to ``pos`` (``(n, 3)`` or ``(B, n, 3)``), reached exactly at the end of the next step.

    The vertices stay fixed afterwards until :meth:`set_vertices_fixed` releases them.
    """
    self._entity.set_vertices_target(verts_idx_local, pos, envs_idx=envs_idx)

get_contacts

get_contacts(with_entity: Entity | None = None, exclude_self_contact: bool = False, is_padded: bool = False)

Contact records of this body; see :meth:MochiRigidEntity.get_contacts.

Source code in eden/entities/mochi.py
def get_contacts(
    self, with_entity: Entity | None = None, exclude_self_contact: bool = False, is_padded: bool = False
):
    """Contact records of this body; see :meth:`MochiRigidEntity.get_contacts`."""
    partner = None if with_entity is None else with_entity._entity
    return self._query_contacts(
        lambda padded: self._entity.get_contacts(
            with_entity=partner, exclude_self_contact=exclude_self_contact, is_padded=padded
        ),
        is_padded,
    )

set_contact_params

set_contact_params(**params) -> None

Re-tune the material's contact parameters after build (see :meth:MochiRigidEntity.set_contact_params).

Source code in eden/entities/mochi.py
def set_contact_params(self, **params) -> None:
    """Re-tune the material's contact parameters after build (see :meth:`MochiRigidEntity.set_contact_params`)."""
    self._entity.set_contact_params(**params)

set_contact_enabled

set_contact_enabled(other: Entity, enabled: bool = True) -> None

Enable or disable contact between this entity and other (post-build, symmetric).

Source code in eden/entities/mochi.py
def set_contact_enabled(self, other: Entity, enabled: bool = True) -> None:
    """Enable or disable contact between this entity and ``other`` (post-build, symmetric)."""
    self._env.mochi_solver.enable_entity_contact(self._entity, other._entity, enabled)

attach_to

attach_to(entity: Entity, link_name: str, attach_offset=None, attach_quat=None) -> None

Mount this body on a link of a Mochi rigid entity — what SceneOptions.attachments_dict calls.

The mounting transform has the meaning it has for :meth:RigidEntity.attach_to: the body's own frame (its default_root_pos / default_root_quat frame, in which a primitive is centred) sits at (attach_offset, attach_quat) in the link frame, defaulting to default_root_pos / default_root_quat. The vertices of the face nearest the link origin — those within :data:MOUNT_FACE_TOLERANCE of the extreme along the line from the body centre to the link origin — are tied to the link by :meth:attach_to_link; the rest of the body hangs off them and deforms. A sponge mounted under a gripper is {"sponge": ("robot", "hand", (0, 0, 0.13), (1, 0, 0, 0))}. Each tie is :data:MOUNT_TIE_TO_MATERIAL times as stiff as the body's material is at one vertex. Only tetrahedral solids mount this way; a shell or a rod raises.

The solver anchors each tied vertex in the link frame of the build configuration (every joint at zero), so the rest geometry is placed there before the build, and :meth:post_build then carries the whole body to wherever the parent's default joint positions put the link.

Source code in eden/entities/mochi.py
def attach_to(
    self,
    entity: Entity,
    link_name: str,
    attach_offset=None,
    attach_quat=None,
) -> None:
    """Mount this body on a link of a Mochi rigid entity — what ``SceneOptions.attachments_dict`` calls.

    The mounting transform has the meaning it has for :meth:`RigidEntity.attach_to`: the body's own
    frame (its ``default_root_pos`` / ``default_root_quat`` frame, in which a primitive is centred) sits
    at ``(attach_offset, attach_quat)`` in the link frame, defaulting to ``default_root_pos`` /
    ``default_root_quat``. The vertices of the face nearest the link origin — those within
    :data:`MOUNT_FACE_TOLERANCE` of the extreme along the line from the body centre to the link
    origin — are tied to the link by :meth:`attach_to_link`; the rest of the body hangs off them and
    deforms. A sponge mounted under a gripper is ``{"sponge": ("robot", "hand", (0, 0, 0.13), (1, 0, 0, 0))}``.
    Each tie is :data:`MOUNT_TIE_TO_MATERIAL` times as stiff as the body's material is at one vertex. Only
    tetrahedral solids mount this way; a shell or a rod raises.

    The solver anchors each tied vertex in the link frame of the *build* configuration (every joint at
    zero), so the rest geometry is placed there before the build, and :meth:`post_build` then carries the
    whole body to wherever the parent's default joint positions put the link.
    """
    if not isinstance(self.material, en.materials.MochiElasticMaterialOptions):
        raise NotImplementedError(
            f"MochiSoftEntity '{self.name}' is a {type(self.material).__name__} body; attachments_dict mounts "
            f"tetrahedral solids (MochiElasticMaterialOptions) only. Tie a shell or rod to a link with "
            f"`attach_to_link(...)` from `env.register_pre_build_hook(...)`, choosing the vertices yourself."
        )
    if not isinstance(entity, MochiRigidEntity):
        raise TypeError(
            f"MochiSoftEntity '{self.name}' can only be mounted on a Mochi rigid entity, but its parent "
            f"'{entity.name}' is a {type(entity).__name__}: a Mochi scene holds Mochi bodies only. Give "
            f"'{entity.name}' `material=MochiRigidMaterialOptions(...)`."
        )
    mount_pos = np.asarray(self._options.default_root_pos if attach_offset is None else attach_offset)
    mount_quat = np.asarray(self._options.default_root_quat if attach_quat is None else attach_quat)
    if np.linalg.norm(mount_pos) < MOUNT_FACE_TOLERANCE:
        raise ValueError(
            f"MochiSoftEntity '{self.name}' is mounted with its centre at the origin of link '{link_name}' "
            f"(offset {tuple(mount_pos.tolist())}), so no face of it is nearest the link and none can be "
            f"tied. Offset the body to sit against the link, e.g. attach_offset=(0, 0, <half its height>)."
        )

    def _do_attach():
        gs_link = _get_link_or_raise(entity, link_name)
        # Rest geometry in the body frame, the mesh as authored (scaled) about its own origin. The morph rotated
        # it about its vertex centroid, not about default_root_pos, so undo exactly that placement.
        rest_world = np.asarray(self.init_positions, dtype=gs.np_float)
        morph_pos = np.asarray(self._entity.morph.pos, dtype=gs.np_float)
        verts_body = (self._entity.world_to_morph_frame(rest_world) - morph_pos).astype(gs.np_float)
        verts_link = gs_utils_geom.transform_by_trans_quat(
            verts_body, mount_pos.astype(gs.np_float), mount_quat.astype(gs.np_float)
        )
        tied = _mount_face(verts_link)
        if len(tied) < 3:
            raise ValueError(
                f"MochiSoftEntity '{self.name}': only {len(tied)} vertex(es) lie on the face nearest link "
                f"'{link_name}', too few to hold the body. Refine the mesh, or rotate the body so a face "
                f"(not an edge or corner) points at the link."
            )
        link_pos, link_quat = _link_build_pose(gs_link)
        self.set_rest_positions(gs_utils_geom.transform_by_trans_quat(verts_link, link_pos, link_quat))
        extent = verts_link.max(axis=0) - verts_link.min(axis=0)
        spacing = float(np.prod(extent) / len(verts_link)) ** (1.0 / 3.0)
        stiffness = MOUNT_TIE_TO_MATERIAL * self.material.E * spacing
        self._entity.attach_to_link(gs_link, tied, stiffness=stiffness, damping=MOUNT_DAMPING)
        self._mount = (entity, link_name, verts_link)

    self._env.register_pre_build_hook(_do_attach)
    self._is_attached = True
    self._attachment_parent = entity

post_build

post_build() -> None

Carry a mounted body from the build pose of its link to the link's current pose.

Source code in eden/entities/mochi.py
def post_build(self) -> None:
    """Carry a mounted body from the build pose of its link to the link's current pose."""
    if self._mount is None:
        return
    parent, link_name, verts_link = self._mount
    link = parent.get_link(link_name)
    verts = torch.as_tensor(verts_link, dtype=gs.tc_float, device=self.device)
    verts = verts.unsqueeze(0).expand(self._env.num_envs, -1, -1)
    pos = link.get_pos().unsqueeze(1).expand_as(verts)
    quat = link.get_quat().unsqueeze(1).expand(-1, verts.shape[1], -1)
    world = gs_utils_geom.transform_by_trans_quat(verts, pos, quat)
    self.set_vertex_positions(world)
    self.set_vertex_velocities(torch.zeros_like(world))
attach_to_link(entity: MochiRigidEntity, link_name: str, verts_idx, stiffness: float = 1000000.0, damping: float = 0.0) -> None

Tie vertices rigidly to a link of a Mochi rigid entity (pre-build only).

Each vertex is bound by a stiff penalty to the point of the link it coincides with at build time, inside the same implicit solve — so place the soft body at its mounted pose. Call from an env.register_pre_build_hook callback, where :attr:init_positions is available to pick verts_idx.

PARAMETER DESCRIPTION
entity

The rigid/articulated entity carrying the link.

TYPE: MochiRigidEntity

link_name

Name of the link to attach to.

TYPE: str

verts_idx

Local indices of the vertices to attach.

TYPE: array_like

stiffness

Penalty stiffness of the tie. Defaults to 1e6.

TYPE: float DEFAULT: 1000000.0

damping

Damping on the tie violation rate. Defaults to 0.0.

TYPE: float DEFAULT: 0.0

Source code in eden/entities/mochi.py
@gs.assert_unbuilt
def attach_to_link(
    self, entity: MochiRigidEntity, link_name: str, verts_idx, stiffness: float = 1e6, damping: float = 0.0
) -> None:
    """Tie vertices rigidly to a link of a Mochi rigid entity (pre-build only).

    Each vertex is bound by a stiff penalty to the point of the link it coincides with at build
    time, inside the same implicit solve — so place the soft body at its mounted pose. Call from
    an ``env.register_pre_build_hook`` callback, where :attr:`init_positions` is available to
    pick ``verts_idx``.

    Parameters
    ----------
    entity : MochiRigidEntity
        The rigid/articulated entity carrying the link.
    link_name : str
        Name of the link to attach to.
    verts_idx : array_like
        Local indices of the vertices to attach.
    stiffness : float, optional
        Penalty stiffness of the tie. Defaults to 1e6.
    damping : float, optional
        Damping on the tie violation rate. Defaults to 0.0.
    """
    self._require_added("attach_to_link")
    self._entity.attach_to_link(entity.get_link(link_name), verts_idx, stiffness=stiffness, damping=damping)

set_rest_positions

set_rest_positions(positions) -> None

Move the body's rest geometry before the scene is built (pre-build only).

The build reads :attr:init_positions to fill the solver, and :meth:attach_to_link anchors each vertex in the frame of the link it coincides with at build time — so a soft body mounted on a link whose pose is only known once the model is parsed is placed here, from a pre-build hook, rather than through the morph. positions must have the body's own vertex count and ordering.

Call :meth:set_visual_mesh before this, not after: it places the render mesh through the morph transform and binds it to the tetrahedra by barycentric coordinates, which then carry it along.

PARAMETER DESCRIPTION
positions

World rest positions, shape (n_vertices, 3).

TYPE: array_like

Source code in eden/entities/mochi.py
@gs.assert_unbuilt
def set_rest_positions(self, positions) -> None:
    """Move the body's rest geometry before the scene is built (pre-build only).

    The build reads :attr:`init_positions` to fill the solver, and :meth:`attach_to_link` anchors each
    vertex in the frame of the link it coincides with *at build time* — so a soft body mounted on a link
    whose pose is only known once the model is parsed is placed here, from a pre-build hook, rather than
    through the morph. ``positions`` must have the body's own vertex count and ordering.

    Call :meth:`set_visual_mesh` *before* this, not after: it places the render mesh through the morph
    transform and binds it to the tetrahedra by barycentric coordinates, which then carry it along.

    Parameters
    ----------
    positions : array_like
        World rest positions, shape ``(n_vertices, 3)``.
    """
    self._require_added("set_rest_positions")
    if self._has_contact_skin:
        raise RuntimeError(
            f"MochiSoftEntity '{self.name}': set_rest_positions() cannot follow set_contact_skin(). The skin "
            f"is placed through the morph transform but embedded in the tetrahedra only at build, so moved "
            f"rest positions leave it outside the body and its contact samples act from the wrong place. "
            f"Swapping the two calls does not help either: place the body through the morph "
            f"(`default_root_pos` / `default_root_quat`) to keep the skin, or drop the skin."
        )
    positions = np.asarray(positions, dtype=self._entity.init_positions.dtype)
    if positions.shape != self._entity.init_positions.shape:
        raise ValueError(
            f"MochiSoftEntity '{self.name}'.set_rest_positions expects "
            f"{self._entity.init_positions.shape}, got {positions.shape}."
        )
    self._entity.init_positions = positions
    self._rest_positions_moved = True

set_visual_mesh

set_visual_mesh(file: str, pos=(0.0, 0.0, 0.0), euler=(0.0, 0.0, 0.0), scale: float = 1.0) -> None

Skin a detailed render mesh onto the simulation tetrahedra (solids only, pre-build only).

pos / euler / scale place the render mesh in the simulation mesh's file frame; the entity's morph transform is then applied to it, and each of its vertices is bound to the tetrahedron it falls in by barycentric coordinates, so it follows the deformation. Because the placement goes through the morph, this must run before :meth:set_rest_positions — which raises here if it already ran, rather than leaving a render mesh embedded against geometry that has since moved away (the visual mesh then extrapolates far outside the body, and the body renders as nothing).

PARAMETER DESCRIPTION
file

Mesh file (resolved through :func:eden.utils.assets.get_asset_path).

TYPE: str

pos

Offset of the render mesh in the simulation mesh's file frame. Defaults to zero.

TYPE: array - like[float, float, float] DEFAULT: (0.0, 0.0, 0.0)

euler

Rotation of the render mesh in degrees. Defaults to zero.

TYPE: array - like[float, float, float] DEFAULT: (0.0, 0.0, 0.0)

scale

Uniform scale of the render mesh. Defaults to 1.0.

TYPE: float DEFAULT: 1.0

Source code in eden/entities/mochi.py
@gs.assert_unbuilt
def set_visual_mesh(self, file: str, pos=(0.0, 0.0, 0.0), euler=(0.0, 0.0, 0.0), scale: float = 1.0) -> None:
    """Skin a detailed render mesh onto the simulation tetrahedra (solids only, pre-build only).

    ``pos`` / ``euler`` / ``scale`` place the render mesh in the *simulation mesh's file frame*; the
    entity's morph transform is then applied to it, and each of its vertices is bound to the tetrahedron
    it falls in by barycentric coordinates, so it follows the deformation. Because the placement goes
    through the morph, this must run *before* :meth:`set_rest_positions` — which raises here if it
    already ran, rather than leaving a render mesh embedded against geometry that has since moved away
    (the visual mesh then extrapolates far outside the body, and the body renders as nothing).

    Parameters
    ----------
    file : str
        Mesh file (resolved through :func:`eden.utils.assets.get_asset_path`).
    pos : array-like[float, float, float], optional
        Offset of the render mesh in the simulation mesh's file frame. Defaults to zero.
    euler : array-like[float, float, float], optional
        Rotation of the render mesh in degrees. Defaults to zero.
    scale : float, optional
        Uniform scale of the render mesh. Defaults to 1.0.
    """
    self._require_added("set_visual_mesh")
    if self._rest_positions_moved:
        raise RuntimeError(
            f"MochiSoftEntity '{self.name}': set_visual_mesh() must be called before "
            f"set_rest_positions(). The render mesh is placed through the morph transform and embedded "
            f"in the tetrahedra as they stand now, so embedding it after the rest geometry has moved "
            f"binds it to the wrong place. Swap the two calls: the barycentric embedding carries the "
            f"render mesh along when the rest positions move."
        )
    self._entity.set_visual_mesh(file=get_asset_path(file), pos=pos, euler=euler, scale=scale)

set_contact_skin

set_contact_skin(file: str, pos=(0.0, 0.0, 0.0), euler=(0.0, 0.0, 0.0), scale: float = 1.0) -> None

Carry this solid's contact samples on a detailed triangle surface (solids only, pre-build only).

The samples otherwise lie on the boundary triangles of the tetrahedral mesh, so a coarse mesh contacts through a coarse surface. A skin (fingertip pads, the authored collision surface) places them where the contact happens, at the cost of more samples. The skin only carries this body's samples: other bodies still collide against its tetrahedra. Placement works as in :meth:set_visual_mesh, but the solver embeds the skin in the tetrahedra only at build, so it cannot be combined with :meth:set_rest_positions (it raises if the rest geometry has already moved).

PARAMETER DESCRIPTION
file

Mesh file (resolved through :func:eden.utils.assets.get_asset_path).

TYPE: str

pos

Offset of the skin in the simulation mesh's file frame. Defaults to zero.

TYPE: array - like[float, float, float] DEFAULT: (0.0, 0.0, 0.0)

euler

Rotation of the skin in degrees. Defaults to zero.

TYPE: array - like[float, float, float] DEFAULT: (0.0, 0.0, 0.0)

scale

Uniform scale of the skin. Defaults to 1.0.

TYPE: float DEFAULT: 1.0

Source code in eden/entities/mochi.py
@gs.assert_unbuilt
def set_contact_skin(self, file: str, pos=(0.0, 0.0, 0.0), euler=(0.0, 0.0, 0.0), scale: float = 1.0) -> None:
    """Carry this solid's contact samples on a detailed triangle surface (solids only, pre-build only).

    The samples otherwise lie on the boundary triangles of the tetrahedral mesh, so a coarse mesh contacts
    through a coarse surface. A skin (fingertip pads, the authored collision surface) places them where the
    contact happens, at the cost of more samples. The skin only carries this body's samples: other bodies
    still collide against its tetrahedra. Placement works as in :meth:`set_visual_mesh`, but the solver
    embeds the skin in the tetrahedra only at build, so it cannot be combined with
    :meth:`set_rest_positions` (it raises if the rest geometry has already moved).

    Parameters
    ----------
    file : str
        Mesh file (resolved through :func:`eden.utils.assets.get_asset_path`).
    pos : array-like[float, float, float], optional
        Offset of the skin in the simulation mesh's file frame. Defaults to zero.
    euler : array-like[float, float, float], optional
        Rotation of the skin in degrees. Defaults to zero.
    scale : float, optional
        Uniform scale of the skin. Defaults to 1.0.
    """
    self._require_added("set_contact_skin")
    if self._rest_positions_moved:
        raise RuntimeError(
            f"MochiSoftEntity '{self.name}': set_contact_skin() cannot follow set_rest_positions(). The skin "
            f"is placed through the morph transform, so it would sit where the body was before the move. "
            f"Calling it first does not help either (the skin is embedded only at build): place the body "
            f"through the morph (`default_root_pos` / `default_root_quat`) to keep the skin, or drop the skin."
        )
    self._entity.set_contact_skin(file=get_asset_path(file), pos=pos, euler=euler, scale=scale)
    self._has_contact_skin = True

Cameras

Record video with Camera.start_recording(video_path, fps) and stop_recording(), not with Genesis' own camera recorder.

Camera

Camera(env: EnvBase, options: CameraOptions, key: str = '')

Bases: ConfigurableMixin[CameraOptions]

Wrapper around a Genesis camera, configured from :class:CameraOptions.

PARAMETER DESCRIPTION
model

Camera model.

cam_pos

Camera position in world frame.

cam_lookat

Camera lookat in world frame.

cam_resolution

Camera resolution in (width, height).

cam_up

Camera up vector.

cam_aperture

Camera aperture.

cam_fov

The vertical field of view of the camera in degrees.

cam_near

Near clipping plane in meters. None leaves Genesis's default (rasterizer/batch renderer only).

cam_far

Far clipping plane in meters. None leaves Genesis's default (rasterizer/batch renderer only).

debug

Whether this is a debug camera. A debug camera will use non-batch renderer.

follow_entity_name

Name of the entity to follow.

attach_entity_name

Name of the entity to attach the camera to.

attach_link_name

Name of the link to attach the camera to.

overlays

What :meth:render_rgb paints onto every frame, in order (:mod:eden.entities.camera_overlays).

Source code in eden/entities/camera.py
def __init__(self, env: EnvBase, options: CameraOptions, key: str = ""):
    self._uid = gs.UID()
    self._options = options
    self._env = env
    #: The camera's key in ``EnvBase.cameras`` ("" when built outside an env). Not ``name``, which a configurable
    #: already has: its class's registry-style name, ``"camera"``.
    self.key = key

    # CameraOptions declares every Camera parameter, so today this only ever takes the
    # options branch -- the class-default fallback is unreachable here. It is still routed
    # through the shared helper so a parameter added to Camera/SplatCamera but not to the
    # options (``overlays`` is a mutable class default) cannot reintroduce the
    # aliasing (see mirror_options_onto).
    mirror_options_onto(self, self._options)

    # Resolve cam_intrinsic → override cam_fov / cam_resolution on self
    if self._options.cam_intrinsic is not None:
        intrinsic = self._options.cam_intrinsic
        assert intrinsic.shape == (3, 3), "`cam_intrinsic` should be a 3x3 matrix"
        en.logger.warning("`cam_intrinsic` is provided, ignoring `cam_fov` and `cam_resolution`")
        self.cam_fov = resolved_cam_fov(self._options)
        self.cam_resolution = (
            int(math.ceil(intrinsic[0, 2] * 2)),
            int(math.ceil(intrinsic[1, 2] * 2)),
        )
        en.logger.info(
            f"Using `cam_intrinsic` to set `cam_fov`: {self.cam_fov} and `cam_resolution`: {self.cam_resolution}"
        )

    # Resolve cam_extrinsic → override cam_pos / cam_lookat / cam_up on self
    if self._options.cam_extrinsic is not None:
        extrinsic = self._options.cam_extrinsic
        assert extrinsic.shape == (4, 4), "`cam_extrinsic` should be a 4x4 matrix"
        en.logger.warning("`cam_extrinsic` is provided, ignoring `cam_pos`, `cam_lookat`, and `cam_up`")
        T = np.linalg.inv(extrinsic)
        pos, lookat, up = T_to_pos_lookat_up(T)
        self.cam_pos = tuple(pos)
        self.cam_lookat = tuple(lookat)
        self.cam_up = tuple(up)

    # Validate follow vs attach
    if self._options.follow_entity_name != "":
        assert self._options.attach_entity_name == "", (
            "`attach_entity_name` should be empty if `follow_entity_name` is provided"
        )
        assert self._options.attach_link_name == "", (
            "`attach_link_name` should be empty if `follow_entity_name` is provided"
        )
        assert self._options.attach_offset is None, (
            "`attach_offset` should be None if `follow_entity_name` is provided"
        )
        assert self._options.attach_quat is None, "`attach_quat` should be None if `follow_entity_name` is provided"
    elif self._options.attach_entity_name != "":
        assert self._options.follow_entity_name == "", (
            "`follow_entity_name` should be empty if `attach_entity_name` is provided"
        )
        assert self._options.attach_link_name != "", (
            "`attach_link_name` should be provided if `attach_entity_name` is provided"
        )
        assert self._options.attach_offset is not None, (
            "`attach_offset` should be provided if `attach_entity_name` is provided"
        )
        assert self._options.attach_quat is not None, (
            "`attach_quat` should be provided if `attach_entity_name` is provided"
        )

    # Compute attach_T from offset/quat (stored on self, not options)
    if self._options.attach_entity_name != "" and self._options.attach_offset is not None:
        self.attach_T = trans_quat_to_T(
            np.array(self._options.attach_offset),
            np.array(self._options.attach_quat),
        )

    # Determine backend: sensor (add_sensor) vs legacy (add_camera)
    self._sensor_options = self._options.sensor_options
    self._cam = None  # Legacy camera object (set in pre_build)
    self._sensor = None  # Sensor object (set in pre_build)
    # Video recording is Eden-owned for both backends: one frame per render call,
    # streamed to file (see start_recording).
    self._recording: _Recording | None = None

    # Constructed now, so an unknown overlay or a misspelled parameter fails at env construction; built in a
    # post-build hook, once the managers they read exist.
    self._overlays: list[CameraOverlay] = [
        build_camera_overlay(self, overlay_options, i) for i, overlay_options in enumerate(self.overlays)
    ]
    composites = [type(overlay).__name__ for overlay in self._overlays if isinstance(overlay, BackgroundComposite)]
    if len(composites) > 1:
        raise ValueError(
            f"Camera {self.display_name}: CameraOptions.overlays declares {len(composites)} background composites "
            f"({composites}); a camera has one background. Keep one, and list in its entities every entity it "
            "should replace."
        )
    self._overlays_built = False
    self._frame_envs_idx_cache: dict[tuple[int, bool], torch.Tensor] = {}

Attributes

model class-attribute instance-attribute

model: Literal['pinhole', 'thinlens', 'fisheye'] = 'pinhole'

cam_pos class-attribute instance-attribute

cam_pos: Vec3FType = (5.0, 0.0, 1.8)

cam_lookat class-attribute instance-attribute

cam_lookat: Vec3FType = (0.0, 0.0, 0.5)

cam_resolution class-attribute instance-attribute

cam_resolution: Vec2IType = (1280, 720)

cam_up class-attribute instance-attribute

cam_up: Vec3FType = (0.0, 0.0, 1.0)

cam_aperture class-attribute instance-attribute

cam_aperture: float = 2.0

cam_fov class-attribute instance-attribute

cam_fov: float = 55.0

cam_near class-attribute instance-attribute

cam_near: float | None = None

cam_far class-attribute instance-attribute

cam_far: float | None = None

debug class-attribute instance-attribute

debug: bool = False

follow_entity_name class-attribute instance-attribute

follow_entity_name: str = ''

attach_entity_name class-attribute instance-attribute

attach_entity_name: str = ''
attach_link_name: str = ''

overlays class-attribute instance-attribute

overlays: list[CameraOverlayOptions] = []

attach_T instance-attribute

attach_T: ndarray | Tensor

key instance-attribute

key = key

supports_pointcloud property

supports_pointcloud: bool

Whether :meth:render_pointcloud is available (legacy backend only).

Lets a caller that depends on point clouds reject an incompatible camera up front instead of hitting :class:NotImplementedError on the first render.

display_name property

display_name: str

How error messages name the camera: its key in EnvBase.cameras, quoted, or its uid without one.

renderer_kind property

renderer_kind: Literal['sensor', 'raytracer', 'batch_renderer', 'rasterizer']

Which renderer makes this camera's frames. A debug camera rasterizes even in a batch-rendered scene.

batch_renderer_casts_shadows property

batch_renderer_casts_shadows: bool

Whether this camera's batch renderer has a light that casts shadows; False for any other renderer.

splat_single_view property

splat_single_view: bool | None

For a splat env's camera, whether it renders the splat once for every env's image (True) or once per env (False); None for any other camera.

segmentation_idx_dict property

segmentation_idx_dict

is_attached property

is_attached: bool

Whether this camera is attached to an entity link.

sensor property

sensor

Access the underlying Genesis sensor object. Only available with sensor backend.

Methods:

pre_build

pre_build()
Source code in eden/entities/camera.py
def pre_build(self):
    if self._uses_sensor_backend:
        self._pre_build_sensor()
    else:
        self._pre_build_legacy()

post_build

post_build()
Source code in eden/entities/camera.py
def post_build(self):
    if self._overlays:
        self._env.register_post_build_hook(self._build_overlays)
    if self._uses_sensor_backend:
        if isinstance(self._sensor_options, RasterizerCameraOptions):
            _apply_rasterizer_sensor_shadow_setting(
                self._sensor,
                shadow=self._env.env_options.rasterizer_shadows,
            )
        return

set_attach_transform

set_attach_transform(offset_T) -> None

Replace the camera-to-link attach transform declared via attach_offset/attach_quat.

Accepts a single (4, 4) transform or a batched (n_rendered, 4, 4) stack for per-env camera mounts (camera-extrinsics domain randomization): Genesis composes the attached pose as link_T @ offset_T, which broadcasts over a batched offset. A batched offset requires a batched camera (batch renderer or env_separate_rigid) and one transform per rendered env — anything else would only crash later, deep inside the next render, so it is rejected here.

PARAMETER DESCRIPTION
offset_T

The new camera pose(s) relative to the attached link, shape (4, 4) or (n_rendered, 4, 4).

TYPE: Tensor | ndarray

Source code in eden/entities/camera.py
def set_attach_transform(self, offset_T) -> None:
    """Replace the camera-to-link attach transform declared via ``attach_offset``/``attach_quat``.

    Accepts a single ``(4, 4)`` transform or a batched ``(n_rendered, 4, 4)`` stack for
    per-env camera mounts (camera-extrinsics domain randomization): Genesis composes the
    attached pose as ``link_T @ offset_T``, which broadcasts over a batched offset. A
    batched offset requires a batched camera (batch renderer or ``env_separate_rigid``)
    and one transform per rendered env — anything else would only crash later, deep
    inside the next render, so it is rejected here.

    Parameters
    ----------
    offset_T : torch.Tensor | np.ndarray
        The new camera pose(s) relative to the attached link, shape ``(4, 4)`` or
        ``(n_rendered, 4, 4)``.
    """
    if self._uses_sensor_backend:
        raise NotImplementedError(
            "set_attach_transform() is not supported with the sensor backend. "
            "Use the legacy camera backend (remove sensor_options from CameraOptions)."
        )
    if self.attach_entity_name == "":
        raise RuntimeError(
            "set_attach_transform() requires an attached camera — declare "
            "attach_entity_name/attach_link_name (+ attach_offset/attach_quat) in CameraOptions."
        )
    shape = tuple(offset_T.shape)
    if shape[-2:] != (4, 4) or len(shape) not in (2, 3):
        raise ValueError(f"offset_T must be (4, 4) or (n_rendered, 4, 4), got {shape}.")
    if len(shape) == 3:
        # Genesis composes batched poses only for a batched camera, over exactly the
        # rendered envs; a mismatch here surfaces as an opaque matmul/set_pose error
        # inside the next render (and a stray (1, 4, 4) would silently broadcast).
        if not self._cam._is_batched:
            raise ValueError(
                f"offset_T is batched ({shape}) but camera {self.display_name} is not batched — "
                "batched mounts need the batch renderer or env_separate_rigid."
            )
        n_rendered = len(self._env.scene.visualizer._context.rendered_envs_idx)
        if shape[0] != n_rendered:
            raise ValueError(
                f"offset_T has batch size {shape[0]} but the camera renders {n_rendered} envs "
                "(rendered_envs_idx) — provide one transform per rendered env."
            )
    rigid_link = self._env.entities[self.attach_entity_name].get_link(self.attach_link_name)
    self._cam.attach(rigid_link, offset_T)
    self.attach_T = offset_T

get_pos

get_pos(envs_idx=None)

Return the current position of the camera.

Source code in eden/entities/camera.py
def get_pos(self, envs_idx=None):
    """Return the current position of the camera."""
    if self._uses_sensor_backend:
        raise NotImplementedError(
            "get_pos() is not supported with the sensor backend. Use sensor.read() to get rendered data."
        )
    return self._cam.get_pos(envs_idx=envs_idx)

get_quat

get_quat(envs_idx=None)

Return the current quaternion of the camera.

Source code in eden/entities/camera.py
def get_quat(self, envs_idx=None):
    """Return the current quaternion of the camera."""
    if self._uses_sensor_backend:
        raise NotImplementedError(
            "get_quat() is not supported with the sensor backend. Use sensor.read() to get rendered data."
        )
    return self._cam.get_quat(envs_idx=envs_idx)

get_lookat

get_lookat(envs_idx=None)

Return the current lookat point of the camera.

Source code in eden/entities/camera.py
def get_lookat(self, envs_idx=None):
    """Return the current lookat point of the camera."""
    if self._uses_sensor_backend:
        raise NotImplementedError("get_lookat() is not supported with the sensor backend.")
    return self._cam.get_lookat(envs_idx=envs_idx)

get_up

get_up(envs_idx=None)

Return the current up vector of the camera.

Source code in eden/entities/camera.py
def get_up(self, envs_idx=None):
    """Return the current up vector of the camera."""
    if self._uses_sensor_backend:
        raise NotImplementedError("get_up() is not supported with the sensor backend.")
    return self._cam.get_up(envs_idx=envs_idx)

get_transform

get_transform(envs_idx=None)

Return the current transform matrix of the camera.

Source code in eden/entities/camera.py
def get_transform(self, envs_idx=None):
    """Return the current transform matrix of the camera."""
    if self._uses_sensor_backend:
        raise NotImplementedError("get_transform() is not supported with the sensor backend.")
    return self._cam.get_transform(envs_idx=envs_idx)

set_pose

set_pose(transform=None, pos=None, lookat=None, up=None, envs_idx=None)

Set the camera pose.

PARAMETER DESCRIPTION
transform

The transform matrix of the camera.

TYPE: (ndarray, shape(4, 4) or (N, 4, 4)) DEFAULT: None

pos

The position of the camera.

TYPE: (array - like, shape(3) or (N, 3)) DEFAULT: None

lookat

The lookat point of the camera.

TYPE: (array - like, shape(3) or (N, 3)) DEFAULT: None

up

The up vector of the camera.

TYPE: (array - like, shape(3) or (N, 3)) DEFAULT: None

envs_idx

The environment indices for which to update the pose. If not provided, the camera pose will be set for the specific environment bound to the camera if any, all the environments otherwise.

TYPE: array of indices in integers DEFAULT: None

Source code in eden/entities/camera.py
def set_pose(self, transform=None, pos=None, lookat=None, up=None, envs_idx=None):
    """Set the camera pose.

    Parameters
    ----------
    transform : np.ndarray, shape (4, 4) or (N, 4, 4), optional
        The transform matrix of the camera.
    pos : array-like, shape (3,) or (N, 3), optional
        The position of the camera.
    lookat : array-like, shape (3,) or (N, 3), optional
        The lookat point of the camera.
    up : array-like, shape (3,) or (N, 3), optional
        The up vector of the camera.
    envs_idx : array of indices in integers, optional
        The environment indices for which to update the pose. If not provided, the camera pose will be set for the
        specific environment bound to the camera if any, all the environments otherwise.
    """
    if self._uses_sensor_backend:
        raise NotImplementedError("set_pose() is not supported with the sensor backend.")
    if self.is_attached:
        raise RuntimeError(
            "Cannot set pose on an attached camera. Detach it first or modify the attachment offset."
        )
    self._cam.set_pose(transform=transform, pos=pos, lookat=lookat, up=up, envs_idx=envs_idx)

set_pos

set_pos(pos, envs_idx=None)

Set the camera position, keeping current lookat and up.

PARAMETER DESCRIPTION
pos

The position of the camera.

TYPE: (array - like, shape(3) or (N, 3))

envs_idx

Environment indices. If None, applies to all environments.

TYPE: array - like DEFAULT: None

Source code in eden/entities/camera.py
def set_pos(self, pos, envs_idx=None):
    """Set the camera position, keeping current lookat and up.

    Parameters
    ----------
    pos : array-like, shape (3,) or (N, 3)
        The position of the camera.
    envs_idx : array-like, optional
        Environment indices. If None, applies to all environments.
    """
    if self._uses_sensor_backend:
        raise NotImplementedError("set_pos() is not supported with the sensor backend.")
    if self.is_attached:
        raise RuntimeError(
            "Cannot set position on an attached camera. Detach it first or modify the attachment offset."
        )
    self._cam.set_pose(pos=pos, envs_idx=envs_idx)

set_quat

set_quat(quat, envs_idx=None)

Set the camera orientation from a quaternion (wxyz), keeping current position.

PARAMETER DESCRIPTION
quat

Quaternion in (w, x, y, z) format.

TYPE: (array - like, shape(4) or (N, 4))

envs_idx

Environment indices. If None, applies to all environments.

TYPE: array - like DEFAULT: None

Source code in eden/entities/camera.py
def set_quat(self, quat, envs_idx=None):
    """Set the camera orientation from a quaternion (wxyz), keeping current position.

    Parameters
    ----------
    quat : array-like, shape (4,) or (N, 4)
        Quaternion in (w, x, y, z) format.
    envs_idx : array-like, optional
        Environment indices. If None, applies to all environments.
    """
    if self._uses_sensor_backend:
        raise NotImplementedError("set_quat() is not supported with the sensor backend.")
    if self.is_attached:
        raise RuntimeError(
            "Cannot set orientation on an attached camera. Detach it first or modify the attachment offset."
        )
    # Genesis keeps the camera pose on the host in float64 numpy
    # (https://github.com/Genesis-Embodied-AI/Genesis/pull/3571), so the quaternion and the env index join it there.
    quat = tensor_to_array(quat, dtype=np.float64)
    if envs_idx is None:
        idx = ()
    else:
        # Raises on a malformed or out-of-range index, naming it; any accepted form becomes an index tensor, a mask
        # stays a mask.
        envs_idx = sanitize_envs_idx(envs_idx, self._env.num_envs, prefer_slice=False)
        # A device index costs one sync whatever is done here, since Genesis' set_pose brings envs_idx to the host
        # too: this copy waits for the queued work, and the one in set_pose finds none left.
        idx = tensor_to_array(envs_idx)
    # Use internal _pos (without envs_offset) to match set_pose's coordinate space
    pos = self._cam._pos[idx]
    T = trans_quat_to_T(pos, quat)
    self._cam.set_pose(transform=T, envs_idx=envs_idx)

start_recording

start_recording(video_path: str | None = None, fps: float = 60.0)

Open a video file and record one frame per render() / render_rgb() call.

Frames are encoded and streamed to the file as they are rendered, so the recording length is not bounded by memory. The output path and framerate are fixed for the whole video, hence given here rather than at stop_recording().

One frame per render call is Eden's recording contract: a kinematic replay that never steps the scene records exactly as a stepped rollout does. Genesis' own camera.start_recording instead captures inside scene.step(), which would record nothing for a replay script.

A camera that renders several environments at once (batch renderer, or env_separate_rigid) records one video per environment, named {stem}_{i}{ext} after the environment's position in the rendered batch.

PARAMETER DESCRIPTION
video_path

Output video file path, ending in '.mp4'. Defaults to {log_dir}/video_{timestamp}.mp4.

TYPE: str DEFAULT: None

fps

Framerate the video is encoded at. Must be positive. Playback speed is fps render calls per second, whatever the simulation timestep.

TYPE: float DEFAULT: 60.0

Source code in eden/entities/camera.py
def start_recording(self, video_path: str | None = None, fps: float = 60.0):
    """Open a video file and record one frame per ``render()`` / ``render_rgb()`` call.

    Frames are encoded and streamed to the file as they are rendered, so the
    recording length is not bounded by memory. The output path and framerate are
    fixed for the whole video, hence given here rather than at ``stop_recording()``.

    One frame per render call is Eden's recording contract: a kinematic replay that
    never steps the scene records exactly as a stepped rollout does. Genesis' own
    ``camera.start_recording`` instead captures inside ``scene.step()``, which would
    record nothing for a replay script.

    A camera that renders several environments at once (batch renderer, or
    ``env_separate_rigid``) records one video per environment, named
    ``{stem}_{i}{ext}`` after the environment's position in the rendered batch.

    Parameters
    ----------
    video_path : str, optional
        Output video file path, ending in '.mp4'. Defaults to
        ``{log_dir}/video_{timestamp}.mp4``.
    fps : float, optional
        Framerate the video is encoded at. Must be positive. Playback speed is
        ``fps`` render calls per second, whatever the simulation timestep.
    """
    if self._recording is not None:
        raise RuntimeError(f"Already recording to '{self._recording.path}'. Call stop_recording() first.")
    # Checked here rather than left to the encoder: a non-positive framerate otherwise
    # surfaces as a PyAV error on the first frame, deep inside the render loop.
    if fps <= 0.0:
        raise ValueError(f"Framerate of a recording must be positive; got fps={fps}.")

    self._recording = _Recording(path=video_path or f"{en.log_dir}/video_{get_now()}.mp4", fps=fps)

stop_recording

stop_recording()

Finalize the video file(s) named by :meth:start_recording.

Frames are encoded as they are recorded, so this only flushes the ones the encoders still hold and closes the files.

A recording that captured no frame writes no file at all. That is said out loud rather than passed over: it means nothing rendered while the recording was open, which is a mistake in the calling loop far more often than an intent.

Source code in eden/entities/camera.py
def stop_recording(self):
    """Finalize the video file(s) named by :meth:`start_recording`.

    Frames are encoded as they are recorded, so this only flushes the ones the
    encoders still hold and closes the files.

    A recording that captured no frame writes no file at all. That is said out loud
    rather than passed over: it means nothing rendered while the recording was open,
    which is a mistake in the calling loop far more often than an intent.
    """
    if self._recording is None:
        raise RuntimeError(
            "Recording not started. Call start_recording(video_path, fps) first — the output path and "
            "framerate belong to start_recording(), not stop_recording()."
        )

    recording, self._recording = self._recording, None

    if not recording.encoders:
        en.logger.warning(
            f"Recording to '{recording.path}' captured no frame, so no video was written. Frames are "
            "captured by render() / render_rgb() — a recording that never renders produces nothing."
        )
        return

    # Every file is finalized before any failure is raised, so one environment failing to
    # encode leaves neither the others open nor the camera unable to record. The first
    # failure is the one raised: a later one is as likely to be its consequence.
    error = None
    for encoder in recording.encoders:
        try:
            encoder.close()
        except gs.GenesisException as e:
            error = error or e
    if error is not None:
        raise error

destroy

destroy() -> None

Finalize a recording still running, as part of tearing the camera down.

This is what makes stopping a recording explicitly optional: a camera going away leaves a playable video behind rather than a file the encoder never flushed.

Source code in eden/entities/camera.py
def destroy(self) -> None:
    """Finalize a recording still running, as part of tearing the camera down.

    This is what makes stopping a recording explicitly optional: a camera going away
    leaves a playable video behind rather than a file the encoder never flushed.
    """
    if self._recording is None:
        return
    try:
        self.stop_recording()
    except gs.GenesisException as e:
        # Teardown carries on regardless, so a video failing to encode takes neither
        # the other cameras nor the scene down with it. The error was already reported
        # by the encoder when it happened.
        en.logger.warning(f"{e}")

read

read(envs_idx=None)

Read sensor data. Only available with the sensor backend.

RETURNS DESCRIPTION
data

Named tuple with rgb field containing the rendered image.

TYPE: CameraData

Source code in eden/entities/camera.py
def read(self, envs_idx=None):
    """Read sensor data. Only available with the sensor backend.

    Returns
    -------
    data : CameraData
        Named tuple with `rgb` field containing the rendered image.
    """
    if not self._uses_sensor_backend:
        raise RuntimeError(
            "read() is only available with the sensor backend. "
            "Use render_rgb()/render_depth()/etc. for legacy cameras."
        )
    return self._sensor.read(envs_idx=envs_idx)

pick_pixel

pick_pixel(camera_index, x, y)

Cast a ray through pixel (x, y) and return what it hits.

Sensor backend only (e.g. Nyx). Delegates to the underlying sensor's pick_pixel and returns its result — the hit entity / link / world position — or None for background (sky).

Source code in eden/entities/camera.py
def pick_pixel(self, camera_index, x, y):
    """Cast a ray through pixel ``(x, y)`` and return what it hits.

    Sensor backend only (e.g. Nyx). Delegates to the underlying sensor's
    ``pick_pixel`` and returns its result — the hit entity / link / world
    position — or ``None`` for background (sky).
    """
    if not self._uses_sensor_backend:
        raise NotImplementedError("pick_pixel() is only available with the sensor backend (e.g. Nyx).")
    return self._sensor.pick_pixel(camera_index, x, y)

render

render(rgb: bool = True, depth: bool = False, segmentation: bool = False, colorize_seg: bool = False, normal: bool = False)
Source code in eden/entities/camera.py
def render(
    self,
    rgb: bool = True,
    depth: bool = False,
    segmentation: bool = False,
    colorize_seg: bool = False,
    normal: bool = False,
):
    rgb_out, depth_out, segmentation_out, normal_out = self._render(
        rgb=rgb,
        depth=depth,
        segmentation=segmentation,
        colorize_seg=colorize_seg,
        normal=normal,
    )
    if rgb:
        self._record_frame(rgb_out)
    return rgb_out, depth_out, segmentation_out, normal_out

render_unshadowed_rgb

render_unshadowed_rgb() -> Tensor | None

This view rendered again with the scene's shadows off, (N, H, W, 3) on the env's device, which lets :func:~eden.utils.background_shadows.background_shadow_factor compare every pixel with its own unshadowed brightness; None when the renderer can't (the batch renderer bakes shadows in at build time, and the factor then estimates the unshadowed brightness instead).

Source code in eden/entities/camera.py
def render_unshadowed_rgb(self) -> torch.Tensor | None:
    """This view rendered again with the scene's shadows off, ``(N, H, W, 3)`` on the env's device, which lets
    :func:`~eden.utils.background_shadows.background_shadow_factor` compare every pixel with its own unshadowed
    brightness; ``None`` when the renderer can't (the batch renderer bakes shadows in at build time, and the
    factor then estimates the unshadowed brightness instead).
    """
    if self.renderer_kind != "rasterizer":
        return None
    with _rasterizer_shadows_off(self._env.scene.visualizer):
        rgb = numpy_to_tensor(self._cam.render(rgb=True)[0], device=self._env.device)
    return (rgb if rgb.ndim == 4 else rgb[None])[..., :3]

render_rgb

render_rgb()

The camera's RGB frame, with its background composite and overlays painted on, recorded if recording.

Source code in eden/entities/camera.py
def render_rgb(self):
    """The camera's RGB frame, with its background composite and overlays painted on, recorded if recording."""
    if self._uses_sensor_backend:
        rgb_out = self._sensor.read().rgb
        # A sensor read is a view into a cache the next step() repopulates, so the
        # recording needs its own copy of it (see _record_frame). The view itself is
        # what is returned, as callers of a sensor read expect.
        return self._finish_frame(rgb_out, may_alias=True)

    # The segmentation comes from the same render as the RGB, so an overlay that reads it costs no extra pass.
    segmentation = self._paints and any(overlay._needs_segmentation for overlay in self._overlays)
    rgb_out, _, segm, _ = self._render(rgb=True, segmentation=segmentation)
    return self._finish_frame(rgb_out, segmentation=segm)

render_depth

render_depth()
Source code in eden/entities/camera.py
def render_depth(self):
    if self._uses_sensor_backend:
        raise NotImplementedError(
            "Depth rendering is not supported with the sensor backend. "
            "Use the legacy camera backend (remove sensor_options from CameraOptions)."
        )
    _, depth_out, _, _ = self.render(rgb=False, depth=True, segmentation=False, colorize_seg=False, normal=False)
    return depth_out

render_pointcloud

render_pointcloud(world_frame=True)

Render a point cloud from the camera's current view.

PARAMETER DESCRIPTION
world_frame

True returns the points in the world frame, False in the camera frame. Defaults to True.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
pc

The point of each pixel, shape (H, W, 3) or (N, H, W, 3).

TYPE: ndarray

mask_arr

Boolean mask of the pixels with a valid depth, shape (H, W) or (N, H, W).

TYPE: ndarray

Source code in eden/entities/camera.py
def render_pointcloud(self, world_frame=True):
    """Render a point cloud from the camera's current view.

    Parameters
    ----------
    world_frame : bool, optional
        True returns the points in the world frame, False in the camera frame. Defaults to True.

    Returns
    -------
    pc : np.ndarray
        The point of each pixel, shape (H, W, 3) or (N, H, W, 3).
    mask_arr : np.ndarray
        Boolean mask of the pixels with a valid depth, shape (H, W) or (N, H, W).
    """
    if self._uses_sensor_backend:
        raise NotImplementedError(
            "Pointcloud rendering is not supported with the sensor backend. "
            "Use the legacy camera backend (remove sensor_options from CameraOptions)."
        )
    pc, valid_mask = self._cam.render_pointcloud(world_frame=world_frame)
    return pc, valid_mask

render_segm

render_segm()
Source code in eden/entities/camera.py
def render_segm(self):
    if self._uses_sensor_backend:
        raise NotImplementedError(
            "Segmentation rendering is not supported with the sensor backend. "
            "Use the legacy camera backend (remove sensor_options from CameraOptions)."
        )
    _, _, segmentation_out, _ = self.render(
        rgb=False, depth=False, segmentation=True, colorize_seg=False, normal=False
    )
    return segmentation_out

render_normal

render_normal()
Source code in eden/entities/camera.py
def render_normal(self):
    if self._uses_sensor_backend:
        raise NotImplementedError(
            "Normal rendering is not supported with the sensor backend. "
            "Use the legacy camera backend (remove sensor_options from CameraOptions)."
        )
    _, _, _, normal_out = self.render(rgb=False, depth=False, segmentation=False, colorize_seg=False, normal=True)
    return normal_out

snapshot

snapshot(image_path: str | None = None)
Source code in eden/entities/camera.py
def snapshot(self, image_path: str | None = None):
    image_path = image_path or f"{en.log_dir}/snapshot_{get_now()}.png"
    rgb = self.render_rgb()
    if rgb.ndim == 4:
        rgb = rgb[0]
    rgb = tensor_to_array(rgb)
    gs.tools.save_img_arr(rgb, image_path)

SplatCamera

SplatCamera(key: str, env, options: CameraOptions)

Bases: _SplatCameraBase

Renders the splat once from an env-invariant pose and uses it for every env's image.

The cheap path for a fixed camera over a shared (per_env_gaussians=False) background: the splat looks identical from every env, so rendering it N times is waste. Only the first env's pose is rendered. The env picks this class automatically for non-attached cameras — see :meth:~eden.envs.splat.SplatEnvBase._splat_camera_class.

Source code in eden/entities/camera.py
def __init__(self, key: str, env, options: CameraOptions):
    super().__init__(env, options, key=key)

Attributes

splat_single_view property

splat_single_view: bool

BatchSplatCamera

BatchSplatCamera(key: str, env, options: CameraOptions)

Bases: _SplatCameraBase

Renders the splat per env — every environment's camera in one gsplat call.

Needed when the splat differs across envs: an attached/following camera (per-env pose) or per_env_gaussians (per-env geometry). The env picks this class over the single-view :class:SplatCamera — see :meth:~eden.envs.splat.SplatEnvBase._splat_camera_class.

Source code in eden/entities/camera.py
def __init__(self, key: str, env, options: CameraOptions):
    super().__init__(env, options, key=key)

Attributes

splat_single_view property

splat_single_view: bool

Camera overlays

Overlays paint state the scene does not render (ink, marks) or composite a background onto every RGB frame a camera produces. Declare them in CameraOptions.overlays.

Camera overlays: paint what the simulation tracks but does not render onto every RGB frame a camera produces.

Some state a task keeps is visible in the real world but has no geometry in the scene: the ink a marker lays on a whiteboard, the strokes a brush leaves on paper. The task tracks it as a tensor, so a plain camera shows a clean surface. An overlay draws that tensor onto the camera's frames after the renderer has made them.

A camera declares its overlays in :attr:CameraOptions.overlays <eden.options.camera.CameraOptions>, by registry key, so they survive a saved config.json. They are applied in :meth:Camera.render_rgb <eden.entities.camera.Camera.render_rgb> and so reach everything built on it: the returned frame, the camera's recording, :meth:~eden.entities.camera.Camera.snapshot, and an observation that reads the camera. :meth:~eden.entities.camera.Camera.render stays the raw multi-output pass.

A background composite is an overlay too: :class:BackgroundImageComposite replaces a camera's background with an image, :class:SplatComposite with a Gaussian splat (a splat env's cameras). Both replace the pixels where the sim shows one of their entities or nothing at all, and report them through :meth:CameraOverlay.replaced_pixels; the overlays after them leave those pixels as composited, since they no longer show the sim surface an overlay locates its marks on. A camera takes at most one; declare it first, so the overlays after it paint over the composited frame. A splat env's camera shows its splat only when it declares a SplatComposite, as a splat config's :meth:~eden.utils.configs.EdenSplatConfig.recording_camera does.

Writing one: subclass :class:CameraOverlay, declare its parameters as class attributes, register it with @CAMERA_OVERLAY_REGISTRY.register(), and implement :meth:CameraOverlay.paint. Set _needs_segmentation = True to receive the frame's segmentation, which the sim pass renders with the RGB at no extra render. Set _needs_pointcloud = True to receive the frame's world-frame point cloud; the camera renders it once per frame for all its overlays, which costs a second render of the view (its depth). A camera that can render neither (the sensor backend) refuses such an overlay at env construction. Resolve whatever the overlay reads (a metric term, an entity) in :meth:CameraOverlay.build, which runs after the managers are built. Frames rendered before then (the observation manager renders once while it loads, to size its terms) are not painted.

:class:PlanePaintOverlay is the general one: it paints the :class:PaintedPlane a metric term returns (any :class:PaintedPlaneSource), a weight grid on a plane, where the camera sees that plane. It shades the marks as the surface they lie on is shaded, so they keep the shadows and the light the renderer gave that surface.

Attributes

CAMERA_OVERLAY_REGISTRY module-attribute

CAMERA_OVERLAY_REGISTRY = Registry('CAMERA_OVERLAY')

Classes

CameraOverlay

CameraOverlay(camera: Camera, options: CameraOverlayOptions)

Bases: ConfigurableMixin[CameraOverlayOptions]

Base of the camera overlays: paints onto the RGB frames of the camera it is declared on.

Constructed with the camera, at env construction; :meth:build runs once the env's managers are built.

Source code in eden/entities/camera_overlays.py
def __init__(self, camera: Camera, options: CameraOverlayOptions) -> None:
    super().__init__(options)
    # CameraOverlayOptions converts its extras already, unless they were read before this module was imported.
    params = {name: getattr(self, name) for name in self.get_parameter_names()}
    params = validated_parameter_values(type(self), params, subject=type(self).__name__)
    for name, value in params.items():
        setattr(self, name, value)
    type(self).check_parameters(params)
    self._camera = camera
    self._env = camera._env

Methods:

check_parameters classmethod
check_parameters(params: Mapping[str, Any]) -> None

Refuse parameter values the overlay cannot work with: ranges and rules across parameters. params holds every parameter, defaults filled in and already of its declared type.

Runs when its :class:~eden.options.camera.CameraOverlayOptions are constructed, so a bad value fails in the config that wrote it, and again when the overlay is constructed. Checks that need the scene belong in :meth:build.

Source code in eden/entities/camera_overlays.py
@classmethod
def check_parameters(cls, params: Mapping[str, Any]) -> None:
    """Refuse parameter values the overlay cannot work with: ranges and rules across parameters. ``params`` holds
    every parameter, defaults filled in and already of its declared type.

    Runs when its :class:`~eden.options.camera.CameraOverlayOptions` are constructed, so a bad value fails in the
    config that wrote it, and again when the overlay is constructed. Checks that need the scene belong in
    :meth:`build`.
    """
build
build() -> None

Resolve and check what the overlay reads; runs after the env's managers are built.

Source code in eden/entities/camera_overlays.py
def build(self) -> None:
    """Resolve and check what the overlay reads; runs after the env's managers are built."""
paint
paint(rgb: Tensor, buffers: OverlayBuffers, envs_idx: Tensor) -> Tensor

Return rgb with the overlay painted on.

PARAMETER DESCRIPTION
rgb

(N, H, W, 3) on the env's device, one image per rendered env, in the renderer's dtype (uint8 for the rasterizer and the batch renderer). Return the same shape and dtype; never write into rgb, which may be the renderer's own buffer (returning it as is, when nothing is painted, is fine).

TYPE: Tensor

buffers

The extra renders of the same frame the overlay asked for.

TYPE: OverlayBuffers

envs_idx

(N,) the env each image shows, on the env's device.

TYPE: Tensor

Source code in eden/entities/camera_overlays.py
def paint(self, rgb: torch.Tensor, buffers: OverlayBuffers, envs_idx: torch.Tensor) -> torch.Tensor:
    """Return ``rgb`` with the overlay painted on.

    Parameters
    ----------
    rgb : torch.Tensor
        ``(N, H, W, 3)`` on the env's device, one image per rendered env, in the renderer's dtype (``uint8`` for
        the rasterizer and the batch renderer). Return the same shape and dtype; never write into ``rgb``, which may
        be the renderer's own buffer (returning it as is, when nothing is painted, is fine).
    buffers : OverlayBuffers
        The extra renders of the same frame the overlay asked for.
    envs_idx : torch.Tensor
        ``(N,)`` the env each image shows, on the env's device.
    """
    raise NotImplementedError
replaced_pixels
replaced_pixels(buffers: OverlayBuffers) -> Tensor | None

(N, H, W) the pixels :meth:paint replaced with something other than the simulated scene (a background image), which the overlays after this one leave as they are; None (the default) when it only paints over the scene.

Source code in eden/entities/camera_overlays.py
def replaced_pixels(self, buffers: OverlayBuffers) -> torch.Tensor | None:
    """``(N, H, W)`` the pixels :meth:`paint` replaced with something other than the simulated scene (a
    background image), which the overlays after this one leave as they are; ``None`` (the default) when it only
    paints over the scene.
    """
    return None

OverlayBuffers dataclass

OverlayBuffers(segmentation: Tensor | None = None, pointcloud: Tensor | None = None, pointcloud_valid: Tensor | None = None)

What the camera rendered for its overlays alongside one RGB frame, one entry per image (N, ...).

segmentation (N, H, W) is each pixel's segmentation id, None unless an overlay of the camera sets _needs_segmentation. pointcloud is each pixel's world position (N, H, W, 3) and pointcloud_valid (N, H, W) says which pixels hit something; both are None unless an overlay sets _needs_pointcloud.

Attributes

segmentation class-attribute instance-attribute
segmentation: Tensor | None = None
pointcloud class-attribute instance-attribute
pointcloud: Tensor | None = None
pointcloud_valid class-attribute instance-attribute
pointcloud_valid: Tensor | None = None

Methods:

PlanePaintOverlay

PlanePaintOverlay(camera: Camera, options: CameraOverlayOptions)

Bases: CameraOverlay

Paints a metric's marks, in color or in their own colours, where the camera sees the plane they lie on.

The metric term metric_name is a :class:PaintedPlaneSource: its painted_plane(envs_idx) is read at every frame for the envs the frame shows only, so a camera over one env never computes the marks of the other thousand. A pixel is on the plane when its world point (from the frame's point cloud, so whatever stands in front hides the marks) lies within max_gap of it. Each pixel is looked up on a supersample x supersample grid of sub-pixels and averaged, since a mark a few millimetres wide can be thinner than a pixel; only the pixels round the grid's image are, so the sub-pixel buffers scale with the marks' footprint in the frame, not with the frame.

A curved surface qualifies when, over the grid, it stays within max_gap of the plane touching it, and its distances along the surface differ from the plane's by well under a cell: the marks are then painted where they lie. A narrow stripe round a tube's crown does (mochi_wipe_the_bar); a mark wrapping round it would not.

The marks are shaded as their surface is. A mark is a change of colour on the surface the metric names (:attr:PaintedPlane.entity_name), so it takes the light that surface's pixel took: the shadow the brush casts over a stroke, the falloff across the sheet, the light's tint. The renderer draws a matte surface as its colour times the light reaching it, so a marked pixel is the rendered pixel times color / surface colour, blended by how much of the pixel is marked: rendered * lerp(1, color / surface colour, weight). The surface colour is read from the built entity, which therefore has to be one plain colour (an image texture is refused at build), and no channel of it may be under :data:MIN_SURFACE_CHANNEL, below which the division amplifies the frame's 8-bit steps into visible banding. A channel the scaling lifts past 255 is clipped.

The multiplication is in each renderer's own colour space, as measured: on the rasterizer the pixel itself is the colour times the light, while the batch renderer multiplies in linear light and encodes its pixels as sRGB, so there the tint applies to the decoded pixel. Other renderers (the ray tracer, whose colour response has not been measured) are refused at build. A surface that cannot be shaded against is refused too, with no flat-colour fallback: an image-textured or multi-coloured surface (paint marks onto a plain-coloured entity laid over it).

The one thing the ratio cannot carry is light a surface reflects on top of its colour: a highlight, or the white sheen the rasterizer's default plastic surface adds in direct light (about 20 of 255 under the default lights, whatever the colour). It is scaled with the mark's colour, so in direct light a dark mark renders without it, up to about that much darker in its dark channels than a surface of the mark's colour would. In shadow, where there is no sheen, the marks match such a surface to within a level or two.

Marks of one colour (an ink's) are painted in color, the camera's to choose, not the metric's: grading never reads it. A task's :meth:~eden.utils.configs.EdenConfig.recording_camera fills it from wherever the task keeps it (the whiteboard marker's ink). Marks of many colours (a painting's) come with their colours (:attr:PaintedPlane.rgb), each shaded as color is, and color is then left out.

PARAMETER DESCRIPTION
metric_name

The metric term whose painted_plane() is painted.

TYPE: str

color

RGB in [0, 1] of the marks, as the surface's colour is given: a mark at full weight renders as the surface would in this colour. Required for marks of one colour (no colour is right for every mark); left out for marks that come with their own (:attr:PaintedPlane.rgb).

TYPE: tuple[float, float, float] | None

max_gap

How far (m) a pixel's world point may lie off the plane and still be painted. Covers the depth buffer's error at the camera's distance, and a curved surface's sag from its plane.

TYPE: float

supersample

Sub-pixels per side each pixel is looked up on.

TYPE: int

Source code in eden/entities/camera_overlays.py
def __init__(self, camera: Camera, options: CameraOverlayOptions) -> None:
    super().__init__(camera, options)
    self._rgb = None if self.color is None else _unit_rgb(self.color)

Attributes

metric_name class-attribute instance-attribute
metric_name: str = ''
color class-attribute instance-attribute
color: tuple[float, float, float] | None = None
max_gap class-attribute instance-attribute
max_gap: float = 0.002
supersample class-attribute instance-attribute
supersample: int = 4

Methods:

check_parameters classmethod
check_parameters(params: Mapping[str, Any]) -> None
Source code in eden/entities/camera_overlays.py
@classmethod
def check_parameters(cls, params: Mapping[str, Any]) -> None:
    if params["max_gap"] <= 0.0 or params["supersample"] < 1:
        raise ValueError(
            f"PlanePaintOverlay needs max_gap > 0 and supersample >= 1; got max_gap={params['max_gap']}, "
            f"supersample={params['supersample']}."
        )
    # No colour is for a metric that gives its marks' own (checked at build, against the metric).
    if params["color"] is not None and _unit_rgb(params["color"]) is None:
        raise ValueError(
            f"PlanePaintOverlay(metric_name={params['metric_name']!r}) got color={params['color']!r}; it needs the "
            "marks' colour as (r, g, b) with each channel a number in [0, 1], e.g. color=(0.02, 0.03, 0.10), not "
            "in [0, 255]. A task's recording_camera() passes the colour it keeps (the whiteboard marker's ink)."
        )
build
build() -> None
Source code in eden/entities/camera_overlays.py
def build(self) -> None:
    # Deferred: the managers import the entities, so a module-scope import here would be a cycle.
    from eden.managers.terms.utils import resolve_metric_manager

    manager = resolve_metric_manager(self._env, term="PlanePaintOverlay", metric_name=self.metric_name)
    metric = manager.get_term(self.metric_name)
    if not isinstance(metric, PaintedPlaneSource):
        raise TypeError(
            f"PlanePaintOverlay paints the marks a metric term returns from painted_plane(envs_idx), but metric "
            f"'{self.metric_name}' ({type(metric).__name__}) has no painted_plane(). Set metric_name to a metric "
            f"that keeps marks on a plane (active metrics: {manager.active_terms}); a metric class term becomes "
            "one by defining painted_plane(envs_idx) returning eden.entities.camera_overlays.PaintedPlane."
        )
    self._metric = metric
    # One env's worth, to check the contract here rather than on the first frame.
    plane = metric.painted_plane(torch.zeros(1, dtype=torch.long, device=self._env.device))
    subject = f"PlanePaintOverlay: metric '{self.metric_name}' ({type(metric).__name__}) returned a PaintedPlane"
    if plane.rgb is None and self._rgb is None:
        raise ValueError(
            f"{subject} of marks of one colour (rgb=None), and the overlay has no color to paint them in. "
            "Configure color=(r, g, b) in [0, 1]: a task's recording_camera() passes the colour it keeps (the "
            "whiteboard marker's ink)."
        )
    if plane.rgb is not None and self._rgb is not None:
        raise ValueError(
            f"{subject} with the marks' own colours (rgb), so color={self.color!r} would never be painted. Leave "
            "color out for this metric."
        )
    if plane.rgb is not None and tuple(plane.rgb.shape) != (1, *plane.grid.shape, 3):
        raise ValueError(
            f"{subject} with rgb {tuple(plane.rgb.shape)} for one env; expected (1, *grid.shape, 3) = "
            f"{(1, *plane.grid.shape, 3)}, a colour per cell of the grid it returned."
        )
    if plane.rgb is not None and not plane.rgb.is_floating_point():
        raise ValueError(
            f"{subject} with rgb of dtype {plane.rgb.dtype}; the marks' colours must be floating, in [0, 1]. An "
            "8-bit image would tint by its 0-255 values: divide it by 255 first."
        )
    if tuple(plane.weight.shape) != (1, *plane.grid.shape):
        raise ValueError(
            f"{subject} with weight {tuple(plane.weight.shape)} for one env; expected (1, *grid.shape) = "
            f"{(1, *plane.grid.shape)}, one grid per env asked for, on the PlaneGrid it returned."
        )
    if not (plane.weight.dtype == torch.bool or plane.weight.is_floating_point()):
        raise ValueError(
            f"{subject} weight of dtype {plane.weight.dtype}; it must be floating, in [0, 1], or bool (a cell "
            "marked or not). An integer grid has no scale the overlay could read: divide it by its full value "
            "first."
        )
    axes = torch.stack([plane.u_axis, plane.v_axis])
    if not torch.allclose(axes @ axes.T, torch.eye(2, device=axes.device), atol=1e-5):
        raise ValueError(
            f"{subject} with axes u_axis={plane.u_axis.tolist()}, v_axis={plane.v_axis.tolist()}, which are not "
            "orthonormal; the plane test and the cell lookup measure metres along them. Return unit, "
            f"perpendicular axes from {type(metric).__name__}.painted_plane()."
        )
    renderer = self._camera.renderer_kind
    if renderer not in _PIXEL_ENCODING:
        raise ValueError(
            f"PlanePaintOverlay on camera {self._camera.display_name}, whose frames come from the {renderer}. The "
            "overlay shades marks by how the renderer turns a surface's colour into pixels, which is measured for "
            f"the {' and the '.join(k.replace('_', ' ') for k in _PIXEL_ENCODING)} only. Record this camera with "
            "one of those."
        )
    self._srgb = _PIXEL_ENCODING[renderer] == "srgb"
    surface = _plain_surface_rgb(self._env, plane.entity_name, subject=f"{subject} on entity")
    if min(surface) < MIN_SURFACE_CHANNEL:
        raise ValueError(
            f"{subject} on entity '{plane.entity_name}', whose colour {surface} has a channel under "
            f"{MIN_SURFACE_CHANNEL}. The marks are shaded as the surface is, by dividing its rendered pixels by its "
            "colour, and so dark a channel turns the frame's 8-bit steps into visible bands. Lighten the "
            f"surface's colour to at least {MIN_SURFACE_CHANNEL} per channel."
        )
    # A marked pixel is the surface's rendered pixel times the mark's colour over this, at full weight.
    self._surface = torch.tensor(surface, device=self._env.device)
    self._color = None if self._rgb is None else torch.tensor(self._rgb, device=self._env.device)
paint
paint(rgb: Tensor, buffers: OverlayBuffers, envs_idx: Tensor) -> Tensor
Source code in eden/entities/camera_overlays.py
def paint(self, rgb: torch.Tensor, buffers: OverlayBuffers, envs_idx: torch.Tensor) -> torch.Tensor:
    plane: PaintedPlane = self._metric.painted_plane(envs_idx)
    marks = plane.weight.float()  # a bool or half grid, as build() allows
    rows, cols = plane.grid.shape
    offset = buffers.pointcloud - plane.origin
    u, v = (offset * plane.u_axis).sum(-1), (offset * plane.v_axis).sum(-1)
    gap = (offset * torch.linalg.cross(plane.u_axis, plane.v_axis)).sum(-1)
    on_plane = buffers.pointcloud_valid & (gap.abs() < self.max_gap)

    # The pixels whose centre lies on the grid, grown by one: a sub-pixel interpolates between neighbouring
    # centres, so every sub-pixel on the grid belongs to one of them. The crop is their bounding box over the
    # batch, plus a pixel of margin so its sub-pixels interpolate as they would across the whole frame.
    _, on_grid = self._cells(u, v, plane, rows, cols)
    near = F.max_pool2d((on_plane & on_grid)[:, None].float(), 3, stride=1, padding=1)[:, 0].amax(dim=0) > 0
    hit_rows, hit_cols = (
        torch.nonzero(near.any(dim=1)),
        torch.nonzero(near.any(dim=0)),
    )  # syncs: a render, not a step
    if hit_rows.numel() == 0:
        return rgb
    height, width = near.shape
    r0, r1 = max(int(hit_rows[0]) - 1, 0), min(int(hit_rows[-1]) + 2, height)
    c0, c1 = max(int(hit_cols[0]) - 1, 0), min(int(hit_cols[-1]) + 2, width)

    uv = torch.stack([u, v], dim=1)[:, :, r0:r1, c0:c1]
    if self.supersample > 1:
        uv = F.interpolate(uv, scale_factor=self.supersample, mode="bilinear", align_corners=False)
    cell_idx, on_grid = self._cells(uv[:, 0], uv[:, 1], plane, rows, cols)
    weight = marks.flatten(1).gather(1, cell_idx.flatten(1)).view_as(cell_idx) * on_grid
    if plane.rgb is not None:
        # Each sub-pixel's cell's colour, weighted: averaged with the weights, over each pixel's sub-pixels.
        cells = plane.rgb.float().flatten(1, 2)  # (K, cells, 3)
        index = cell_idx.flatten(1)[..., None].expand(-1, -1, 3)
        color = cells.gather(1, index).view(*cell_idx.shape, 3) * weight[..., None]
        if self.supersample > 1:
            color = F.avg_pool2d(color.permute(0, 3, 1, 2), self.supersample).permute(0, 2, 3, 1)
    if self.supersample > 1:
        weight = F.avg_pool2d(weight[:, None], self.supersample)[:, 0]
    on = on_plane[:, r0:r1, c0:c1]
    weight = torch.where(on, weight, 0.0)[..., None]
    if plane.rgb is None:
        color = self._color * weight  # one colour: the weighted colour is it times the weight
    else:
        color = torch.where(on[..., None], color, 0.0)

    painted = rgb.clone()
    # The surface's rendered pixel already holds the light that reached it (its shadows included); the mark keeps
    # that light and changes the colour it falls on. Each sub-pixel's tint is lerp(1, colour / surface, weight),
    # and averaged over a pixel's sub-pixels that is 1 - weight + (colour * weight) / surface.
    tint = 1.0 - weight + color / self._surface
    crop = rgb[:, r0:r1, c0:c1].float()
    if self._srgb:
        # Through linear light, for the marked pixels only: the crop is the marks' bounding box over every image of
        # the frame, most of it unmarked, and an unmarked pixel's tint is 1.
        marked = weight[..., 0] > 0  # syncs, as the crop does: a render, not a step
        blend = crop.clone()
        blend[marked] = _srgb_encode((_srgb_decode(crop[marked] / 255.0) * tint[marked]).clamp_(max=1.0)) * 255.0
    else:
        blend = (crop * tint).clamp_(max=255.0)
    painted[:, r0:r1, c0:c1] = blend if rgb.is_floating_point() else blend.round().to(rgb.dtype)
    return painted

PaintedPlane dataclass

PaintedPlane(origin: Tensor, u_axis: Tensor, v_axis: Tensor, grid: PlaneGrid, weight: Tensor, entity_name: str, rgb: Tensor | None = None)

Marks on a plane, on a grid of square cells: what :class:PlanePaintOverlay paints.

The plane runs through origin along the orthonormal axes u_axis and v_axis (world frame). Cell (r, c) of weight is cell (r, c) of grid in the plane's (u, v) (:class:~eden.utils.plane_grid.PlaneGrid: row 0 at the top). A curved surface may be described by the plane touching it, within :class:PlanePaintOverlay's rule.

PARAMETER DESCRIPTION
origin

(3,) each, on the env's device.

TYPE: Tensor

u_axis

(3,) each, on the env's device.

TYPE: Tensor

v_axis

(3,) each, on the env's device.

TYPE: Tensor

grid

Where the cells lie on the plane: the grid the metric keeps its marks on.

TYPE: PlaneGrid

weight

(len(envs_idx), *grid.shape), floating in [0, 1] or bool: how much each cell is marked, for the envs painted_plane(envs_idx) was asked for, in that order.

TYPE: Tensor

entity_name

The entity whose surface the marks lie on (the board, the sheet). Its colour is what the overlay shades the marks against, so it has to be one plain, opaque colour: no image texture, no emission, no transparency, the same in every env, and the same on every one of its geoms (model a board's marked face apart from its frame).

TYPE: str

rgb

(len(envs_idx), *grid.shape, 3), floating in [0, 1]: each cell's colour, for marks of many colours (a painting's). None (marks of one colour, an ink's): the overlay's color paints them.

TYPE: Tensor | None DEFAULT: None

Attributes

origin instance-attribute
origin: Tensor
u_axis instance-attribute
u_axis: Tensor
v_axis instance-attribute
v_axis: Tensor
grid instance-attribute
grid: PlaneGrid
weight instance-attribute
weight: Tensor
entity_name instance-attribute
entity_name: str
rgb class-attribute instance-attribute
rgb: Tensor | None = None

Methods:

PaintedPlaneSource

Bases: Protocol

A metric term that keeps marks on a plane, as :class:PlanePaintOverlay reads them. Structural: a term qualifies by defining the method, without subclassing this.

Methods:

painted_plane
painted_plane(envs_idx: Tensor) -> PaintedPlane

The marks of the envs envs_idx (a (K,) long tensor), read at every frame for the envs it shows.

Source code in eden/entities/camera_overlays.py
def painted_plane(self, envs_idx: torch.Tensor) -> PaintedPlane:
    """The marks of the envs ``envs_idx`` (a ``(K,)`` long tensor), read at every frame for the envs it shows."""

BackgroundComposite

BackgroundComposite(camera: Camera, options: CameraOverlayOptions)

Bases: CameraOverlay

Replaces the pixels where the sim shows a background with another image: one of entities, or nothing at all (segmentation id 0, the renderer's own background). The overlays after it leave those pixels as composited (:meth:replaced_pixels). Subclasses say where the background comes from (:meth:background).

With shadow_strength the background is darkened where the sim pass shows shadows on entities (:mod:eden.utils.background_shadows). The rasterizer renders the view a second time without shadows to measure them, which needs EnvOptions.rasterizer_shadows on and a GPU-backed OpenGL context. The batch renderer bakes shadows in, so it has them estimated, which is exact for flat, untextured entities under directional lights: an image-textured entity is refused, and at least one BatchRendererOptions.lights entry needs castshadow=True. Shading holds about 20 * N * H * W bytes of temporaries per frame (about 1 GB at 1024 envs of 224x224).

PARAMETER DESCRIPTION
entities

Scene entities whose pixels the background replaces, every link of each (segmentation_level may give an entity several ids). The renderer's empty background is always replaced.

TYPE: list[str]

shadow_strength

How strongly the sim's shadows on entities darken the background, in [0, 1]: 0 pastes it as is, so objects cast no shadow on it; 1 turns a background pixel that receives no light black. Needs at least one entry in entities, since the shadows are read from them.

TYPE: float

Source code in eden/entities/camera_overlays.py
def __init__(self, camera: Camera, options: CameraOverlayOptions) -> None:
    super().__init__(options)
    # CameraOverlayOptions converts its extras already, unless they were read before this module was imported.
    params = {name: getattr(self, name) for name in self.get_parameter_names()}
    params = validated_parameter_values(type(self), params, subject=type(self).__name__)
    for name, value in params.items():
        setattr(self, name, value)
    type(self).check_parameters(params)
    self._camera = camera
    self._env = camera._env

Attributes

entities class-attribute instance-attribute
entities: list[str] = []
shadow_strength class-attribute instance-attribute
shadow_strength: float = 0.0

Methods:

check_parameters classmethod
check_parameters(params: Mapping[str, Any]) -> None
Source code in eden/entities/camera_overlays.py
@classmethod
def check_parameters(cls, params: Mapping[str, Any]) -> None:
    strength = params["shadow_strength"]
    if not 0.0 <= strength <= 1.0:
        raise ValueError(f"{cls.__name__} needs shadow_strength in [0, 1]; got {strength}.")
    if strength > 0.0 and not params["entities"]:
        raise ValueError(
            f"{cls.__name__}(shadow_strength={strength}) needs at least one entry in entities: the shadows are "
            "read from those entities in the sim pass. Name the surface the background stands in for, e.g. "
            "entities=['plane']."
        )
build
build() -> None
Source code in eden/entities/camera_overlays.py
def build(self) -> None:
    # Deferred: the env module imports the entities, so a module-scope import here would be a cycle.
    from eden.utils.misc import resolve_named_entity

    entity_seg_ids = self._env.entity_segmentation_ids
    surface_ids = []
    for entity_name in self.entities:
        entity = resolve_named_entity(
            self._env.entities,
            entity_name,
            hint=f"{type(self).__name__}(entities=...) lists it; list only those.",
        )
        # An entity owns one id per link/geom at segmentation_level="link"/"geom", and every one of them has to
        # be listed or the entity is only partly treated as background.
        seg_ids = entity_seg_ids.get(entity.idx)
        if not seg_ids:
            raise ValueError(
                f"{type(self).__name__}(entities=...) names entity '{entity_name}', but the scene assigns it no "
                "segmentation id, so it could not be masked as background."
            )
        surface_ids.extend(seg_ids)
    #: The segmentation ids replaced: the renderer's empty background (0) and every id of ``entities``.
    self.background_ids = [0, *surface_ids]
    self._background_ids = torch.tensor(self.background_ids, device=self._env.device)
    # The surfaces that receive shadows: every entity id, not the renderer's empty background.
    self._shadow_surface_ids = torch.tensor(surface_ids, dtype=torch.long, device=self._env.device)
    if self.shadow_strength > 0.0:
        self._check_sim_pass_casts_shadows()
background
background(rgb: Tensor, envs_idx: Tensor) -> Tensor

The image the background pixels of rgb take: (1, H, W, 3) for every image, or (N, H, W, 3) one per image, on the env's device, in any dtype holding [0, 255].

Source code in eden/entities/camera_overlays.py
def background(self, rgb: torch.Tensor, envs_idx: torch.Tensor) -> torch.Tensor:
    """The image the background pixels of ``rgb`` take: ``(1, H, W, 3)`` for every image, or ``(N, H, W, 3)``
    one per image, on the env's device, in any dtype holding ``[0, 255]``.
    """
    raise NotImplementedError
replaced_pixels
replaced_pixels(buffers: OverlayBuffers) -> Tensor
Source code in eden/entities/camera_overlays.py
def replaced_pixels(self, buffers: OverlayBuffers) -> torch.Tensor:
    return torch.isin(buffers.segmentation, self._background_ids)
paint
paint(rgb: Tensor, buffers: OverlayBuffers, envs_idx: Tensor) -> Tensor
Source code in eden/entities/camera_overlays.py
def paint(self, rgb: torch.Tensor, buffers: OverlayBuffers, envs_idx: torch.Tensor) -> torch.Tensor:
    background = self.background(rgb, envs_idx).to(self._env.device)
    if self.shadow_strength > 0.0:
        background = self._shade(background, rgb, buffers.segmentation)
    return torch.where(self.replaced_pixels(buffers)[..., None], background.to(rgb.dtype), rgb)

BackgroundImageComposite

BackgroundImageComposite(camera: Camera, options: CameraOverlayOptions)

Bases: BackgroundComposite

Replaces the background with the image at image_path, scaled to the camera's resolution: a SimplerEnv-style real-image backdrop, or a green screen.

PARAMETER DESCRIPTION
image_path

The background image.

TYPE: str

entities

See :class:BackgroundComposite.

shadow_strength

See :class:BackgroundComposite.

Source code in eden/entities/camera_overlays.py
def __init__(self, camera: Camera, options: CameraOverlayOptions) -> None:
    super().__init__(camera, options)
    image = Image.open(self.image_path).convert("RGB").resize(tuple(camera.cam_resolution))
    self._image = torch.as_tensor(np.array(image), device=self._env.device)[None]

Attributes

image_path class-attribute instance-attribute
image_path: str = ''

Methods:

check_parameters classmethod
check_parameters(params: Mapping[str, Any]) -> None
Source code in eden/entities/camera_overlays.py
@classmethod
def check_parameters(cls, params: Mapping[str, Any]) -> None:
    super().check_parameters(params)
    if not params["image_path"]:
        raise ValueError(
            "BackgroundImageComposite needs image_path, the background image: "
            "BackgroundImageComposite.configure(image_path='backdrop.png', entities=['plane'])."
        )
background
background(rgb: Tensor, envs_idx: Tensor) -> Tensor
Source code in eden/entities/camera_overlays.py
def background(self, rgb: torch.Tensor, envs_idx: torch.Tensor) -> torch.Tensor:
    return self._image

SplatComposite

SplatComposite(camera: Camera, options: CameraOverlayOptions)

Bases: BackgroundComposite

Replaces the background with the env's Gaussian splat, rendered from the camera's pose (:class:~eden.utils.batch_splat_renderer.BatchSplatRenderer). Only a splat env's camera can carry it.

Whether the splat is rendered once and used for every env's image, or once per env, is the camera's (Camera.splat_single_view: :class:~eden.entities.camera.SplatCamera or :class:~eden.entities.camera.BatchSplatCamera, picked by SplatOptions.camera_mode).

PARAMETER DESCRIPTION
entities

See :class:BackgroundComposite.

shadow_strength

See :class:BackgroundComposite.

Source code in eden/entities/camera_overlays.py
def __init__(self, camera: Camera, options: CameraOverlayOptions) -> None:
    super().__init__(camera, options)
    if camera.splat_single_view is None:
        raise ValueError(
            f"SplatComposite renders the env's Gaussian splat, but camera {camera.display_name} is not a splat "
            "env's camera. Build the task with splat_options (an EdenSplatConfig / EdenSplatRLConfig), or use "
            "BackgroundImageComposite for a static backdrop."
        )

Methods:

background
background(rgb: Tensor, envs_idx: Tensor) -> Tensor
Source code in eden/entities/camera_overlays.py
def background(self, rgb: torch.Tensor, envs_idx: torch.Tensor) -> torch.Tensor:
    env = self._env
    renderer = env._splat_renderer
    # Per-env mode: refresh the animated Gaussians from sim poses (once per render).
    env._refresh_splat_gaussians()

    # Per-env camera poses: a free camera reports one (4,4), an attached one (N,4,4); in per-env Gaussian mode a
    # single shared pose is broadcast to num_envs so every env's (independently animated) Gaussians are rendered.
    cam_T = torch.as_tensor(self._camera.get_transform(), dtype=torch.float32, device=env.device)
    if cam_T.ndim == 2:
        cam_T = cam_T.unsqueeze(0)
    cam_pos, cam_rot = cam_T[:, :3, 3], cam_T[:, :3, :3]
    if renderer.per_env_gaussians and cam_pos.shape[0] == 1:
        cam_pos = cam_pos.expand(env.num_envs, 3)
        cam_rot = cam_rot.expand(env.num_envs, 3, 3)
    if self._camera.splat_single_view:
        cam_pos, cam_rot = cam_pos[:1], cam_rot[:1]  # one view, used for every image

    width, height = self._camera.cam_resolution
    splat, _ = renderer.render(cam_pos, cam_rot, height, width, self._camera.cam_fov)
    return splat.mul(255).byte()  # the sim pass's uint8 RGB

Functions:

build_camera_overlay

build_camera_overlay(camera: Camera, options: CameraOverlayOptions, index: int) -> CameraOverlay

The overlay options names, constructed for camera: entry index of its CameraOptions.overlays.

Source code in eden/entities/camera_overlays.py
def build_camera_overlay(camera: Camera, options: CameraOverlayOptions, index: int) -> CameraOverlay:
    """The overlay ``options`` names, constructed for ``camera``: entry ``index`` of its ``CameraOptions.overlays``."""
    where = f"Camera {camera.display_name}: CameraOptions.overlays[{index}]"
    if not options.name:
        raise ValueError(
            f"{where} names no overlay (name={options.name!r}). Build the entry with the overlay class's configure(), "
            "e.g. PlanePaintOverlay.configure(metric_name='drawing', color=(0, 0, 0)), rather than "
            "CameraOverlayOptions(...)."
        )
    try:
        overlay_class = CAMERA_OVERLAY_REGISTRY.get(options.name)
    except KeyError as e:
        raise ValueError(f"{where}: {e.args[0]}") from None
    if (overlay_class._needs_pointcloud or overlay_class._needs_segmentation) and not camera.supports_pointcloud:
        needs = "point cloud" if overlay_class._needs_pointcloud else "segmentation"
        raise ValueError(
            f"{where}: {overlay_class.__name__} paints by the frame's {needs}, which a sensor-backend camera "
            "cannot render. Remove sensor_options from this CameraOptions to use the legacy camera backend."
        )
    return overlay_class(camera, options)

Sensors

Sensor

Sensor(env: EnvBase, options: SensorOptions)

Bases: ConfigurableMixin[SensorOptions]

Wrapper around a Genesis sensor, configured from :class:SensorOptions.

PARAMETER DESCRIPTION
sensor

The Genesis sensor options.

attach_entity_name

Name of the entity to attach the sensor to. Empty for static sensors.

TYPE: str

attach_link_name

Name of the link to attach the sensor to.

TYPE: str

track_link_names

Resolved into the nested sensor's track_link_idx (global link indices) if applicable to the sensor. Each entry is either: - entity_name - every link on that scene entity (Genesis link order) is tracked. - entity_name/link_name - a single link on that entity. link_name is the Genesis link name (with morph-prefix fallback if a short suffix is used, e.g. baselink → sphere_baselink).

TYPE: list[str]

filter_link_names

Resolved into the nested sensor's filter_link_idx (global link indices) for contact sensors (Contact / ContactForce): contacts whose counterpart link is one of these are ignored, as if that link were not touching. Same token grammar as track_link_names.

TYPE: list[str]

Source code in eden/entities/sensor.py
def __init__(self, env: EnvBase, options: SensorOptions):
    self._uid = gs.UID()
    self._options = options
    self._env = env
    self._sensor = None  # Will be set in pre_build()

    # SensorOptions declares every Sensor parameter, so today this only ever takes the
    # options branch -- the class-default fallback is unreachable here. It is still routed
    # through the shared helper so a parameter added to Sensor but not to SensorOptions
    # cannot reintroduce the aliasing (see mirror_options_onto).
    mirror_options_onto(self, self._options)

Attributes

sensor_options instance-attribute

sensor_options: SensorOptions

attach_entity_name class-attribute instance-attribute

attach_entity_name: str = ''
attach_link_name: str = ''
track_link_names: list[str] = []
filter_link_names: list[str] = []

is_attached property

is_attached: bool

Whether this sensor is attached to an entity link.

sensor property

sensor

Access the underlying Genesis sensor object.

Methods:

pre_build

pre_build()
Source code in eden/entities/sensor.py
def pre_build(self):
    # If sensor is attached to an entity, inject entity_idx and link_idx_local
    if self.attach_entity_name:
        entity = resolve_named_entity(
            self._env.entities,
            self.attach_entity_name,
            hint="SensorOptions.attach_entity_name names it; set it to one of those.",
        )
        self._options.sensor.entity_idx = entity.idx

        if self.attach_link_name:
            link = entity.get_link(self.attach_link_name)
            self._options.sensor.link_idx_local = link.idx_local
    elif self.attach_link_name:
        raise ValueError("Should provide attach_entity_name if attach_link_name is provided.")

    self._apply_track_link_indices_from_names()
    self._apply_filter_link_indices_from_names()

    self._sensor = self._env.scene.add_sensor(self._options.sensor)

get_pos

get_pos(envs_idx=None) -> Tensor

Get the world position of the sensor.

For attached sensors, this computes link_pos + transform_by_quat(offset_pos, link_quat). For static sensors, returns the initial position from sensor options.

RETURNS DESCRIPTION
pos

World position of shape (num_envs, 3).

TYPE: Tensor

Source code in eden/entities/sensor.py
def get_pos(self, envs_idx=None) -> torch.Tensor:
    """Get the world position of the sensor.

    For attached sensors, this computes `link_pos + transform_by_quat(offset_pos, link_quat)`.
    For static sensors, returns the initial position from sensor options.

    Returns
    -------
    pos : torch.Tensor
        World position of shape (num_envs, 3).
    """
    if not self.is_attached:
        pos = torch.tensor(self._options.sensor.pos_offset, dtype=torch.float32, device=self._env.device)
        return pos.unsqueeze(0).expand(self._env.num_envs, -1)

    link = self._sensor._link
    # relative=False: world-frame link pose (pre-1.1.1 default) so the sensor pose is
    # composed in world frame. Genesis >=1.1.1 defaults link getters to the user frame.
    link_pos = link.get_pos(envs_idx, relative=False)  # (num_envs, 3)
    link_quat = link.get_quat(envs_idx, relative=False)  # (num_envs, 4) wxyz

    offset_pos = self._sensor._shared_metadata.offsets_pos[:, self._sensor._idx]  # (B, 3)
    if envs_idx is not None:
        offset_pos = offset_pos[envs_idx]

    return link_pos + transform_by_quat(offset_pos, link_quat)

get_quat

get_quat(envs_idx=None) -> Tensor

Get the world orientation of the sensor as a quaternion (wxyz).

For attached sensors, this computes quat_mul(link_quat, offset_quat). For static sensors, returns the initial orientation from sensor options.

RETURNS DESCRIPTION
quat

World orientation quaternion (wxyz) of shape (num_envs, 4).

TYPE: Tensor

Source code in eden/entities/sensor.py
def get_quat(self, envs_idx=None) -> torch.Tensor:
    """Get the world orientation of the sensor as a quaternion (wxyz).

    For attached sensors, this computes `quat_mul(link_quat, offset_quat)`.
    For static sensors, returns the initial orientation from sensor options.

    Returns
    -------
    quat : torch.Tensor
        World orientation quaternion (wxyz) of shape (num_envs, 4).
    """
    if not self.is_attached:
        quat_np = euler_to_quat([self._options.sensor.euler_offset])  # (1, 4)
        quat = torch.tensor(quat_np, dtype=torch.float32, device=self._env.device).reshape(4)
        return quat.unsqueeze(0).expand(self._env.num_envs, -1)

    link = self._sensor._link
    # relative=False: world-frame link orientation (pre-1.1.1 default).
    link_quat = link.get_quat(envs_idx, relative=False)  # (num_envs, 4) wxyz

    offset_quat = self._sensor._shared_metadata.offsets_quat[:, self._sensor._idx]  # (B, 4)
    if envs_idx is not None:
        offset_quat = offset_quat[envs_idx]

    return quat_mul(link_quat, offset_quat)

set_pos_offset

set_pos_offset(pos_offset, envs_idx=None)

Set the positional offset of the sensor relative to the attached link.

PARAMETER DESCRIPTION
pos_offset

Position offset (x, y, z) in link-local frame.

TYPE: array - like

envs_idx

Environment indices. If None, applies to all environments.

TYPE: array - like DEFAULT: None

Source code in eden/entities/sensor.py
def set_pos_offset(self, pos_offset, envs_idx=None):
    """Set the positional offset of the sensor relative to the attached link.

    Parameters
    ----------
    pos_offset : array-like
        Position offset (x, y, z) in link-local frame.
    envs_idx : array-like, optional
        Environment indices. If None, applies to all environments.
    """
    if not self.is_attached:
        raise RuntimeError("Cannot set position offset on a static (unattached) sensor.")
    self._sensor.set_pos_offset(pos_offset, envs_idx=envs_idx)

set_quat_offset

set_quat_offset(quat_offset, envs_idx=None)

Set the rotational offset of the sensor relative to the attached link.

PARAMETER DESCRIPTION
quat_offset

Quaternion offset (wxyz) in link-local frame.

TYPE: array - like

envs_idx

Environment indices. If None, applies to all environments.

TYPE: array - like DEFAULT: None

Source code in eden/entities/sensor.py
def set_quat_offset(self, quat_offset, envs_idx=None):
    """Set the rotational offset of the sensor relative to the attached link.

    Parameters
    ----------
    quat_offset : array-like
        Quaternion offset (wxyz) in link-local frame.
    envs_idx : array-like, optional
        Environment indices. If None, applies to all environments.
    """
    if not self.is_attached:
        raise RuntimeError("Cannot set quaternion offset on a static (unattached) sensor.")
    self._sensor.set_quat_offset(quat_offset, envs_idx=envs_idx)

read

read(envs_idx=None) -> Tensor | tuple[Tensor, ...]

Read current sensor data (with noise, delay, and other effects if configured).

PARAMETER DESCRIPTION
envs_idx

Environment indices to read from. If None, reads from all environments.

TYPE: array - like DEFAULT: None

RETURNS DESCRIPTION
sensor_data

The sensor data with noise if configured. The return format depends on the sensor type.

TYPE: Tensor | tuple[Tensor, ...]

Source code in eden/entities/sensor.py
def read(self, envs_idx=None) -> torch.Tensor | tuple[torch.Tensor, ...]:
    """Read current sensor data (with noise, delay, and other effects if configured).

    Parameters
    ----------
    envs_idx : array-like, optional
        Environment indices to read from. If None, reads from all environments.

    Returns
    -------
    sensor_data: torch.Tensor | tuple[torch.Tensor, ...]
        The sensor data with noise if configured. The return format depends on the sensor type.
    """
    return self._sensor.read(envs_idx=envs_idx)

read_ground_truth

read_ground_truth(envs_idx=None) -> Tensor | tuple[Tensor, ...]

Read ground truth sensor data (without noise, delay, or other effects).

PARAMETER DESCRIPTION
envs_idx

Environment indices to read from. If None, reads from all environments.

TYPE: array - like DEFAULT: None

RETURNS DESCRIPTION
sensor_data

Ground truth sensor data. The return format depends on the sensor type.

TYPE: Tensor | tuple[Tensor, ...]

Source code in eden/entities/sensor.py
def read_ground_truth(self, envs_idx=None) -> torch.Tensor | tuple[torch.Tensor, ...]:
    """Read ground truth sensor data (without noise, delay, or other effects).

    Parameters
    ----------
    envs_idx : array-like, optional
        Environment indices to read from. If None, reads from all environments.

    Returns
    -------
    sensor_data: torch.Tensor | tuple[torch.Tensor, ...]
        Ground truth sensor data. The return format depends on the sensor type.
    """
    return self._sensor.read_ground_truth(envs_idx=envs_idx)

See also