pybullet-robot 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .bullet_robot import BulletRobot
|
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Tuple, TypeAlias
|
|
2
|
+
from threading import Lock
|
|
3
|
+
import copy
|
|
4
|
+
from numbers import Number
|
|
5
|
+
from pprint import pprint
|
|
6
|
+
import pybullet as pb
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
QuatType: TypeAlias = np.ndarray
|
|
10
|
+
"""Numpy array representating quaternion in format [x,y,z,w]"""
|
|
11
|
+
Vector3D: TypeAlias = np.ndarray
|
|
12
|
+
"""Numpy array representating 3D cartesian vector in format [x,y,z]"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def wrap_angle(angle: float | np.ndarray) -> float | np.ndarray:
|
|
16
|
+
"""Wrap the provided angle(s) (radians) to be within -pi and pi.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
angle (float | np.ndarray): Input angle (or array of angles) in radians.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
float | np.ndarray: Output after wrapping.
|
|
23
|
+
"""
|
|
24
|
+
return (angle + np.pi) % (2 * np.pi) - np.pi
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_pybullet_object_joint_info(
|
|
28
|
+
objectId: int, cid: int = 0, verbose: bool = False
|
|
29
|
+
) -> Dict[str, Any]:
|
|
30
|
+
"""Get dictionarised version of joint information from pybullet's getJointInfo for all joints.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
objectId (int): Pybullet object ID
|
|
34
|
+
cid (int, optional): Pybullet physics client ID. Defaults to 0.
|
|
35
|
+
verbose (bool, optional): Verbosity flag. Defaults to False.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict[str, Any]: Key-value pairs of joint info.
|
|
39
|
+
Keys:
|
|
40
|
+
"jointIndex",
|
|
41
|
+
"jointName",
|
|
42
|
+
"jointType",
|
|
43
|
+
"qIndex",
|
|
44
|
+
"uIndex",
|
|
45
|
+
"flags",
|
|
46
|
+
"jointDamping",
|
|
47
|
+
"jointFriction",
|
|
48
|
+
"jointLowerLimit",
|
|
49
|
+
"jointUpperLimit",
|
|
50
|
+
"jointMaxForce",
|
|
51
|
+
"jointMaxVelocity",
|
|
52
|
+
"linkName",
|
|
53
|
+
"jointAxis",
|
|
54
|
+
"parentFramePos",
|
|
55
|
+
"parentFrameOrn",
|
|
56
|
+
"parentIndex",
|
|
57
|
+
"numActuatedJoint",
|
|
58
|
+
"actuatedJointName",
|
|
59
|
+
"actuatedJointIndex",
|
|
60
|
+
"actuatedJointLowerLimit",
|
|
61
|
+
"actuatedJointUpperLimit"
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
PYBULLET_JOINT_INFO_KEYS = [
|
|
65
|
+
"jointIndex",
|
|
66
|
+
"jointName",
|
|
67
|
+
"jointType",
|
|
68
|
+
"qIndex",
|
|
69
|
+
"uIndex",
|
|
70
|
+
"flags",
|
|
71
|
+
"jointDamping",
|
|
72
|
+
"jointFriction",
|
|
73
|
+
"jointLowerLimit",
|
|
74
|
+
"jointUpperLimit",
|
|
75
|
+
"jointMaxForce",
|
|
76
|
+
"jointMaxVelocity",
|
|
77
|
+
"linkName",
|
|
78
|
+
"jointAxis",
|
|
79
|
+
"parentFramePos",
|
|
80
|
+
"parentFrameOrn",
|
|
81
|
+
"parentIndex",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
numJoints = pb.getNumJoints(objectId, physicsClientId=cid)
|
|
85
|
+
jointInfoValues = [[] for _ in range(len(PYBULLET_JOINT_INFO_KEYS))]
|
|
86
|
+
for i in range(numJoints):
|
|
87
|
+
jointInfoTuple = pb.getJointInfo(objectId, i, physicsClientId=cid)
|
|
88
|
+
for jointInfoValue, jointInfoItem in zip(jointInfoValues, jointInfoTuple):
|
|
89
|
+
if isinstance(jointInfoItem, bytes):
|
|
90
|
+
jointInfoItem = jointInfoItem.decode()
|
|
91
|
+
jointInfoValue.append(jointInfoItem)
|
|
92
|
+
jointInfo = dict(zip(PYBULLET_JOINT_INFO_KEYS, jointInfoValues))
|
|
93
|
+
jointInfo["numJoint"] = numJoints
|
|
94
|
+
actuated_joint_name = []
|
|
95
|
+
actuated_joint_index = []
|
|
96
|
+
actuated_joint_lower_limits = []
|
|
97
|
+
actuated_joint_upper_limits = []
|
|
98
|
+
continuous_joint_name = []
|
|
99
|
+
continuous_joint_index = []
|
|
100
|
+
|
|
101
|
+
for n, name in enumerate(jointInfo["jointName"]):
|
|
102
|
+
jtype = jointInfo["jointType"][n]
|
|
103
|
+
if jtype == pb.JOINT_FIXED:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
j_upper_lim = jointInfo["jointUpperLimit"][n]
|
|
107
|
+
j_lower_lim = jointInfo["jointLowerLimit"][n]
|
|
108
|
+
actuated_joint_index.append(n)
|
|
109
|
+
actuated_joint_name.append(name)
|
|
110
|
+
actuated_joint_lower_limits.append(j_lower_lim)
|
|
111
|
+
actuated_joint_upper_limits.append(j_upper_lim)
|
|
112
|
+
|
|
113
|
+
if jtype == pb.JOINT_REVOLUTE and j_upper_lim == 0 and j_lower_lim == 0:
|
|
114
|
+
continuous_joint_name.append(name)
|
|
115
|
+
continuous_joint_index.append(n)
|
|
116
|
+
|
|
117
|
+
jointInfo["numActuatedJoint"] = len(actuated_joint_name)
|
|
118
|
+
jointInfo["actuatedJointName"] = actuated_joint_name
|
|
119
|
+
jointInfo["actuatedJointIndex"] = actuated_joint_index
|
|
120
|
+
jointInfo["continuousJointName"] = continuous_joint_name
|
|
121
|
+
jointInfo["continuousJointIndex"] = continuous_joint_index
|
|
122
|
+
jointInfo["actuatedJointLowerLimit"] = actuated_joint_lower_limits
|
|
123
|
+
jointInfo["actuatedJointUpperLimit"] = actuated_joint_upper_limits
|
|
124
|
+
|
|
125
|
+
if verbose:
|
|
126
|
+
print("jointInfo:")
|
|
127
|
+
pprint(jointInfo)
|
|
128
|
+
return jointInfo
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_pybullet_object_link_info(
|
|
132
|
+
objectId: int, cid: int = 0, verbose: bool = False
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
"""Get dictionarised version of link information from links of a pybullet object.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
objectId (int): Pybullet object ID
|
|
138
|
+
cid (int, optional): Pybullet physics client ID. Defaults to 0.
|
|
139
|
+
verbose (bool, optional): Verbosity flag. Defaults to False.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dict[str, Any]: Key-value pairs of joint info.
|
|
143
|
+
Keys:
|
|
144
|
+
"mass",
|
|
145
|
+
"lateralFriction",
|
|
146
|
+
"localInertiaDiagonal",
|
|
147
|
+
"localInertialPos",
|
|
148
|
+
"localInertialOrn",
|
|
149
|
+
"restitution",
|
|
150
|
+
"rollingFriction",
|
|
151
|
+
"spinningFriction",
|
|
152
|
+
"contactDamping",
|
|
153
|
+
"contactStiffness",
|
|
154
|
+
"bodyType",
|
|
155
|
+
"collisionMargin",
|
|
156
|
+
"index",
|
|
157
|
+
"name"
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
PYBULLET_LINK_INFO_KEYS = [
|
|
161
|
+
"mass",
|
|
162
|
+
"lateralFriction",
|
|
163
|
+
"localInertiaDiagonal",
|
|
164
|
+
"localInertialPos",
|
|
165
|
+
"localInertialOrn",
|
|
166
|
+
"restitution",
|
|
167
|
+
"rollingFriction",
|
|
168
|
+
"spinningFriction",
|
|
169
|
+
"contactDamping",
|
|
170
|
+
"contactStiffness",
|
|
171
|
+
"bodyType",
|
|
172
|
+
"collisionMargin",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
numJoints = pb.getNumJoints(objectId, physicsClientId=cid)
|
|
176
|
+
baseName = pb.getBodyInfo(objectId, physicsClientId=cid)[0].decode()
|
|
177
|
+
|
|
178
|
+
linkIndexes = [-1] + list(range(numJoints))
|
|
179
|
+
linkNames = [baseName]
|
|
180
|
+
for i in range(numJoints):
|
|
181
|
+
jointInfoTuple = pb.getJointInfo(objectId, i, physicsClientId=cid)
|
|
182
|
+
linkNames.append(jointInfoTuple[12].decode())
|
|
183
|
+
|
|
184
|
+
linkInfoValues = [[] for _ in range(len(PYBULLET_LINK_INFO_KEYS))]
|
|
185
|
+
for linkIndex in linkIndexes:
|
|
186
|
+
linkInfoTuple = pb.getDynamicsInfo(objectId, linkIndex, physicsClientId=cid)
|
|
187
|
+
for linkInfoValue, linkInfoIterm in zip(linkInfoValues, linkInfoTuple):
|
|
188
|
+
linkInfoValue.append(linkInfoIterm)
|
|
189
|
+
linkInfo = dict(zip(PYBULLET_LINK_INFO_KEYS, linkInfoValues))
|
|
190
|
+
|
|
191
|
+
linkInfo["index"] = linkIndexes
|
|
192
|
+
linkInfo["name"] = linkNames
|
|
193
|
+
|
|
194
|
+
if verbose:
|
|
195
|
+
print("linkInfo:")
|
|
196
|
+
pprint(linkInfo)
|
|
197
|
+
|
|
198
|
+
return linkInfo
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_pybullet_object_info(
|
|
202
|
+
objectId: int, cid: int = 0, verbose: bool = True
|
|
203
|
+
) -> Dict[str, Any]:
|
|
204
|
+
"""Get dictionarised version of information about a pybullet object.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
objectId (int): Pybullet object ID
|
|
208
|
+
cid (int, optional): Pybullet physics client ID. Defaults to 0.
|
|
209
|
+
verbose (bool, optional): Verbosity flag. Defaults to False.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dict[str, Any]: Key-value pairs of joint info.
|
|
213
|
+
Keys:
|
|
214
|
+
"objectId",
|
|
215
|
+
"objectName",
|
|
216
|
+
"objectMass",
|
|
217
|
+
"baseName",
|
|
218
|
+
"jointInfo",
|
|
219
|
+
"linkInfo",
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
objectName = pb.getBodyInfo(objectId, physicsClientId=cid)[1].decode()
|
|
223
|
+
baseName = pb.getBodyInfo(objectId, physicsClientId=cid)[0].decode()
|
|
224
|
+
jointInfo = get_pybullet_object_joint_info(objectId, cid=cid)
|
|
225
|
+
linkInfo = get_pybullet_object_link_info(objectId, cid=cid)
|
|
226
|
+
objectMass = np.sum(linkInfo["mass"])
|
|
227
|
+
objectInfo = {
|
|
228
|
+
"objectId": objectId,
|
|
229
|
+
"objectName": objectName,
|
|
230
|
+
"objectMass": objectMass,
|
|
231
|
+
"baseName": baseName,
|
|
232
|
+
"jointInfo": jointInfo,
|
|
233
|
+
"linkInfo": linkInfo,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if verbose:
|
|
237
|
+
print("\n" + "*" * 100 + "\nPybullet Info " + "\u2193 " * 20 + "\n" + "*" * 100)
|
|
238
|
+
print("objectInfo:")
|
|
239
|
+
pprint(objectInfo)
|
|
240
|
+
print("*" * 100 + "\nPybullet Info " + "\u2191 " * 20 + "\n" + "*" * 100)
|
|
241
|
+
return objectInfo
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def fix_pb_link_mass_inertia(
|
|
245
|
+
body_id: int,
|
|
246
|
+
cid: int = 0,
|
|
247
|
+
mass: float = 1e-9,
|
|
248
|
+
local_inertia_diagonal: float = (1e-9, 1e-9, 1e-9),
|
|
249
|
+
):
|
|
250
|
+
"""Fix pybullet virtual links.
|
|
251
|
+
|
|
252
|
+
When loading urdf, pybullet assigned default value for link with no inertial (default value mass=1,
|
|
253
|
+
localinertiadiagonal = 1,1,1; identity local inertial frame). This function fixes the link inertial
|
|
254
|
+
values to the given mass and local_inertia_diagonal; small values by default.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
body_id (int): Pybullet object ID
|
|
258
|
+
cid (int, optional): Pybullet physics client ID. Defaults to 0.
|
|
259
|
+
mass (float, optional): Virtual mass to assign to virtual link. Defaults to 1e-9.
|
|
260
|
+
local_inertia_diagonal (float, optional): Values to assign to inertia matrix diagonals for virtual links.
|
|
261
|
+
Defaults to (1e-9, 1e-9, 1e-9).
|
|
262
|
+
"""
|
|
263
|
+
link_ids = [-1] + [-1] + list(range(pb.getNumJoints(body_id, physicsClientId=cid)))
|
|
264
|
+
for link_index in link_ids:
|
|
265
|
+
link_info = pb.getDynamicsInfo(body_id, link_index, physicsClientId=cid)
|
|
266
|
+
if link_info[0] == 1.0 and link_info[2] == (1.0, 1.0, 1.0):
|
|
267
|
+
pb.changeDynamics(
|
|
268
|
+
bodyUniqueId=body_id,
|
|
269
|
+
linkIndex=link_index,
|
|
270
|
+
mass=mass,
|
|
271
|
+
localInertiaDiagonal=local_inertia_diagonal,
|
|
272
|
+
physicsClientId=cid,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class BulletRobot:
|
|
277
|
+
"""Robot interface utility for a generic robot in pybullet."""
|
|
278
|
+
|
|
279
|
+
def __init__(
|
|
280
|
+
self,
|
|
281
|
+
urdf_path: str,
|
|
282
|
+
cid: int = None,
|
|
283
|
+
run_async: bool = True,
|
|
284
|
+
ee_names: List[str] = None,
|
|
285
|
+
default_joint_positions: List[float] = None,
|
|
286
|
+
place_on_ground: bool = False,
|
|
287
|
+
default_base_position: Vector3D = np.zeros(3),
|
|
288
|
+
default_base_orientation: QuatType = np.array([0, 0, 0, 1]),
|
|
289
|
+
enable_torque_mode: bool = False,
|
|
290
|
+
verbose: bool = True,
|
|
291
|
+
fake_feedback: bool = False,
|
|
292
|
+
load_ground_plane: bool = True,
|
|
293
|
+
use_fixed_base: bool = True,
|
|
294
|
+
ghost_mode: bool = False,
|
|
295
|
+
):
|
|
296
|
+
"""Robot interface utility for a generic robot in pybullet.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
urdf_path (str): Path to urdf file of robot.
|
|
300
|
+
cid (int, optional): Physics client id of pybullet physics engine (if already running).
|
|
301
|
+
If None provided, will create a new physics GUI instance. Defaults to None.
|
|
302
|
+
run_async (bool, optional): Whether to run physics in a separate thread. If set to False,
|
|
303
|
+
step() method has to be called in the main thread. Defaults to True.
|
|
304
|
+
ee_names (List[str], optional): List of end-effectors for the robot. Defaults to None.
|
|
305
|
+
default_joint_positions (List[float], optional): Optional starting values for joints.
|
|
306
|
+
Defaults to None. NOTE: These values should be in the order of joints in pybullet.
|
|
307
|
+
place_on_ground (bool): If true, the base position height will automatically be adjusted
|
|
308
|
+
such that the robot is on the ground with the given joint positions. Defaults to False.
|
|
309
|
+
NOTE: This is a naive implementation. It does not actually check if the collision is
|
|
310
|
+
with the ground object only. It can be with any object in the world.
|
|
311
|
+
default_base_position (Vector3D, optional): Default position of the base of the robot
|
|
312
|
+
in the world frame during start-up. Note that the height value (z) is only used if
|
|
313
|
+
'place_on_ground' is set to False. Defaults to np.zeros(3).
|
|
314
|
+
default_base_orientation (QuatType, optional): Default orientation quaternion in the
|
|
315
|
+
world frame for the base of the robot during start-up. Defaults to np.array([0, 0, 0, 1]).
|
|
316
|
+
enable_torque_mode (bool, optional): Flag to enable effort controlling of the robot (control commands
|
|
317
|
+
have to be sent continuously to keep the robot from falling). Defaults to True.
|
|
318
|
+
verbose (bool, optional): Verbosity flag for debugging robot info during construction. Defaults to True.
|
|
319
|
+
fake_feedback (bool, optional): If set to True, `get_robot_states` will return last set joint commands
|
|
320
|
+
and base pose, velocities as the current state. Only for debugging controllers. Defaults to False.
|
|
321
|
+
load_ground_plane (bool, optional): If set to True, will load a ground plane in the XY plane at origin.
|
|
322
|
+
Defaults to True.
|
|
323
|
+
"""
|
|
324
|
+
self.cid = cid
|
|
325
|
+
if self.cid is None:
|
|
326
|
+
self.cid = pb.connect(pb.GUI_SERVER)
|
|
327
|
+
|
|
328
|
+
if load_ground_plane:
|
|
329
|
+
import pybullet_data
|
|
330
|
+
|
|
331
|
+
pb.setAdditionalSearchPath(pybullet_data.getDataPath())
|
|
332
|
+
pb.loadURDF("plane.urdf", physicsClientId=self.cid)
|
|
333
|
+
|
|
334
|
+
self.sync_mode = not run_async
|
|
335
|
+
|
|
336
|
+
self.default_start_pose = [default_base_position, default_base_orientation]
|
|
337
|
+
|
|
338
|
+
self.ee_names = ee_names
|
|
339
|
+
if self.ee_names is None:
|
|
340
|
+
self.ee_names = []
|
|
341
|
+
|
|
342
|
+
self.robot_id = pb.loadURDF(
|
|
343
|
+
urdf_path,
|
|
344
|
+
basePosition=self.default_start_pose[0],
|
|
345
|
+
baseOrientation=self.default_start_pose[1],
|
|
346
|
+
useFixedBase=use_fixed_base,
|
|
347
|
+
physicsClientId=self.cid,
|
|
348
|
+
)
|
|
349
|
+
pb.setGravity(0, 0, -9.81, physicsClientId=self.cid)
|
|
350
|
+
self._state_mutex = Lock()
|
|
351
|
+
|
|
352
|
+
fix_pb_link_mass_inertia(body_id=self.robot_id, cid=self.cid)
|
|
353
|
+
|
|
354
|
+
self._retrieve_robot_info()
|
|
355
|
+
|
|
356
|
+
if verbose:
|
|
357
|
+
self._print_robot_info()
|
|
358
|
+
|
|
359
|
+
self.default_joint_positions = (
|
|
360
|
+
default_joint_positions
|
|
361
|
+
if default_joint_positions is not None
|
|
362
|
+
else np.zeros_like(self.actuated_joint_ids)
|
|
363
|
+
)
|
|
364
|
+
self._fake_feedback = fake_feedback
|
|
365
|
+
self._fake_feedback_values = {
|
|
366
|
+
"base_position": copy.deepcopy(self.default_start_pose[0]),
|
|
367
|
+
"base_orientation": copy.deepcopy(self.default_start_pose[1]),
|
|
368
|
+
"base_linear_velocity": np.zeros(3),
|
|
369
|
+
"base_angular_velocity": np.zeros(3),
|
|
370
|
+
"joint_positions": dict(
|
|
371
|
+
zip(
|
|
372
|
+
self.actuated_joint_ids,
|
|
373
|
+
copy.deepcopy(self.default_joint_positions),
|
|
374
|
+
)
|
|
375
|
+
),
|
|
376
|
+
"joint_velocities": dict(
|
|
377
|
+
zip(
|
|
378
|
+
self.actuated_joint_ids,
|
|
379
|
+
np.zeros(self.num_of_actuated_joints),
|
|
380
|
+
)
|
|
381
|
+
),
|
|
382
|
+
}
|
|
383
|
+
self.reset_joints(
|
|
384
|
+
joint_ids=self.actuated_joint_ids,
|
|
385
|
+
joint_positions=self.default_joint_positions,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
self.urdf_path = urdf_path
|
|
389
|
+
self._in_torque_mode = False
|
|
390
|
+
|
|
391
|
+
if place_on_ground:
|
|
392
|
+
self._place_robot_on_ground()
|
|
393
|
+
|
|
394
|
+
if enable_torque_mode:
|
|
395
|
+
self.set_torque_control_mode()
|
|
396
|
+
|
|
397
|
+
self._prev_torque_cmds = dict(
|
|
398
|
+
zip(self.actuated_joint_ids, np.zeros(self.num_of_actuated_joints))
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
pb.setRealTimeSimulation(0 if self.sync_mode else 1, physicsClientId=self.cid)
|
|
402
|
+
|
|
403
|
+
self._ghost_mode = ghost_mode
|
|
404
|
+
rgba = (0, 0, 0, 0.3)
|
|
405
|
+
if self._ghost_mode:
|
|
406
|
+
for link_id in self.link_ids:
|
|
407
|
+
if rgba is not None:
|
|
408
|
+
pb.changeVisualShape(
|
|
409
|
+
self.robot_id,
|
|
410
|
+
link_id,
|
|
411
|
+
rgbaColor=rgba,
|
|
412
|
+
physicsClientId=self.cid,
|
|
413
|
+
)
|
|
414
|
+
pb.setCollisionFilterGroupMask(
|
|
415
|
+
self.robot_id, link_id, 0, 0, physicsClientId=self.cid
|
|
416
|
+
)
|
|
417
|
+
pb.changeDynamics(
|
|
418
|
+
self.robot_id,
|
|
419
|
+
link_id,
|
|
420
|
+
mass=0, # non-dynamic objects have mass 0
|
|
421
|
+
lateralFriction=0,
|
|
422
|
+
spinningFriction=0,
|
|
423
|
+
rollingFriction=0,
|
|
424
|
+
restitution=0,
|
|
425
|
+
linearDamping=0,
|
|
426
|
+
angularDamping=0,
|
|
427
|
+
contactStiffness=0,
|
|
428
|
+
contactDamping=0,
|
|
429
|
+
jointDamping=0,
|
|
430
|
+
maxJointVelocity=0,
|
|
431
|
+
physicsClientId=self.cid,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def _retrieve_robot_info(self):
|
|
435
|
+
self.objectInfo = get_pybullet_object_info(
|
|
436
|
+
self.robot_id, cid=self.cid, verbose=False
|
|
437
|
+
)
|
|
438
|
+
self.name = self.objectInfo["objectName"]
|
|
439
|
+
self.mass = self.objectInfo["objectMass"]
|
|
440
|
+
self.base_name = self.objectInfo["baseName"]
|
|
441
|
+
self.link_names = self.objectInfo["linkInfo"]["name"]
|
|
442
|
+
self.link_ids = self.objectInfo["linkInfo"]["index"]
|
|
443
|
+
self.link_masses = self.objectInfo["linkInfo"]["mass"]
|
|
444
|
+
self.num_joints = self.objectInfo["jointInfo"]["numJoint"]
|
|
445
|
+
self.joint_names = self.objectInfo["jointInfo"]["jointName"]
|
|
446
|
+
self.joint_ids = self.objectInfo["jointInfo"]["jointIndex"]
|
|
447
|
+
self.joint_dampings = self.objectInfo["jointInfo"]["jointDamping"]
|
|
448
|
+
self.joint_frictions = self.objectInfo["jointInfo"]["jointFriction"]
|
|
449
|
+
self.joint_lower_limits = self.objectInfo["jointInfo"]["jointLowerLimit"]
|
|
450
|
+
self.joint_upper_limits = self.objectInfo["jointInfo"]["jointUpperLimit"]
|
|
451
|
+
self.joint_neutral_positions = (
|
|
452
|
+
np.array(self.joint_lower_limits) + np.array(self.joint_upper_limits)
|
|
453
|
+
) / 2
|
|
454
|
+
self.num_of_actuated_joints = self.objectInfo["jointInfo"]["numActuatedJoint"]
|
|
455
|
+
self.actuated_joint_names = self.objectInfo["jointInfo"]["actuatedJointName"]
|
|
456
|
+
self.actuated_joint_ids = self.objectInfo["jointInfo"]["actuatedJointIndex"]
|
|
457
|
+
self.actuated_joint_lower_limits = self.objectInfo["jointInfo"][
|
|
458
|
+
"actuatedJointLowerLimit"
|
|
459
|
+
]
|
|
460
|
+
self.actuated_joint_upper_limits = self.objectInfo["jointInfo"][
|
|
461
|
+
"actuatedJointUpperLimit"
|
|
462
|
+
]
|
|
463
|
+
self.continuous_joint_ids = self.objectInfo["jointInfo"]["continuousJointIndex"]
|
|
464
|
+
self.continuous_joint_names = self.objectInfo["jointInfo"][
|
|
465
|
+
"continuousJointName"
|
|
466
|
+
]
|
|
467
|
+
|
|
468
|
+
self.has_continuous_joints = len(self.continuous_joint_ids) > 0
|
|
469
|
+
|
|
470
|
+
self.actuated_joint_neutral_positions = (
|
|
471
|
+
np.array(self.actuated_joint_lower_limits)
|
|
472
|
+
+ np.array(self.actuated_joint_upper_limits)
|
|
473
|
+
) / 2
|
|
474
|
+
|
|
475
|
+
self.link_name_to_index = dict(zip(self.link_names, self.link_ids))
|
|
476
|
+
self.joint_name_to_index = dict(zip(self.joint_names, self.joint_ids))
|
|
477
|
+
self.ee_ids = [self.link_name_to_index[name] for name in self.ee_names]
|
|
478
|
+
|
|
479
|
+
def refresh_sim_data(self):
|
|
480
|
+
self._retrieve_robot_info()
|
|
481
|
+
|
|
482
|
+
def _print_robot_info(self):
|
|
483
|
+
print("\n")
|
|
484
|
+
print("*" * 100 + "\nSimRobot Info " + "\u2193 " * 20 + "\n" + "*" * 100)
|
|
485
|
+
print("robot ID: ", self.robot_id)
|
|
486
|
+
print("robot name: ", self.name)
|
|
487
|
+
print("robot mass: ", self.mass)
|
|
488
|
+
print("base link name: ", self.base_name)
|
|
489
|
+
print("link names: ", len(self.link_names), self.link_names)
|
|
490
|
+
print("link indexes: ", len(self.link_ids), self.link_ids)
|
|
491
|
+
print("link masses: ", len(self.link_masses), self.link_masses)
|
|
492
|
+
print("end-effectors ", self.ee_names)
|
|
493
|
+
|
|
494
|
+
print("num of joints: ", self.num_joints)
|
|
495
|
+
print("num of actuated joints:", self.num_of_actuated_joints)
|
|
496
|
+
|
|
497
|
+
print("joint names: ", len(self.joint_names), self.joint_names)
|
|
498
|
+
print("joint indexes: ", len(self.joint_ids), self.joint_ids)
|
|
499
|
+
print("joint dampings: ", len(self.joint_dampings), self.joint_dampings)
|
|
500
|
+
print(
|
|
501
|
+
"joint frictions: ", len(self.joint_frictions), self.joint_frictions
|
|
502
|
+
)
|
|
503
|
+
print(
|
|
504
|
+
"joint lower limits: ",
|
|
505
|
+
len(self.joint_lower_limits),
|
|
506
|
+
self.joint_lower_limits,
|
|
507
|
+
)
|
|
508
|
+
print(
|
|
509
|
+
"joint higher limits: ",
|
|
510
|
+
len(self.joint_upper_limits),
|
|
511
|
+
self.joint_upper_limits,
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
print(
|
|
515
|
+
"actuated joint names: ",
|
|
516
|
+
len(self.actuated_joint_names),
|
|
517
|
+
self.actuated_joint_names,
|
|
518
|
+
)
|
|
519
|
+
print(
|
|
520
|
+
"actuated joint indexes:",
|
|
521
|
+
len(self.actuated_joint_ids),
|
|
522
|
+
self.actuated_joint_ids,
|
|
523
|
+
)
|
|
524
|
+
print(
|
|
525
|
+
"continuous joint names: ",
|
|
526
|
+
len(self.continuous_joint_names),
|
|
527
|
+
self.continuous_joint_names,
|
|
528
|
+
)
|
|
529
|
+
print(
|
|
530
|
+
"continuous joint indexes:",
|
|
531
|
+
len(self.continuous_joint_ids),
|
|
532
|
+
self.continuous_joint_ids,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
print("*" * 100 + "\nSimRobot Info " + "\u2191 " * 20 + "\n" + "*" * 100)
|
|
536
|
+
print("\n")
|
|
537
|
+
|
|
538
|
+
def step(self):
|
|
539
|
+
"""Step simulation (if this object was created with `async=False` argument)."""
|
|
540
|
+
if self.sync_mode:
|
|
541
|
+
pb.stepSimulation(physicsClientId=self.cid)
|
|
542
|
+
|
|
543
|
+
def shutdown(self):
|
|
544
|
+
try:
|
|
545
|
+
pb.disconnect(physicsClientId=self.cid)
|
|
546
|
+
except (TypeError, pb.error):
|
|
547
|
+
# raised when pb already dead
|
|
548
|
+
pass
|
|
549
|
+
|
|
550
|
+
def toggle_ft_sensor_for_joints(self, joint_ids: List[int], enable: bool = True):
|
|
551
|
+
if isinstance(joint_ids, int):
|
|
552
|
+
joint_ids = [joint_ids]
|
|
553
|
+
for jid in joint_ids:
|
|
554
|
+
pb.enableJointForceTorqueSensor(
|
|
555
|
+
self.robot_id, jid, enable, physicsClientId=self.cid
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def set_robot_transparency(self, alpha: float):
|
|
559
|
+
vis_data = pb.getVisualShapeData(self.robot_id, physicsClientId=self.cid)
|
|
560
|
+
for data in vis_data:
|
|
561
|
+
pb.changeVisualShape(
|
|
562
|
+
self.robot_id,
|
|
563
|
+
data[1],
|
|
564
|
+
rgbaColor=(data[7][0], data[7][1], data[7][2], alpha),
|
|
565
|
+
physicsClientId=self.cid,
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
def _place_robot_on_ground(
|
|
569
|
+
self,
|
|
570
|
+
move_resolution: float = 0.01,
|
|
571
|
+
default_position: Vector3D = None,
|
|
572
|
+
default_orientation: QuatType = None,
|
|
573
|
+
):
|
|
574
|
+
pb.setRealTimeSimulation(0)
|
|
575
|
+
in_collision = True
|
|
576
|
+
if default_position is None:
|
|
577
|
+
default_position = [
|
|
578
|
+
self.default_start_pose[0][0],
|
|
579
|
+
self.default_start_pose[0][1],
|
|
580
|
+
-move_resolution,
|
|
581
|
+
]
|
|
582
|
+
if default_orientation is None:
|
|
583
|
+
default_orientation = self.default_start_pose[1]
|
|
584
|
+
|
|
585
|
+
joint_pos = self.get_joint_states(joint_ids=self.actuated_joint_ids)[0].copy()
|
|
586
|
+
# print(f"{self.__class__.__name__}: Trying to place robot on the ground.")
|
|
587
|
+
while in_collision:
|
|
588
|
+
default_position[2] += move_resolution
|
|
589
|
+
self.reset_base_pose(
|
|
590
|
+
position=default_position, orientation=default_orientation
|
|
591
|
+
)
|
|
592
|
+
self.reset_joints(
|
|
593
|
+
joint_ids=self.actuated_joint_ids, joint_positions=joint_pos
|
|
594
|
+
)
|
|
595
|
+
pb.stepSimulation(physicsClientId=self.cid)
|
|
596
|
+
in_collision = np.any(self.get_contact_states_of_links(self.link_ids))
|
|
597
|
+
# print(f"{self.__class__.__name__}: Robot should be just above the ground now.")
|
|
598
|
+
self.default_start_pose[0] = np.array(default_position)
|
|
599
|
+
|
|
600
|
+
def set_position_control_mode(self):
|
|
601
|
+
"""Set control mode to position-based (robot will stay in place if no commands sent)."""
|
|
602
|
+
pb.setJointMotorControlArray(
|
|
603
|
+
self.robot_id,
|
|
604
|
+
self.joint_ids,
|
|
605
|
+
pb.POSITION_CONTROL,
|
|
606
|
+
targetPositions=self.joint_neutral_positions,
|
|
607
|
+
physicsClientId=self.cid,
|
|
608
|
+
)
|
|
609
|
+
self._in_torque_mode = False
|
|
610
|
+
print("SimRobot position control mode enabled!")
|
|
611
|
+
|
|
612
|
+
def get_physics_parameters(self) -> Dict[str, Any]:
|
|
613
|
+
return pb.getPhysicsEngineParameters(physicsClientId=self.cid)
|
|
614
|
+
|
|
615
|
+
def set_torque_control_mode(self):
|
|
616
|
+
"""Set control mode to effort-based (robot will NOT stay in place if no commands sent)."""
|
|
617
|
+
pb.setJointMotorControlArray(
|
|
618
|
+
self.robot_id,
|
|
619
|
+
self.actuated_joint_ids,
|
|
620
|
+
pb.VELOCITY_CONTROL,
|
|
621
|
+
forces=[0.0] * self.num_of_actuated_joints,
|
|
622
|
+
physicsClientId=self.cid,
|
|
623
|
+
)
|
|
624
|
+
self._in_torque_mode = True
|
|
625
|
+
print("SimRobot effort control mode enabled!")
|
|
626
|
+
|
|
627
|
+
def reset_base_pose(
|
|
628
|
+
self,
|
|
629
|
+
position: Vector3D = None,
|
|
630
|
+
orientation: QuatType = None,
|
|
631
|
+
base_linear_velocity: Vector3D = None,
|
|
632
|
+
base_angular_velocity: Vector3D = None,
|
|
633
|
+
):
|
|
634
|
+
"""Reset the robot's base pose to default pose.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
position (Vector3D, optional): If specified, sets robot to this position. Defaults to None.
|
|
638
|
+
orientation (QuatType, optional): If specified, sets robot to this orientation.
|
|
639
|
+
Quaternion order: x,y,z,w. Defaults to None.
|
|
640
|
+
"""
|
|
641
|
+
if position is None:
|
|
642
|
+
position = self.default_start_pose[0]
|
|
643
|
+
if orientation is None:
|
|
644
|
+
orientation = self.default_start_pose[1]
|
|
645
|
+
if base_linear_velocity is None:
|
|
646
|
+
base_linear_velocity = np.zeros(3)
|
|
647
|
+
if base_angular_velocity is None:
|
|
648
|
+
base_angular_velocity = np.zeros(3)
|
|
649
|
+
if self._fake_feedback:
|
|
650
|
+
self._fake_feedback_values["base_position"] = position
|
|
651
|
+
self._fake_feedback_values["base_orientation"] = orientation
|
|
652
|
+
self._fake_feedback_values["base_linear_velocity"] = base_linear_velocity
|
|
653
|
+
self._fake_feedback_values["base_angular_velocity"] = base_angular_velocity
|
|
654
|
+
pb.resetBasePositionAndOrientation(
|
|
655
|
+
self.robot_id, position, orientation, physicsClientId=self.cid
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
def reset_joints(
|
|
659
|
+
self,
|
|
660
|
+
joint_ids: List[int],
|
|
661
|
+
joint_positions: np.ndarray,
|
|
662
|
+
joint_velocities: np.ndarray = None,
|
|
663
|
+
):
|
|
664
|
+
if joint_velocities is None:
|
|
665
|
+
joint_velocities = np.zeros(len(joint_ids))
|
|
666
|
+
for n, jid in enumerate(joint_ids):
|
|
667
|
+
if self._fake_feedback:
|
|
668
|
+
self._fake_feedback_values["joint_positions"][jid] = joint_positions[n]
|
|
669
|
+
self._fake_feedback_values["joint_velocities"][jid] = joint_velocities[
|
|
670
|
+
n
|
|
671
|
+
]
|
|
672
|
+
pb.resetJointState(
|
|
673
|
+
self.robot_id,
|
|
674
|
+
jid,
|
|
675
|
+
joint_positions[n],
|
|
676
|
+
joint_velocities[n],
|
|
677
|
+
physicsClientId=self.cid,
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
def reset_actuated_joint_positions(
|
|
681
|
+
self,
|
|
682
|
+
joint_positions: np.ndarray = None,
|
|
683
|
+
joint_names: List[str] = None,
|
|
684
|
+
joint_velocities: np.ndarray = None,
|
|
685
|
+
):
|
|
686
|
+
"""Reset the joint positions of the robot.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
joint_positions (np.ndarray, optional): If specified, sets joints to these values, otherwise
|
|
690
|
+
uses default. Defaults to None.
|
|
691
|
+
joint_names (List[str], optional): Joint names in the order the position values were given.
|
|
692
|
+
If None provided, uses default order of self.actuated_joint_names.
|
|
693
|
+
"""
|
|
694
|
+
if joint_positions is None:
|
|
695
|
+
joint_positions = self.default_joint_positions
|
|
696
|
+
if joint_names is None:
|
|
697
|
+
joint_names = self.actuated_joint_names
|
|
698
|
+
joint_ids = self.get_joint_ids(joint_names=joint_names)
|
|
699
|
+
self.reset_joints(
|
|
700
|
+
joint_ids=joint_ids,
|
|
701
|
+
joint_positions=joint_positions,
|
|
702
|
+
joint_velocities=joint_velocities,
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
def get_object_id(self) -> int:
|
|
706
|
+
return self.robot_id
|
|
707
|
+
|
|
708
|
+
def get_base_link_name(self) -> str:
|
|
709
|
+
return pb.getBodyInfo(self.robot_id, physicsClientId=self.cid)[0].decode()
|
|
710
|
+
|
|
711
|
+
def get_joint_name(self, joint_id: int) -> str:
|
|
712
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[
|
|
713
|
+
1
|
|
714
|
+
].decode()
|
|
715
|
+
|
|
716
|
+
def get_joint_id(self, joint_name: str) -> int:
|
|
717
|
+
return self.joint_name_to_index[joint_name]
|
|
718
|
+
|
|
719
|
+
def get_joint_ids(self, joint_names: List[str]) -> List[int]:
|
|
720
|
+
return [self.joint_name_to_index[joint_name] for joint_name in joint_names]
|
|
721
|
+
|
|
722
|
+
def get_link_name(self, link_id: int) -> str:
|
|
723
|
+
if link_id == -1:
|
|
724
|
+
return self.get_base_link_name()
|
|
725
|
+
else:
|
|
726
|
+
return pb.getJointInfo(self.robot_id, link_id, physicsClientId=self.cid)[
|
|
727
|
+
12
|
|
728
|
+
].decode()
|
|
729
|
+
|
|
730
|
+
def get_link_index(self, link_name: str) -> int:
|
|
731
|
+
return self.link_name_to_index[link_name]
|
|
732
|
+
|
|
733
|
+
def get_joint_type(self, joint_id: int) -> str:
|
|
734
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[2]
|
|
735
|
+
|
|
736
|
+
def get_joint_damping(self, joint_id: int) -> float:
|
|
737
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[6]
|
|
738
|
+
|
|
739
|
+
def get_joint_friction(self, joint_id: int) -> float:
|
|
740
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[7]
|
|
741
|
+
|
|
742
|
+
def get_joint_lower_limit_position(self, joint_id: int) -> float:
|
|
743
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[8]
|
|
744
|
+
|
|
745
|
+
def get_joint_upper_limit_position(self, joint_id: int) -> float:
|
|
746
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[9]
|
|
747
|
+
|
|
748
|
+
def get_joint_max_force(self, joint_id: int) -> float:
|
|
749
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[10]
|
|
750
|
+
|
|
751
|
+
def get_joint_max_velocity(self, joint_id: int) -> float:
|
|
752
|
+
return pb.getJointInfo(self.robot_id, joint_id, physicsClientId=self.cid)[11]
|
|
753
|
+
|
|
754
|
+
def get_link_mass(self, link_id: int) -> float:
|
|
755
|
+
return pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[0]
|
|
756
|
+
|
|
757
|
+
def get_link_local_inertial_transform(
|
|
758
|
+
self, link_id: int
|
|
759
|
+
) -> Tuple[np.ndarray, QuatType]:
|
|
760
|
+
return pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[3:5]
|
|
761
|
+
|
|
762
|
+
def get_link_local_inertial_position(self, link_id: int) -> np.ndarray:
|
|
763
|
+
return np.array(
|
|
764
|
+
pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[3]
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
def get_link_local_inertial_quaternion(self, link_id: int) -> QuatType:
|
|
768
|
+
return np.array(
|
|
769
|
+
pb.getDynamicsInfo(self.robot_id, link_id, physicsClientId=self.cid)[4]
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
# Get Base states
|
|
773
|
+
def get_base_local_inertia_transform(self) -> Tuple[Vector3D, QuatType]:
|
|
774
|
+
return self.get_link_local_inertial_transform(-1)
|
|
775
|
+
|
|
776
|
+
def get_base_local_inertia_position(self) -> Vector3D:
|
|
777
|
+
return self.get_link_local_inertial_position(-1)
|
|
778
|
+
|
|
779
|
+
def get_base_local_inertia_quaternion(self) -> QuatType:
|
|
780
|
+
return self.get_link_local_inertial_quaternion(-1)
|
|
781
|
+
|
|
782
|
+
def get_base_com_position(self) -> Vector3D:
|
|
783
|
+
worldTransCom = self.get_base_pose()
|
|
784
|
+
|
|
785
|
+
localTransCom = self.get_base_local_inertia_transform()
|
|
786
|
+
comTransLocal = pb.invertTransform(
|
|
787
|
+
position=localTransCom[0],
|
|
788
|
+
orientation=localTransCom[1],
|
|
789
|
+
physicsClientId=self.cid,
|
|
790
|
+
)
|
|
791
|
+
worldTransLocal = pb.multiplyTransforms(
|
|
792
|
+
positionA=worldTransCom[0],
|
|
793
|
+
orientationA=worldTransCom[1],
|
|
794
|
+
positionB=comTransLocal[0],
|
|
795
|
+
orientationB=comTransLocal[1],
|
|
796
|
+
physicsClientId=self.cid,
|
|
797
|
+
)
|
|
798
|
+
return np.array(worldTransLocal[0])
|
|
799
|
+
|
|
800
|
+
def get_base_pose(self) -> Tuple[Vector3D, QuatType]:
|
|
801
|
+
p, q = pb.getBasePositionAndOrientation(self.robot_id, physicsClientId=self.cid)
|
|
802
|
+
if np.isnan(q[0]):
|
|
803
|
+
# BUG: occasional nans in quaternion retrieved from pybullet
|
|
804
|
+
# https://github.com/bulletphysics/bullet3/issues/976
|
|
805
|
+
raise RuntimeError("Pybullet pose retrieval returned a nan quaternion")
|
|
806
|
+
return np.array(p), np.array(q)
|
|
807
|
+
|
|
808
|
+
def get_base_velocity(self) -> Tuple[Vector3D, Vector3D]:
|
|
809
|
+
lin, ang = pb.getBaseVelocity(self.robot_id, physicsClientId=self.cid)
|
|
810
|
+
return np.array(lin), np.array(ang)
|
|
811
|
+
|
|
812
|
+
def change_dynamics(self, link_name: str, **kwargs):
|
|
813
|
+
"""Exposes the `pb.changeDynamics` pybullet API for this robot. Also
|
|
814
|
+
updates the robot, link and joint info (accessed via `objectInfo` dict,
|
|
815
|
+
and other exposed properties related to joints and links for this robot)
|
|
816
|
+
after making the change.
|
|
817
|
+
|
|
818
|
+
NOTE: Not to be used in high-speed loops as this operation can be heavy.
|
|
819
|
+
"""
|
|
820
|
+
kwargs.pop("bodyUniqueId", None)
|
|
821
|
+
kwargs.pop("linkIndex", None)
|
|
822
|
+
kwargs.pop("physicsClientId", None)
|
|
823
|
+
pb.changeDynamics(
|
|
824
|
+
bodyUniqueId=self.robot_id,
|
|
825
|
+
linkIndex=self.get_link_index(link_name=link_name),
|
|
826
|
+
physicsClientId=self.cid,
|
|
827
|
+
**kwargs,
|
|
828
|
+
)
|
|
829
|
+
self.refresh_sim_data()
|
|
830
|
+
|
|
831
|
+
# Get actuated joint states
|
|
832
|
+
def get_actuated_joint_positions(
|
|
833
|
+
self, actuated_joint_names: List[str] = None
|
|
834
|
+
) -> np.ndarray:
|
|
835
|
+
if actuated_joint_names is None:
|
|
836
|
+
actuated_joint_names = self.actuated_joint_names
|
|
837
|
+
return self.get_joint_states(
|
|
838
|
+
joint_ids=self.get_joint_ids(actuated_joint_names)
|
|
839
|
+
)[0]
|
|
840
|
+
|
|
841
|
+
def get_actuated_joint_velocities(
|
|
842
|
+
self, actuated_joint_names: List[str] = None
|
|
843
|
+
) -> np.ndarray:
|
|
844
|
+
if actuated_joint_names is None:
|
|
845
|
+
actuated_joint_names = self.actuated_joint_names
|
|
846
|
+
return self.get_joint_states(
|
|
847
|
+
joint_ids=self.get_joint_ids(actuated_joint_names)
|
|
848
|
+
)[1]
|
|
849
|
+
|
|
850
|
+
def get_actuated_joint_torques(
|
|
851
|
+
self, actuated_joint_names: List[str] = None
|
|
852
|
+
) -> np.ndarray:
|
|
853
|
+
if actuated_joint_names is None:
|
|
854
|
+
actuated_joint_names = self.actuated_joint_names
|
|
855
|
+
return self.get_joint_states(
|
|
856
|
+
joint_ids=self.get_joint_ids(actuated_joint_names)
|
|
857
|
+
)[2]
|
|
858
|
+
|
|
859
|
+
def get_actuated_joint_name_to_position(self) -> Dict[str, float]:
|
|
860
|
+
return dict(
|
|
861
|
+
zip(
|
|
862
|
+
self.actuated_joint_names,
|
|
863
|
+
self.get_joint_states(joint_ids=self.actuated_joint_ids)[0].tolist(),
|
|
864
|
+
)
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
def get_actuated_joint_name_to_velocity(self) -> Dict[str, float]:
|
|
868
|
+
return dict(
|
|
869
|
+
zip(
|
|
870
|
+
self.actuated_joint_names,
|
|
871
|
+
self.get_joint_states(joint_ids=self.actuated_joint_ids)[1].tolist(),
|
|
872
|
+
)
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
def get_actuated_joint_name_to_torque(self) -> Dict[str, float]:
|
|
876
|
+
return dict(
|
|
877
|
+
zip(
|
|
878
|
+
self.actuated_joint_names,
|
|
879
|
+
self.get_joint_states(joint_ids=self.actuated_joint_ids)[2].tolist(),
|
|
880
|
+
)
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
def get_link_pose(self, link_id: int) -> Tuple[Vector3D, QuatType]:
|
|
884
|
+
if link_id == -1:
|
|
885
|
+
return self.get_base_pose()
|
|
886
|
+
p, o = pb.getLinkState(self.robot_id, link_id, physicsClientId=self.cid)[4:6]
|
|
887
|
+
return np.array(p), np.array(o)
|
|
888
|
+
|
|
889
|
+
def get_link_com_pose(self, link_id: int) -> Tuple[Vector3D, QuatType]:
|
|
890
|
+
if link_id == -1:
|
|
891
|
+
# NOTE: probably not valid for base
|
|
892
|
+
return self.get_base_com_position(), self.get_base_pose()[1]
|
|
893
|
+
p, o = pb.getLinkState(self.robot_id, link_id, physicsClientId=self.cid)[:2]
|
|
894
|
+
return np.array(p), np.array(o)
|
|
895
|
+
|
|
896
|
+
def get_ee_contact_states(self, ee_names: List[str] = None) -> np.ndarray:
|
|
897
|
+
if ee_names is None:
|
|
898
|
+
ee_names = self.ee_names
|
|
899
|
+
return self.get_contact_states_of_links(
|
|
900
|
+
link_ids=[self.get_link_index(link_name=name) for name in ee_names]
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
def get_contact_states_of_links(self, link_ids: List[int]) -> np.ndarray:
|
|
904
|
+
return np.array(
|
|
905
|
+
[
|
|
906
|
+
int(
|
|
907
|
+
len(
|
|
908
|
+
pb.getContactPoints(
|
|
909
|
+
bodyA=self.robot_id,
|
|
910
|
+
linkIndexA=idx,
|
|
911
|
+
physicsClientId=self.cid,
|
|
912
|
+
)
|
|
913
|
+
)
|
|
914
|
+
> 0
|
|
915
|
+
)
|
|
916
|
+
for idx in link_ids
|
|
917
|
+
]
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
def toggle_self_collision(self, enable: bool):
|
|
921
|
+
for n, l_id in enumerate(self.link_ids):
|
|
922
|
+
if n < len(self.link_ids) - 1:
|
|
923
|
+
for l2_id in self.link_ids[n:]:
|
|
924
|
+
pb.setCollisionFilterPair(
|
|
925
|
+
self.robot_id,
|
|
926
|
+
self.robot_id,
|
|
927
|
+
l_id,
|
|
928
|
+
l2_id,
|
|
929
|
+
int(enable),
|
|
930
|
+
physicsClientId=self.cid,
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
# Get robot states
|
|
934
|
+
def get_robot_states(
|
|
935
|
+
self, actuated_joint_names: List[str] = None, ee_names: List[str] = None
|
|
936
|
+
) -> Dict[str, Any]:
|
|
937
|
+
if actuated_joint_names is None:
|
|
938
|
+
actuated_joint_names = self.actuated_joint_names
|
|
939
|
+
joint_ids = self.actuated_joint_ids
|
|
940
|
+
else:
|
|
941
|
+
joint_ids = self.get_joint_ids(joint_names=actuated_joint_names)
|
|
942
|
+
if ee_names is None:
|
|
943
|
+
ee_ids = self.ee_ids
|
|
944
|
+
ee_names = self.ee_names
|
|
945
|
+
else:
|
|
946
|
+
ee_ids = [self.get_link_index(link_name=name) for name in ee_names]
|
|
947
|
+
|
|
948
|
+
if self._fake_feedback:
|
|
949
|
+
base_pose = [
|
|
950
|
+
self._fake_feedback_values["base_position"],
|
|
951
|
+
self._fake_feedback_values["base_orientation"],
|
|
952
|
+
]
|
|
953
|
+
base_vel = [
|
|
954
|
+
self._fake_feedback_values["base_linear_velocity"],
|
|
955
|
+
self._fake_feedback_values["base_angular_velocity"],
|
|
956
|
+
]
|
|
957
|
+
jpos = np.array(
|
|
958
|
+
[self._fake_feedback_values["joint_positions"][i] for i in joint_ids]
|
|
959
|
+
)
|
|
960
|
+
jvel = np.array(
|
|
961
|
+
[self._fake_feedback_values["joint_velocities"][i] for i in joint_ids]
|
|
962
|
+
)
|
|
963
|
+
jtorque = np.zeros_like(jpos)
|
|
964
|
+
else:
|
|
965
|
+
base_pose = self.get_base_pose()
|
|
966
|
+
base_vel = self.get_base_velocity()
|
|
967
|
+
joint_states = self.get_joint_states(joint_ids=joint_ids)
|
|
968
|
+
jpos = joint_states[0]
|
|
969
|
+
jvel = joint_states[1]
|
|
970
|
+
jtorque = joint_states[2]
|
|
971
|
+
return {
|
|
972
|
+
"base_position": base_pose[0],
|
|
973
|
+
"base_com_position": self.get_base_com_position(),
|
|
974
|
+
"base_quaternion": base_pose[1],
|
|
975
|
+
"base_velocity_linear": base_vel[0],
|
|
976
|
+
"base_velocity_angular": base_vel[1],
|
|
977
|
+
"actuated_joint_positions": jpos,
|
|
978
|
+
"actuated_joint_velocities": jvel,
|
|
979
|
+
"actuated_joint_torques": jtorque,
|
|
980
|
+
"joint_order": actuated_joint_names,
|
|
981
|
+
"ee_order": ee_names,
|
|
982
|
+
"ee_contact_states": self.get_contact_states_of_links(ee_ids),
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
def get_joint_states(
|
|
986
|
+
self, joint_ids: List[int] = None
|
|
987
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
988
|
+
"""Get joint positions, velocities and efforts.
|
|
989
|
+
|
|
990
|
+
Args:
|
|
991
|
+
joint_ids (List[int], Optional): List of joint ids (optional)
|
|
992
|
+
|
|
993
|
+
Returns:
|
|
994
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray]: joint positions, joint velocities, joint efforts
|
|
995
|
+
"""
|
|
996
|
+
if joint_ids is None:
|
|
997
|
+
joint_ids = self.actuated_joint_ids
|
|
998
|
+
dof = len(joint_ids)
|
|
999
|
+
q, v, tau = np.zeros(dof), np.zeros(dof), np.zeros(dof)
|
|
1000
|
+
for n, idx in enumerate(joint_ids):
|
|
1001
|
+
q[n], v[n], _, tau[n] = pb.getJointState(
|
|
1002
|
+
self.robot_id, idx, physicsClientId=self.cid
|
|
1003
|
+
)
|
|
1004
|
+
if idx in self.continuous_joint_ids:
|
|
1005
|
+
q[n] = wrap_angle(q[n])
|
|
1006
|
+
if self._in_torque_mode:
|
|
1007
|
+
tau[n] = self._prev_torque_cmds[idx]
|
|
1008
|
+
return q, v, tau
|
|
1009
|
+
|
|
1010
|
+
def compute_joint_pd_error(
|
|
1011
|
+
self,
|
|
1012
|
+
joint_ids: List[int],
|
|
1013
|
+
q_des: np.ndarray,
|
|
1014
|
+
dq_des: np.ndarray,
|
|
1015
|
+
Kp: float | np.ndarray,
|
|
1016
|
+
Kd: float | np.ndarray,
|
|
1017
|
+
):
|
|
1018
|
+
curr_q, curr_dq, _ = self.get_joint_states(joint_ids=joint_ids)
|
|
1019
|
+
|
|
1020
|
+
p_term = np.zeros_like(curr_q)
|
|
1021
|
+
if q_des is not None:
|
|
1022
|
+
p_term = Kp * (q_des - curr_q)
|
|
1023
|
+
if self.has_continuous_joints:
|
|
1024
|
+
for n, jid in enumerate(joint_ids):
|
|
1025
|
+
if jid in self.continuous_joint_ids:
|
|
1026
|
+
p_term[n] = Kp[n] * wrap_angle(q_des[n] - curr_q[n])
|
|
1027
|
+
|
|
1028
|
+
d_term = np.zeros_like(curr_dq)
|
|
1029
|
+
if dq_des is not None:
|
|
1030
|
+
d_term = Kd * (dq_des - curr_dq)
|
|
1031
|
+
|
|
1032
|
+
return p_term + d_term
|
|
1033
|
+
|
|
1034
|
+
def set_actuated_joint_commands(
|
|
1035
|
+
self,
|
|
1036
|
+
actuated_joint_names: List[str] = None,
|
|
1037
|
+
q: float | np.ndarray = 0,
|
|
1038
|
+
Kp: float | np.ndarray = 0,
|
|
1039
|
+
dq: float | np.ndarray = 0,
|
|
1040
|
+
Kd: float | np.ndarray = 0,
|
|
1041
|
+
tau: float | np.ndarray = 0,
|
|
1042
|
+
):
|
|
1043
|
+
# can be further optimised for speed by storing last used actuated joint names, e.g.
|
|
1044
|
+
# if using in a control loop
|
|
1045
|
+
|
|
1046
|
+
if actuated_joint_names is None:
|
|
1047
|
+
actuated_joint_names = self.actuated_joint_names
|
|
1048
|
+
jids = self.get_joint_ids(actuated_joint_names)
|
|
1049
|
+
|
|
1050
|
+
if self._fake_feedback:
|
|
1051
|
+
self.reset_joints(joint_ids=jids, joint_positions=q, joint_velocities=dq)
|
|
1052
|
+
return
|
|
1053
|
+
|
|
1054
|
+
if self._in_torque_mode:
|
|
1055
|
+
tau_cmd = (
|
|
1056
|
+
self.compute_joint_pd_error(
|
|
1057
|
+
joint_ids=jids, q_des=q, dq_des=dq, Kp=Kp, Kd=Kd
|
|
1058
|
+
)
|
|
1059
|
+
+ tau
|
|
1060
|
+
)
|
|
1061
|
+
pb.setJointMotorControlArray(
|
|
1062
|
+
self.robot_id,
|
|
1063
|
+
jids,
|
|
1064
|
+
pb.TORQUE_CONTROL,
|
|
1065
|
+
forces=tau_cmd,
|
|
1066
|
+
physicsClientId=self.cid,
|
|
1067
|
+
)
|
|
1068
|
+
for n, jid in enumerate(jids):
|
|
1069
|
+
self._prev_torque_cmds[jid] = tau_cmd[n]
|
|
1070
|
+
else:
|
|
1071
|
+
# tau = [self._max_actuator_force] * len(actuated_joint_names)
|
|
1072
|
+
if isinstance(dq, Number):
|
|
1073
|
+
dq = [dq] * len(actuated_joint_names)
|
|
1074
|
+
pb.setJointMotorControlArray(
|
|
1075
|
+
self.robot_id,
|
|
1076
|
+
jids,
|
|
1077
|
+
pb.POSITION_CONTROL,
|
|
1078
|
+
targetPositions=q,
|
|
1079
|
+
targetVelocities=dq,
|
|
1080
|
+
# forces=tau,
|
|
1081
|
+
# positionGains=Kp,
|
|
1082
|
+
# velocityGains=Kd,
|
|
1083
|
+
physicsClientId=self.cid,
|
|
1084
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import logging
|
|
3
|
+
import importlib
|
|
4
|
+
from robot_descriptions._descriptions import DESCRIPTIONS as _AR_DESCRIPTIONS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
AWESOME_ROBOTS: List[str] = _AR_DESCRIPTIONS.keys()
|
|
8
|
+
"""List of all available robots from the Awesome Robots List that can be used."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_robot_description_urdf(
|
|
12
|
+
robot_description_pkg_name: str,
|
|
13
|
+
) -> str:
|
|
14
|
+
"""Get robot urdf for the specified robot description package.
|
|
15
|
+
|
|
16
|
+
The specified package should be from the list of awesome robot descriptions
|
|
17
|
+
(https://github.com/robot-descriptions/robot_descriptions.py/tree/main?tab=readme-ov-file#descriptions).
|
|
18
|
+
|
|
19
|
+
The list of available robot descriptions can also be viewed in the variable `AWESOME_ROBOTS` imported
|
|
20
|
+
from `utils.urdf_utils`.
|
|
21
|
+
|
|
22
|
+
Downloads description package for the specified robot and caches it locally (only needs downloading once).
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
robot_description_pkg_name (str): The package name as specified in the Awesome Robot Descriptions list.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: Path to the robot's urdf file.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
return importlib.import_module(
|
|
32
|
+
f"robot_descriptions.{robot_description_pkg_name}"
|
|
33
|
+
).URDF_PATH
|
|
34
|
+
except AttributeError as e:
|
|
35
|
+
msg = f"URDF has not been provided for robot {robot_description_pkg_name}."
|
|
36
|
+
logging.error(msg)
|
|
37
|
+
raise AttributeError(msg) from e
|
|
38
|
+
except (ModuleNotFoundError, KeyError) as e:
|
|
39
|
+
msg = (
|
|
40
|
+
f"No description package called {robot_description_pkg_name} in"
|
|
41
|
+
" Awesome Robot Descriptions list. Use on of the description names from "
|
|
42
|
+
"https://github.com/robot-descriptions/robot_descriptions.py/tree/main?tab=readme-ov-file#descriptions."
|
|
43
|
+
)
|
|
44
|
+
logging.error(msg)
|
|
45
|
+
raise ModuleNotFoundError(msg) from e
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pybullet-robot
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary:
|
|
5
|
+
Author: JustaGist
|
|
6
|
+
Author-email: mail@saifsidhik.page
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: pybullet (>3.1)
|
|
14
|
+
Requires-Dist: robot-descriptions (>=1.10.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Pybullet Robot
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## TODO:
|
|
21
|
+
|
|
22
|
+
- [ ] IK interface
|
|
23
|
+
- [ ] robot descriptions loader
|
|
24
|
+
- [ ] pybullet data robot loader
|
|
25
|
+
- [ ] doc
|
|
26
|
+
- [ ] tests
|
|
27
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pybullet_robot/__init__.py,sha256=sfi7OVZAsaiIeBS4AftTLN7TdnUQOlMSubBgrdnF-CQ,38
|
|
2
|
+
pybullet_robot/bullet_robot.py,sha256=qs7t1yu2Qb4Yu5VO044TZsbeE6DA9Eheg4C4JCa9xik,41840
|
|
3
|
+
pybullet_robot/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pybullet_robot/utils/urdf_utils.py,sha256=kvOuvI4wj4x2XGFgrlmG2x4-F7w1UjMc0hVDIGMUj9E,1789
|
|
5
|
+
pybullet_robot-0.0.1.dist-info/METADATA,sha256=I_euMtwXQEFTOiYRly5m_ts-NOfwX5l6BtorOwEuT1o,620
|
|
6
|
+
pybullet_robot-0.0.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
7
|
+
pybullet_robot-0.0.1.dist-info/RECORD,,
|