from typing import TYPE_CHECKING, Annotated, Any, Literal
from pydantic import Field, StrictBool, model_validator
import genesis as gs
from genesis.typing import NonNegativeFloat, PositiveFloat, StrictInt, StrArrayType, ValidFloat
from .kinematic import Kinematic
if TYPE_CHECKING:
from genesis.engine.entities.rigid_entity import RigidEntity
CoupType = Literal["two_way_soft_constraint", "external_articulation", "ipc_only"]
[docs]class Rigid(Kinematic["RigidEntity"]):
"""
The Rigid class represents a material used in rigid body simulation.
Note
----
This class is intended for use with the rigid solver and provides parameters
relevant to physical interactions such as friction, density, and signed distance fields (SDFs).
Parameters
----------
use_visual_raycasting : bool, optional
See Kinematic. Default is False.
rho : float or None, optional
The density of the material used to estimate mass if necessary. When None, the default depends on context:
1000 kg/m^3 if MuJoCo compatibility is enabled (``RigidOptions.enable_mujoco_compatibility``),
otherwise 600 kg/m^3 for basic rigid objects (mug, table...) vs 1500 kg/m^3 for poly-articulated
robots. Default is None.
friction : float, optional
Friction coefficient within the rigid solver. If None, a default of 1.0 may be used or parsed from file.
friction_torsional : float, optional
Torsional friction coefficient, resisting relative spin about the contact normal. Expressed in meters, as it
stands for the effective contact patch radius over which sliding friction acts. Only effective when torsional
friction is enabled at the scene level (see 'RigidOptions.enable_torsional_friction'). If None, parsed from
file when available (MJCF), otherwise 0.005. Default is None.
friction_rolling : float, optional
Rolling friction coefficient, resisting rolling about the two contact tangent axes. Expressed in meters, like
the torsional coefficient. Only effective when rolling friction is enabled at the scene level (see
'RigidOptions.enable_rolling_friction'). If None, parsed from file when available (MJCF), otherwise 0.0001.
Default is None.
needs_coup : bool, optional
Whether the material participates in coupling with other solvers. Default is True.
coup_friction : float, optional
Friction used during coupling. Must be non-negative. Default is 0.1.
coup_softness : float, optional
Softness of coupling interaction. Must be non-negative. Default is 0.002.
coup_restitution : float, optional
Restitution coefficient in collision coupling. Should be between 0 and 1. Default is 0.0.
sdf_cell_size : float, optional
Cell size in SDF grid in meters. Defines grid resolution. Default is 0.005.
sdf_min_res : int, optional
Minimum resolution of the SDF grid. Must be at least 16. Default is 32.
sdf_max_res : int, optional
Maximum resolution of the SDF grid. Must be >= sdf_min_res. Default is 128.
gravity_compensation : float, optional
Compensation factor for gravity. 1.0 cancels gravity. Default is 0.
coup_type : str or None, optional
Coupling mode for this entity. Only used by the IPC coupler. Requires ``needs_coup=True``.
If None, auto-selected based on entity type: ``'external_articulation'`` for fixed-base
articulated robots, ``'two_way_soft_constraint'`` for floating-base robots, and
``'ipc_only'`` for non-articulated objects. Valid values:
- 'two_way_soft_constraint': Two-way soft coupling.
- 'external_articulation': Joint-level coupling for articulated bodies. Joint positions will be coupled at
the DOF level.
- 'ipc_only': IPC controls entity, transforms copied to Genesis (one-way). Only supported by rigid
non-articulated objects.
Default is None.
coup_links : tuple of str or None, optional
Tuple of link names to include in coupling. When set, only the named links participate
in coupling; other links are excluded. Only supported with needs_coup=True and
``two_way_soft_constraint`` type in IPC. Default is None.
enable_coup_collision : bool, optional
Whether coupler collision is enabled for this entity's links. Only used by the IPC coupler.
Unlike ``needs_coup=False`` (which removes the entity from the coupler entirely), setting this to
False keeps the entity in the coupler for coupling forces but disables contact response. Default is True.
coup_collision_links : tuple of str or None, optional
Tuple of link names whose geoms participate in coupler collision. Only used by the IPC coupler.
Only effective when ``enable_coup_collision=True``. If None, all coupled links have collision.
When set, only the named links get coupler collision; other links are marked no-collision.
Default is None.
contact_resistance : float or None, optional
IPC coupling contact resistance/stiffness override for this entity. ``None`` means use
``IPCCouplerOptions.contact_resistance``. Default is None.
"""
use_visual_raycasting: StrictBool = False
rho: ValidFloat | None = None
friction: Annotated[ValidFloat, Field(ge=0.01, le=5.0)] | None = None
friction_torsional: Annotated[ValidFloat, Field(ge=0.0)] | None = None
friction_rolling: Annotated[ValidFloat, Field(ge=0.0)] | None = None
needs_coup: StrictBool = True
coup_friction: NonNegativeFloat = 0.1
coup_softness: NonNegativeFloat = 0.002
coup_restitution: Annotated[ValidFloat, Field(ge=0.0, le=1.0)] = 0.0
sdf_cell_size: PositiveFloat = 0.005
sdf_min_res: Annotated[StrictInt, Field(ge=16)] = 32
sdf_max_res: Annotated[StrictInt, Field(ge=16)] = 128
gravity_compensation: ValidFloat = 0.0
coup_type: CoupType | None = None
coup_links: StrArrayType | None = None
enable_coup_collision: StrictBool = True
coup_collision_links: StrArrayType | None = None
contact_resistance: PositiveFloat | None = None
@model_validator(mode="before")
@classmethod
def _resolve_defaults(cls, data: dict) -> dict:
# ipc_only entities have their dynamics fully controlled by IPC (gravity + collision).
# Genesis gravity must be disabled to avoid double-counting.
if data.get("coup_type") == "ipc_only":
grav_comp = data.get("gravity_compensation")
if grav_comp is not None and grav_comp != 0.0:
gs.raise_exception(
"`gravity_compensation` must be 0 with coup_type='ipc_only'. "
"IPC controls gravity for ipc_only entities."
)
data["gravity_compensation"] = 0.0
return data
def model_post_init(self, context: Any) -> None:
if self.coup_type is not None and not self.needs_coup:
gs.raise_exception(
"`coup_type` is only supported with needs_coup=True. "
f"Got needs_coup={self.needs_coup}, coup_type={self.coup_type!r}."
)
if self.coup_links is not None and (
not self.needs_coup or self.coup_type not in (None, "two_way_soft_constraint")
):
gs.raise_exception(
"`coup_links` is only supported with needs_coup=True and "
f"'two_way_soft_constraint' type in IPC. "
f"Got needs_coup={self.needs_coup}, coup_type={self.coup_type!r}."
)
if self.coup_collision_links is not None and not self.enable_coup_collision:
gs.raise_exception(
"`coup_collision_links` is only effective when `enable_coup_collision=True`. "
"Set `enable_coup_collision=False` to disable collision for all links."
)
if self.sdf_min_res > self.sdf_max_res:
gs.raise_exception("`sdf_min_res` must be smaller than or equal to `sdf_max_res`.")
if self.coup_restitution != 0:
gs.logger.warning("Non-zero `coup_restitution` could lead to instability. Use with caution.")