Skip to content

Instantly share code, notes, and snippets.

@justagist
Created March 5, 2024 10:41
Show Gist options
  • Save justagist/a00a58fb960ff84eaaf44ca6f510fc71 to your computer and use it in GitHub Desktop.
Save justagist/a00a58fb960ff84eaaf44ca6f510fc71 to your computer and use it in GitHub Desktop.
Pybullet interface for any robot urdfs. Primary purpose is for testing controllers and planners. Not implemented for handling vision/camera/sensor information.
"""This is a standalone file for interfacing with any robot in pybullet.
FEATURES:
- Main use-case is for controlling the robot. Not implemented for testing vision/camera stuff.
- Handles prismatic, revolute and continuous joints.
- Control in torque (Position, velocity, torque targets) or in direct position mode.
- Retrieve kinematic and dynamics data for links and robot.
- Get contact states for specified end-effectors (or links)
TODO:
- place_on_ground: does not check if the collision is with the ground object only. It can be with any object in the world.
- optimise further using joint indices instead of names wherever possible.
- retrieve contact forces for links as well
"""
import pybullet as pb
from threading import Lock
import numpy as np
from typing import List, Dict, Any, Tuple, TypeAlias
from pprint import pprint
QuatType: TypeAlias = np.ndarray
"""Numpy array representating quaternion in format [x,y,z,w]"""
Vector3D: TypeAlias = np.ndarray
"""Numpy array representating 3D cartesian vector in format [x,y,z]"""
def wrap_angle(angle: float | np.ndarray) -> float | np.ndarray:
"""Wrap the provided angle(s) (radians) to be within -pi and pi.
Args:
angle (float | np.ndarray): Input angle (or array of angles) in radians.
Returns:
float | np.ndarray: Output after wrapping.
"""
return (angle + np.pi) % (2 * np.pi) - np.pi
def get_pybullet_object_joint_info(
objectId: int, cid: int = 0, verbose: bool = False
) -> Dict[str, Any]:
"""Get dictionarised version of joint information from pybullet's getJointInfo for all joints.
Args:
objectId (int): Pybullet object ID
cid (int, optional): Pybullet physics client ID. Defaults to 0.
verbose (bool, optional): Verbosity flag. Defaults to False.
Returns:
Dict[str, Any]: Key-value pairs of joint info.
Keys:
"jointIndex",
"jointName",
"jointType",
"qIndex",
"uIndex",
"flags",
"jointDamping",
"jointFriction",
"jointLowerLimit",
"jointUpperLimit",
"jointMaxForce",
"jointMaxVelocity",
"linkName",
"jointAxis",
"parentFramePos",
"parentFrameOrn",
"parentIndex",
"numActuatedJoint",
"actuatedJointName",
"actuatedJointIndex",
"actuatedJointLowerLimit",
"actuatedJointUpperLimit"
"""
PYBULLET_JOINT_INFO_KEYS = [
"jointIndex",
"jointName",
"jointType",
"qIndex",
"uIndex",
"flags",
"jointDamping",
"jointFriction",
"jointLowerLimit",
"jointUpperLimit",
"jointMaxForce",
"jointMaxVelocity",
"linkName",
"jointAxis",
"parentFramePos",
"parentFrameOrn",
"parentIndex",
]
numJoints = pb.getNumJoints(objectId, physicsClientId=cid)
jointInfoValues = [[] for _ in range(len(PYBULLET_JOINT_INFO_KEYS))]
for i in range(numJoints):
jointInfoTuple = pb.getJointInfo(objectId, i, physicsClientId=cid)
for jointInfoValue, jointInfoItem in zip(jointInfoValues, jointInfoTuple):
if isinstance(jointInfoItem, bytes):
jointInfoItem = jointInfoItem.decode()
jointInfoValue.append(jointInfoItem)
jointInfo = dict(zip(PYBULLET_JOINT_INFO_KEYS, jointInfoValues))
jointInfo["numJoint"] = numJoints
actuated_joint_name = []
actuated_joint_index = []
actuated_joint_lower_limits = []
actuated_joint_upper_limits = []
continuous_joint_name = []
continuous_joint_index = []
for n, name in enumerate(jointInfo["jointName"]):
jtype = jointInfo["jointType"][n]
if jtype == pb.JOINT_FIXED:
continue
j_upper_lim = jointInfo["jointUpperLimit"][n]
j_lower_lim = jointInfo["jointLowerLimit"][n]
actuated_joint_index.append(n)
actuated_joint_name.append(name)
actuated_joint_lower_limits.append(j_lower_lim)
actuated_joint_upper_limits.append(j_upper_lim)
if jtype == pb.JOINT_REVOLUTE and j_upper_lim == 0 and j_lower_lim == 0:
continuous_joint_name.append(name)
continuous_joint_index.append(n)
jointInfo["numActuatedJoint"] = len(actuated_joint_name)
jointInfo["actuatedJointName"] = actuated_joint_name
jointInfo["actuatedJointIndex"] = actuated_joint_index
jointInfo["continuousJointName"] = continuous_joint_name
jointInfo["continuousJointIndex"] = continuous_joint_index
jointInfo["actuatedJointLowerLimit"] = actuated_joint_lower_limits
jointInfo["actuatedJointUpperLimit"] = actuated_joint_upper_limits
if verbose:
print("jointInfo:")
pprint(jointInfo)
return jointInfo
def get_pybullet_object_link_info(
objectId: int, cid: int = 0, verbose: bool = False
) -> Dict[str, Any]:
"""Get dictionarised version of link information from links of a pybullet object.
Args:
objectId (int): Pybullet object ID
cid (int, optional): Pybullet physics client ID. Defaults to 0.
verbose (bool, optional): Verbosity flag. Defaults to False.
Returns:
Dict[str, Any]: Key-value pairs of joint info.
Keys:
"mass",
"lateralFriction",
"localInertiaDiagonal",
"localInertialPos",
"localInertialOrn",
"restitution",
"rollingFriction",
"spinningFriction",
"contactDamping",
"contactStiffness",
"bodyType",
"collisionMargin",
"index",
"name"
"""
PYBULLET_LINK_INFO_KEYS = [
"mass",
"lateralFriction",
"localInertiaDiagonal",
"localInertialPos",
"localInertialOrn",
"restitution",
"rollingFriction",
"spinningFriction",
"contactDamping",
"contactStiffness",
"bodyType",
"collisionMargin",
]
numJoints = pb.getNumJoints(objectId, physicsClientId=cid)
baseName = pb.getBodyInfo(objectId, physicsClientId=cid)[0].decode()
linkIndexes = [-1] + list(range(numJoints))
linkNames = [baseName]
for i in range(numJoints):
jointInfoTuple = pb.getJointInfo(objectId, i, physicsClientId=cid)
linkNames.append(jointInfoTuple[12].decode())
linkInfoValues = [[] for _ in range(len(PYBULLET_LINK_INFO_KEYS))]
for linkIndex in linkIndexes:
linkInfoTuple = pb.getDynamicsInfo(objectId, linkIndex, physicsClientId=cid)
for linkInfoValue, linkInfoIterm in zip(linkInfoValues, linkInfoTuple):
linkInfoValue.append(linkInfoIterm)
linkInfo = dict(zip(PYBULLET_LINK_INFO_KEYS, linkInfoValues))
linkInfo["index"] = linkIndexes
linkInfo["name"] = linkNames
if verbose:
print("linkInfo:")
pprint(linkInfo)
return linkInfo
def get_pybullet_object_info(
objectId: int, cid: int = 0, verbose: bool = True
) -> Dict[str, Any]:
"""Get dictionarised version of information about a pybullet object.
Args:
objectId (int): Pybullet object ID
cid (int, optional): Pybullet physics client ID. Defaults to 0.
verbose (bool, optional): Verbosity flag. Defaults to False.
Returns:
Dict[str, Any]: Key-value pairs of joint info.
Keys:
"objectId",
"objectName",
"objectMass",
"baseName",
"jointInfo",
"linkInfo",
"""
objectId = objectId
objectName = pb.getBodyInfo(objectId, physicsClientId=cid)[1].decode()
baseName = pb.getBodyInfo(objectId, physicsClientId=cid)[0].decode()
jointInfo = get_pybullet_object_joint_info(objectId, cid=cid)
linkInfo = get_pybullet_object_link_info(objectId, cid=cid)
objectMass = np.sum(linkInfo["mass"])
objectInfo = {
"objectId": objectId,
"objectName": objectName,
"objectMass": objectMass,
"baseName": baseName,
"jointInfo": jointInfo,
"linkInfo": linkInfo,
}
if verbose:
print("\n" + "*" * 100 + "\nPybullet Info " + "\u2193 " * 20 + "\n" + "*" * 100)
print("objectInfo:")
pprint(objectInfo)
print("*" * 100 + "\nPybullet Info " + "\u2191 " * 20 + "\n" + "*" * 100)
return objectInfo
def fix_pb_link_mass_inertia(
body_id: int,
cid: int = 0,
mass: float = 1e-9,
local_inertia_diagonal: float = (1e-9, 1e-9, 1e-9),
):
"""Fix pybullet virtual links.
When loading urdf, pybullet assigned default value for link with no inertial (default value mass=1,
localinertiadiagonal = 1,1,1; identity local inertial frame). This function fixes the link inertial
values to the given mass and local_inertia_diagonal; small values by default.
Args:
body_id (int): Pybullet object ID
cid (int, optional): Pybullet physics client ID. Defaults to 0.
mass (float, optional): Virtual mass to assign to virtual link. Defaults to 1e-9.
local_inertia_diagonal (float, optional): Values to assign to inertia matrix diagonals for virtual links.
Defaults to (1e-9, 1e-9, 1e-9).
"""
link_ids = [-1] + [-1] + list(range(pb.getNumJoints(body_id, physicsClientId=cid)))
for link_index in link_ids:
link_info = pb.getDynamicsInfo(body_id, link_index, physicsClientId=cid)
if link_info[0] == 1.0 and link_info[2] == (1.0, 1.0, 1.0):
pb.changeDynamics(
bodyUniqueId=body_id,
linkIndex=link_index,
mass=mass,
localInertiaDiagonal=local_inertia_diagonal,
physicsClientId=cid,
)
class PybulletSimulatorInterface:
def __init__(
self,
urdf_path: str,
cid: int = None,
run_async: bool = True,
ee_names: List[str] = None,
default_joint_positions: List[float] = None,
place_on_ground: bool = False,
default_base_position: Vector3D = np.zeros(3),
default_base_orientation: QuatType = np.array([0, 0, 0, 1]),
enable_torque_mode: bool = True,
verbose: bool = True,
load_ground_plane: bool = True,
):
"""Create a RobotInterface object for a pybullet simulation of a robot.
Args:
urdf_path (str): Path to urdf file of robot.
cid (int, optional): Physics client id of pybullet physics engine (if already running).
If None provided, will create a new physics GUI instance. Defaults to None.
run_async (bool, optional): Whether to run physics in a separate thread. If set to False,
step() method has to be called in the main thread. Defaults to True.
ee_names (List[str], optional): List of end-effectors for the robot. Defaults to None.
default_joint_positions (List[float], optional): Optional starting values for joints.
Defaults to None. NOTE: These values should be in the order of joints in pybullet.
place_on_ground (bool): If true, the base position height will automatically be adjusted
such that the robot is on the ground with the given joint positions. Defaults to False.
NOTE: This is a naive implementation. It does not actually check if the collision is
with the ground object only. It can be with any object in the world.
default_base_position (Vector3D, optional): Default position of the base of the robot
in the world frame during start-up. Note that the height value (z) is only used if
'place_on_ground' is set to False. Defaults to np.zeros(3).
default_base_orientation (QuatType, optional): Default orientation quaternion in the
world frame for the base of the robot during start-up. Defaults to np.array([0, 0, 0, 1]).
enable_torque_mode (bool, optional): Flag to enable torque controlling of the robot (control commands
have to be sent continuously to keep the robot from falling). Defaults to True.
verbose (bool, optional): Verbosity flag for debugging robot info during construction. Defaults to True.
"""
self.cid = cid
self.ground_id = None
if self.cid is None:
self.cid = pb.connect(pb.GUI_SERVER)
if load_ground_plane:
import pybullet_data
pb.setAdditionalSearchPath(pybullet_data.getDataPath())
self.ground_id = pb.loadURDF("plane.urdf")
else:
if place_on_ground:
print(
f"{__class__.__name__}: Cannot place robot on ground without creating a plane via this interface."
" Set `load_ground_plane=True` to create a ground plane."
)
place_on_ground = False
self.sync_mode = not run_async
self.default_start_pose = [default_base_position, default_base_orientation]
self.ee_names = ee_names
if self.ee_names is None:
self.ee_names = []
self.robot_id = pb.loadURDF(
urdf_path,
basePosition=self.default_start_pose[0],
physicsClientId=self.cid,
)
pb.setGravity(0, 0, -9.81, physicsClientId=self.cid)
self._state_mutex = Lock()
fix_pb_link_mass_inertia(body_id=self.robot_id, cid=self.cid)
self._retrieve_robot_info()
if verbose:
self._print_robot_info()
self.default_joint_positions = (
default_joint_positions
if default_joint_positions is not None
else np.zeros_like(self.actuated_joint_ids)
)
self.reset_actuated_joint_positions(
joint_positions=self.default_joint_positions
)
self.urdf_path = urdf_path
if place_on_ground:
self._place_robot_on_ground()
self._in_torque_mode = False
if enable_torque_mode:
self.set_torque_control_mode()
pb.setRealTimeSimulation(0 if self.sync_mode else 1, physicsClientId=self.cid)
def _retrieve_robot_info(self):
self.objectInfo = get_pybullet_object_info(
self.robot_id, cid=self.cid, verbose=False
)
self.name = self.objectInfo["objectName"]
self.mass = self.objectInfo["objectMass"]
self.base_name = self.objectInfo["baseName"]
self.link_names = self.objectInfo["linkInfo"]["name"]
self.link_ids = self.objectInfo["linkInfo"]["index"]
self.link_masses = self.objectInfo["linkInfo"]["mass"]
self.num_joints = self.objectInfo["jointInfo"]["numJoint"]
self.joint_names = self.objectInfo["jointInfo"]["jointName"]
self.joint_ids = self.objectInfo["jointInfo"]["jointIndex"]
self.joint_dampings = self.objectInfo["jointInfo"]["jointDamping"]
self.joint_frictions = self.objectInfo["jointInfo"]["jointFriction"]
self.joint_lower_limits = self.objectInfo["jointInfo"]["jointLowerLimit"]
self.joint_upper_limits = self.objectInfo["jointInfo"]["jointUpperLimit"]
self.joint_neutral_positions = (
np.array(self.joint_lower_limits) + np.array(self.joint_upper_limits)
) / 2
self.num_of_actuated_joints = self.objectInfo["jointInfo"]["numActuatedJoint"]
self.actuated_joint_names = self.objectInfo["jointInfo"]["actuatedJointName"]
self.actuated_joint_ids = self.objectInfo["jointInfo"]["actuatedJointIndex"]
self.actuated_joint_lower_limits = self.objectInfo["jointInfo"][
"actuatedJointLowerLimit"
]
self.actuated_joint_upper_limits = self.objectInfo["jointInfo"][
"actuatedJointUpperLimit"
]
self.continuous_joint_ids = self.objectInfo["jointInfo"]["continuousJointIndex"]
self.continuous_joint_names = self.objectInfo["jointInfo"][
"continuousJointName"
]
self.has_continuous_joints = len(self.continuous_joint_ids) > 0
self.actuated_joint_neutral_positions = (
np.array(self.actuated_joint_lower_limits)
+ np.array(self.actuated_joint_upper_limits)
) / 2
self.link_name_to_index = dict(zip(self.link_names, self.link_ids))
self.joint_name_to_index = dict(zip(self.joint_names, self.joint_ids))
self.ee_ids = [self.link_name_to_index[name] for name in self.ee_names]
def _print_robot_info(self):
print("\n")
print("*" * 100 + "\nSimRobot Info " + "\u2193 " * 20 + "\n" + "*" * 100)
print("robot ID: ", self.robot_id)
print("robot name: ", self.name)
print("robot mass: ", self.mass)
print("base link name: ", self.base_name)
print("link names: ", len(self.link_names), self.link_names)
print("link indexes: ", len(self.link_ids), self.link_ids)
print("link masses: ", len(self.link_masses), self.link_masses)
print("end-effectors ", self.ee_names)
print("num of joints: ", self.num_joints)
print("num of actuated joints:", self.num_of_actuated_joints)
print("joint names: ", len(self.joint_names), self.joint_names)
print("joint indexes: ", len(self.joint_ids), self.joint_ids)
print("joint dampings: ", len(self.joint_dampings), self.joint_dampings)
print(
"joint frictions: ", len(self.joint_frictions), self.joint_frictions
)
print(
"joint lower limits: ",
len(self.joint_lower_limits),
self.joint_lower_limits,
)
print(
"joint higher limits: ",
len(self.joint_upper_limits),
self.joint_upper_limits,
)
print(
"actuated joint names: ",
len(self.actuated_joint_names),
self.actuated_joint_names,
)
print(
"actuated joint indexes:",
len(self.actuated_joint_ids),
self.actuated_joint_ids,
)
print(
"continuous joint names: ",
len(self.continuous_joint_names),
self.continuous_joint_names,
)
print(
"continuous joint indexes:",
len(self.continuous_joint_ids),
self.continuous_joint_ids,
)
print("*" * 100 + "\nSimRobot Info " + "\u2191 " * 20 + "\n" + "*" * 100)
print("\n")
def step(self):
"""Step simulation (if this object was created with `async=False` argument)."""
if self.sync_mode:
pb.stepSimulation(physicsClientId=self.cid)
def _place_robot_on_ground(
self, move_resolution: float = 0.01, default_position=None
):
pb.setRealTimeSimulation(0)
in_collision = True
if default_position is None:
default_position = [
self.default_start_pose[0][0],
self.default_start_pose[0][1],
-move_resolution,
]
joint_pos = self.get_actuated_joint_positions(
actuated_joint_names=self.actuated_joint_names
).copy()
# print(f"{self.__class__.__name__}: Trying to place robot on the ground.")
while in_collision:
default_position[2] += move_resolution
self.reset_base_pose(
position=default_position, orientation=self.default_start_pose[1]
)
self.reset_actuated_joint_positions(
joint_positions=joint_pos, joint_names=self.actuated_joint_names
)
pb.stepSimulation(physicsClientId=self.cid)
in_collision = np.any(self.get_contact_states_of_links(self.link_ids))
# print(f"{self.__class__.__name__}: Robot should be just above the ground now.")
self.default_start_pose[0] = np.array(default_position)
def set_position_control_mode(self):
"""Set control mode to position-based (robot will stay in place if no commands sent)."""
pb.setJointMotorControlArray(
self.robot_id,
self.joint_ids,
pb.POSITION_CONTROL,
targetPositions=self.joint_neutral_positions,
physicsClientId=self.cid,
)
self._in_torque_mode = False
print("SimRobot position control mode enabled!")
def get_physics_parameters(self) -> Dict[str, Any]:
return pb.getPhysicsEngineParameters(physicsClientId=self.cid)
def set_torque_control_mode(self):
"""Set control mode to torque-based (robot will NOT stay in place if no commands sent)."""
pb.setJointMotorControlArray(
self.robot_id,
self.actuated_joint_ids,
pb.VELOCITY_CONTROL,
forces=[0.0] * self.num_of_actuated_joints,
physicsClientId=self.cid,
)
self._in_torque_mode = True
print("SimRobot torque control mode enabled!")
def reset_base_pose(self, position: Vector3D = None, orientation: QuatType = None):
"""Reset the robot's base pose to default pose.
Args:
position (Vector3D, optional): If specified, sets robot to this position. Defaults to None.
orientation (QuatType, optional): If specified, sets robot to this orientation.
Quaternion order: x,y,z,w. Defaults to None.
"""
if position is None:
position = self.default_start_pose[0]
if orientation is None:
orientation = self.default_start_pose[1]
pb.resetBasePositionAndOrientation(
self.robot_id, position, orientation, physicsClientId=self.cid
)
def reset_actuated_joint_positions(
self, joint_positions: np.ndarray = None, joint_names: List[str] = None
):
"""Reset the joint positions of the robot.
Args:
joint_positions (np.ndarray, optional): If specified, sets joints to these values, otherwise
uses default. Defaults to None.
joint_names (List[str], optional): Joint names in the order the position values were given.
If None provided, uses default order of self.actuated_joint_names.
"""
if joint_positions is None:
joint_positions = self.default_joint_positions
if joint_names is None:
joint_names = self.actuated_joint_names
for jointName, jointPosition in zip(joint_names, joint_positions):
pb.resetJointState(
self.robot_id,
self.get_joint_id(jointName),
jointPosition,
physicsClientId=self.cid,
)
def get_object_id(self) -> int:
return self.robot_id
def get_base_link_name(self) -> str:
return pb.getBodyInfo(self.robot_id, physicsClientId=self.cid)[0].decode()
def get_joint_name(self, joint_id: int) -> str:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[
1
].decode()
def get_joint_id(self, joint_name: str) -> int:
return self.joint_name_to_index[joint_name]
def get_joint_ids(self, joint_names: List[str]) -> List[int]:
return [self.joint_name_to_index[joint_name] for joint_name in joint_names]
def get_link_name(self, link_id: int) -> str:
if link_id == -1:
return self.get_base_link_name()
else:
return pb.getJointInfo(self.robot_id, link_id, physicsClientId=self.cid)[
12
].decode()
def get_link_index(self, link_name: str) -> int:
return self.link_name_to_index[link_name]
def get_joint_type(self, joint_id: int) -> str:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[2]
def get_joint_damping(self, joint_id: int) -> float:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[6]
def get_joint_friction(self, joint_id: int) -> float:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[7]
def get_joint_lower_limit_position(self, joint_id: int) -> float:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[8]
def get_joint_upper_limit_position(self, joint_id: int) -> float:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[9]
def get_joint_max_force(self, joint_id: int) -> float:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[10]
def get_joint_max_velocity(self, joint_id: int) -> float:
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[11]
def get_link_mass(self, link_id: int) -> float:
return pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[0]
def get_link_local_inertial_transform(
self, link_id: int
) -> Tuple[np.ndarray, QuatType]:
return pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[3:5]
def get_link_local_inertial_position(self, link_id: int) -> np.ndarray:
return np.array(
pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[3]
)
def get_link_local_inertial_quaternion(self, link_id: int) -> QuatType:
return np.array(
pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[4]
)
# Get Base states
def get_base_local_inertia_transform(self) -> Tuple[Vector3D, QuatType]:
return self.get_link_local_inertial_transform(-1)
def get_base_local_inertia_position(self) -> Vector3D:
return self.get_link_local_inertial_position(-1)
def get_base_local_inertia_quaternion(self) -> QuatType:
return self.get_link_local_inertial_quaternion(-1)
def get_base_com_position(self) -> Vector3D:
worldTransCom = pb.getBasePositionAndOrientation(
self.robot_id, physicsClientId=self.cid
)
localTransCom = self.get_base_local_inertia_transform()
comTransLocal = pb.invertTransform(
position=localTransCom[0],
orientation=localTransCom[1],
physicsClientId=self.cid,
)
worldTransLocal = pb.multiplyTransforms(
positionA=worldTransCom[0],
orientationA=worldTransCom[1],
positionB=comTransLocal[0],
orientationB=comTransLocal[1],
physicsClientId=self.cid,
)
return np.array(worldTransLocal[0])
def get_base_pose(self) -> Tuple[Vector3D, QuatType]:
p, q = pb.getBasePositionAndOrientation(self.robot_id, physicsClientId=self.cid)
return np.array(p), np.array(q)
def get_base_velocity(self) -> Tuple[Vector3D, Vector3D]:
lin, ang = pb.getBaseVelocity(self.robot_id, physicsClientId=self.cid)
return np.array(lin), np.array(ang)
# Get actuated joint states
def get_actuated_joint_positions(
self, actuated_joint_names: List[str]
) -> np.ndarray:
return np.array(
[
joint_state[0]
for joint_state in pb.getJointStates(
self.robot_id,
self.get_joint_ids(actuated_joint_names),
physicsClientId=self.cid,
)
]
)
def get_actuated_joint_velocities(
self, actuated_joint_names: List[str]
) -> np.ndarray:
return np.array(
[
joint_state[1]
for joint_state in pb.getJointStates(
self.robot_id,
self.get_joint_ids(actuated_joint_names),
physicsClientId=self.cid,
)
]
)
def get_actuated_joint_torques(self, actuated_joint_names: List[str]) -> np.ndarray:
return np.array(
[
joint_state[3]
for joint_state in pb.getJointStates(
self.robot_id,
self.get_joint_ids(actuated_joint_names),
physicsClientId=self.cid,
)
]
)
def get_actuated_joint_name_to_position(self) -> Dict[str, float]:
return dict(
zip(
self.actuated_joint_names,
[
joint_state[0]
for joint_state in pb.getJointStates(
self.robot_id, self.actuated_joint_ids, physicsClientId=self.cid
)
],
)
)
def get_actuated_joint_name_to_velocity(self) -> Dict[str, float]:
return dict(
zip(
self.actuated_joint_names,
[
joint_state[1]
for joint_state in pb.getJointStates(
self.robot_id, self.actuated_joint_ids, physicsClientId=self.cid
)
],
)
)
def get_actuated_joint_name_to_torque(self) -> Dict[str, float]:
return dict(
zip(
self.actuated_joint_names,
[
joint_state[3]
for joint_state in pb.getJointStates(
self.robot_id, self.actuated_joint_ids, physicsClientId=self.cid
)
],
)
)
def get_link_pose(self, link_id: int) -> Tuple[Vector3D, QuatType]:
if link_id == -1:
return self.get_base_pose()
p, o = pb.getLinkState(self.robot_id, link_id, physicsClientId=self.cid)[4:6]
return np.array(p), np.array(o)
def get_ee_contact_states(self, ee_names: List[str] = None) -> np.ndarray:
if ee_names is None:
ee_names = self.ee_names
return self.get_contact_states_of_links(
link_ids=[self.get_link_index(link_name=name) for name in ee_names]
)
def get_contact_states_of_links(self, link_ids: List[int]) -> np.ndarray:
return np.array(
[
int(
len(
pb.getContactPoints(
bodyA=self.robot_id,
linkIndexA=idx,
physicsClientId=self.cid,
)
)
> 0
)
for idx in link_ids
]
)
# Get robot states
def get_robot_states(
self, actuated_joint_names: List[str] = None, ee_names: List[str] = None
) -> Dict[str, Any]:
if actuated_joint_names is None:
actuated_joint_names = self.actuated_joint_names
joint_ids = self.actuated_joint_ids
else:
joint_ids = self.get_joint_ids(joint_names=actuated_joint_names)
if ee_names is None:
ee_ids = self.ee_ids
ee_names = self.ee_names
else:
ee_ids = [self.get_link_index(link_name=name) for name in ee_names]
joint_states = self.get_joint_states(joint_ids=joint_ids)
base_pose = self.get_base_pose()
base_vel = self.get_base_velocity()
return {
"base_position": base_pose[0],
"base_com_position": self.get_base_com_position(),
"base_quaternion": base_pose[1],
"base_velocity_linear": base_vel[0],
"base_velocity_angular": base_vel[1],
"actuated_joint_positions": joint_states[0],
"actuated_joint_velocities": joint_states[1],
"actuated_joint_torques": joint_states[2],
"joint_order": actuated_joint_names,
"ee_order": ee_names,
"ee_contact_states": self.get_contact_states_of_links(ee_ids),
}
def get_joint_states(
self, joint_ids: List[int] = None
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Get joint positions, velocities and torques.
Args:
joint_ids (List[int], Optional): List of joint ids (optional)
Returns:
Tuple[np.ndarray, np.ndarray, np.ndarray]: joint positions, joint velocities, joint torques
"""
if joint_ids is None:
joint_ids = self.actuated_joint_ids
dof = len(joint_ids)
q, v, tau = np.zeros(dof), np.zeros(dof), np.zeros(dof)
for n, idx in enumerate(joint_ids):
q[n], v[n], _, tau[n] = pb.getJointState(
self.robot_id, idx, physicsClientId=self.cid
)
return q, v, tau
def compute_joint_error(
self, target_q: np.ndarray, joint_names_order: List[str]
) -> np.ndarray:
if not self.has_continuous_joints:
return target_q - self.get_actuated_joint_positions(joint_names_order)
curr_q = self.get_actuated_joint_positions(joint_names_order)
err = target_q - curr_q
for n, joint_name in enumerate(joint_names_order):
if joint_name in self.continuous_joint_names:
err[n] = wrap_angle(target_q[n] - curr_q[n])
return err
def set_actuated_joint_commands(
self,
actuated_joint_names: List[str] = None,
q: float | np.ndarray = 0,
Kp: float | np.ndarray = 0,
dq: float | np.ndarray = 0,
Kd: float | np.ndarray = 0,
tau: float | np.ndarray = 0,
):
# can be further optimised for speed by storing last used actuated joint names, e.g.
# if using in a control loop
if actuated_joint_names is None:
actuated_joint_names = self.actuated_joint_names
if self._in_torque_mode:
tau_cmd = (
Kp
* self.compute_joint_error(
target_q=q, joint_names_order=actuated_joint_names
)
+ Kd * (dq - self.get_actuated_joint_velocities(actuated_joint_names))
+ tau
)
pb.setJointMotorControlArray(
self.robot_id,
self.get_joint_ids(actuated_joint_names),
pb.TORQUE_CONTROL,
forces=tau_cmd,
physicsClientId=self.cid,
)
else:
tau = [50] * len(actuated_joint_names)
pb.setJointMotorControlArray(
self.robot_id,
self.get_joint_ids(actuated_joint_names),
pb.POSITION_CONTROL,
targetPositions=q,
targetVelocities=dq,
forces=tau,
positionGains=Kp,
velocityGains=Kd,
physicsClientId=self.cid,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment