robogym/robot/shadow_hand/mujoco/mujoco_shadow_hand.py (103 lines of code) (raw):

import numpy as np from mujoco_py.generated import const from robogym.mujoco.simulation_interface import SimulationInterface from robogym.robot.shadow_hand.hand_forward_kinematics import ( FINGERTIP_SITE_NAMES, REFERENCE_SITE_NAMES, get_relative_positions, ) from robogym.robot.shadow_hand.hand_interface import ACTUATORS, Hand, Observation from robogym.robot.shadow_hand.hand_utils import ( denormalize_by_limit, normalize_by_limits, ) from robogym.robot.shadow_hand.mujoco.parameter_manager import MuJoCoParameterManager class MuJoCoObservation(Observation): """ Shadow Hand observation coming from the MuJoCo simulation """ def __init__( self, simulation: SimulationInterface, hand_prefix: str, joint_group: str ): fingers = np.array( [ simulation.mj_sim.data.get_site_xpos(hand_prefix + site) for site in FINGERTIP_SITE_NAMES ] ) reference = np.array( [ simulation.mj_sim.data.get_site_xpos(hand_prefix + site) for site in REFERENCE_SITE_NAMES ] ) self._fingertip_positions = get_relative_positions(fingers, reference) self._joint_positions = simulation.get_qpos(joint_group).copy() self._joint_vel = simulation.get_qvel(joint_group).copy() self._time = simulation.mj_sim.data.time self._force_limits = simulation.mj_sim.model.actuator_forcerange.copy() self._actuator_force = normalize_by_limits( simulation.mj_sim.data.actuator_force, self._force_limits ) def joint_positions(self) -> np.ndarray: return self._joint_positions def joint_velocities(self) -> np.ndarray: return self._joint_vel def actuator_effort(self) -> np.ndarray: return self._actuator_force def timestamp(self) -> float: return self._time def fingertip_positions(self) -> np.ndarray: return self._fingertip_positions class MuJoCoShadowHand(Hand): """ MuJoCo interface to interact with robotic Shadow Hand """ def get_name(self) -> str: return "unnamed-mujoco-shadowhand" def __init__( self, simulation: SimulationInterface, hand_prefix="robot0:", autostep=False ): """ :param simulation: simulation interface for the MuJoCo shadow hand xml :param hand_prefix: Prefix to add to the joint names while constructing the MuJoCo simulation :param autostep: When true, calls step() on the simulation whenever a control is set. This should only be used only when the MuJoCoShadowHand is being controlled without a SimulationRunner in the loop. """ self.simulation = simulation self.hand_prefix = hand_prefix self.autostep = autostep self.joint_group = hand_prefix + "hand_joint_angles" self.simulation.register_joint_group(self.joint_group, prefix=hand_prefix) self._parameter_manager = MuJoCoParameterManager(self.mj_sim) assert self.mj_sim.model.nu == len( ACTUATORS ), "Action space must have compatible shape" # Are we in the joint control mode or in the force control mode? self.joint_control_mode = True self.force_limits = self.mj_sim.model.actuator_forcerange.copy() # Store copies of parameters in the initial state self.gainprm_copy = self.mj_sim.model.actuator_gainprm.copy() self.biasprm_copy = self.mj_sim.model.actuator_biasprm.copy() self.ctrlrange_copy = self.mj_sim.model.actuator_ctrlrange.copy() def parameter_manager(self): return self._parameter_manager def actuator_ctrl_range_upper_bound(self) -> np.ndarray: # We use control range in xml instead of constants to take into account # ADR randomization for joint limit. return self.mj_sim.model.actuator_ctrlrange[:, 1] def actuator_ctrl_range_lower_bound(self) -> np.ndarray: # We use control range in xml instead of constants to take into account # ADR randomization for joint limit. return self.mj_sim.model.actuator_ctrlrange[:, 0] @property def mj_sim(self): """ MuJoCo MjSim simulation object """ return self.simulation.mj_sim def set_position_control(self, control: np.ndarray) -> None: assert self.is_position_control_valid(control), f"Invalid control: {control}" if not self.joint_control_mode: # Need to change the parameters of the motors # state. self.mj_sim.model.actuator_gaintype[:] = const.GAIN_USER self.mj_sim.model.actuator_biastype[:] = const.BIAS_USER self.mj_sim.model.actuator_gainprm[:] = self.gainprm_copy self.mj_sim.model.actuator_biasprm[:] = self.biasprm_copy self.mj_sim.model.actuator_ctrlrange[:] = self.ctrlrange_copy self.joint_control_mode = True self.mj_sim.data.ctrl[:] = control if self.autostep: self.mj_sim.step() def set_effort_control(self, control: np.ndarray) -> None: if self.joint_control_mode: # Need to change the parameters of the motors self.mj_sim.model.actuator_gaintype[:] = const.GAIN_FIXED self.mj_sim.model.actuator_biastype[:] = const.BIAS_NONE self.mj_sim.model.actuator_gainprm[:, 0] = 1.0 self.mj_sim.model.actuator_biasprm[:] = 0 self.mj_sim.model.actuator_ctrlrange[:] = np.array([[-1.0, 1.0]]) self.joint_control_mode = False # Transform 0 and 1 into force limits force_applied = denormalize_by_limit(control, self.force_limits) self.mj_sim.data.ctrl[:] = force_applied if self.autostep: self.mj_sim.step() def observe(self) -> Observation: return MuJoCoObservation(self.simulation, self.hand_prefix, self.joint_group)