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
----------
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.
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
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
[docs] 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.")