├── README.md ├── example_calculate_inverse_dynamics.py ├── example_gravity_compensation.py ├── example_visualize_movement.py ├── illustrations ├── create_dataset_plots.py ├── fep_dataset_acc_plot.svg ├── fep_dataset_pid-corrected-torque_plot.svg ├── fep_dataset_pos_plot.svg ├── fep_dataset_simulated-torque_plot.svg ├── fep_dataset_vel_plot.svg └── visualize_movement_illustration.gif ├── movement_datasets ├── __init__.py ├── fep_state_to_pid-corrected-torque_55s_dataset.csv ├── fep_state_to_simulated-torque_55s_dataset.csv └── read_write_helper.py ├── panda_robot ├── __init__.py ├── model_description │ ├── meshes │ │ ├── collision │ │ │ ├── finger.stl │ │ │ ├── hand.stl │ │ │ ├── link0.stl │ │ │ ├── link1.stl │ │ │ ├── link2.stl │ │ │ ├── link3.stl │ │ │ ├── link4.stl │ │ │ ├── link5.stl │ │ │ ├── link6.stl │ │ │ └── link7.stl │ │ └── visual │ │ │ ├── finger.stl │ │ │ ├── hand.stl │ │ │ ├── link0.stl │ │ │ ├── link1.stl │ │ │ ├── link2.stl │ │ │ ├── link3.stl │ │ │ ├── link4.stl │ │ │ ├── link5.stl │ │ │ ├── link6.stl │ │ │ └── link7.stl │ ├── panda.urdf │ └── panda_with_gripper.urdf └── panda_robot.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | ## PyBullet Simulation of the Franka Emika Panda 7-DOF Robot ## 2 | 3 | ![Python version req](https://img.shields.io/badge/python-3.5%20%7C%203.6%20%7C%203.7-blue) 4 | ![PyBullet version req](https://img.shields.io/badge/PyBullet-v3.0.8-brightgreen) 5 | 6 | -------------------------------------------------------------------------------- 7 | 8 |

9 | FEP PyBullet Movement gif 10 |
Visualization of the Franka Emika Panda in PyBullet Simulation
11 |

12 | 13 | -------------------------------------------------------------------------------- 14 | 15 | Simple simulation environment for the Franka Emika Panda 7-DOF robot. The project also includes the recording of a 55 seconds movement of the Franka Emika Panda with a mounted gripper that aimed to provide a well explored state space dataset as it also recorded the torque values applied by the robots control loop after having been corrected by its internal PID controller. The dataset can be found in 'movement_datasets/fep_state_to_pid-corrected-torque_55s_dataset.csv' and is recorded in radian at a sampling rate of 1000Hz, resulting in 55,000 data points. The dataset is visualized in the plots below. 16 | 17 | Aside from the functionality to visualize a recorded FEP dataset does the project also provide the code to create simulated torque values based on PyBullet's internal inverse dynamics calculation via the Recursive Newton-Euler Algorithm (RNEA) as well as the option to perform gravity compensation in the simulation. PyBullets RNEA algorithm is calculated based on the FEP robot model description that was taken from Franka Emikas official ROS repository (see [here](https://github.com/frankaemika/franka_ros)) and was converted from .xacro to .urdf via ROS. In simulation is it possible to consider the dynamic effects of an end effector (a gripper in this project), though only the torques for the 7 joints of the body are recorded. 18 | 19 | Further restrictions and specifications of the Franka Emika Panda that were considered in the simulation were taken from the official Franka Emika Panda Datasheet (see [here](https://s3-eu-central-1.amazonaws.com/franka-de-uploads/uploads/Datasheet-EN.pdf)). 20 | 21 | Below you will find plots of the recorded robot state over the 55s movement as well as the PID corrected torque and the PyBullet RNEA simulated torque. 22 | 23 | 24 | -------------------------------------------------------------------------------- 25 | #### Position Plot #### 26 | 27 |

28 | FEP dataset position plot 29 |

30 | 31 | -------------------------------------------------------------------------------- 32 | #### Velocity Plot #### 33 | 34 |

35 | FEP dataset velocity plot 36 |

37 | 38 | -------------------------------------------------------------------------------- 39 | #### Acceleration Plot #### 40 | 41 |

42 | FEP dataset acceleration plot 43 |

44 | 45 | -------------------------------------------------------------------------------- 46 | #### PID-corrected Torque Plot #### 47 | 48 |

49 | FEP dataset pid-corrected-torque plot 50 |

51 | 52 | -------------------------------------------------------------------------------- 53 | #### PyBullet Simulated Torque Plot #### 54 | 55 |

56 | FEP dataset simulated-torque plot 57 |

58 | -------------------------------------------------------------------------------- /example_calculate_inverse_dynamics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pybullet as p 3 | import pybullet_data 4 | 5 | from panda_robot import PandaRobot 6 | from movement_datasets import read_fep_dataset, write_fep_dataset 7 | 8 | INCLUDE_GRIPPER = True 9 | DTYPE = 'float64' 10 | SAMPLING_RATE = 1e-3 # 1000Hz sampling rate 11 | FEP_MOVEMENT_DATASET_PATH = "./movement_datasets/fep_state_to_pid-corrected-torque_55s_dataset.csv" 12 | FEP_SIM_OUTPUT_DATASET_PATH = "./movement_datasets/fep_state_to_simulated-torque_55s_dataset.csv" 13 | 14 | 15 | def main(): 16 | """""" 17 | 18 | # Basic Setup of environment 19 | physics_client_id = p.connect(p.DIRECT) 20 | p.setTimeStep(SAMPLING_RATE) 21 | p.setGravity(0, 0, -9.81) 22 | 23 | # Setup plane 24 | p.setAdditionalSearchPath(pybullet_data.getDataPath()) 25 | plane_id = p.loadURDF("plane.urdf") 26 | 27 | # Setup robot 28 | panda_robot = PandaRobot(include_gripper=INCLUDE_GRIPPER) 29 | 30 | # Read FEP movement dataset, setting in context the current state of the robot (the position and acceleration of 31 | # all joints) to the acceleration that is desired in order to follow the intended movement. The actual FEP 32 | # PID-corrected tau values are discared as they are irrelevant since this is solely a simulation and no comparison. 33 | pos, vel, desired_acc, _ = read_fep_dataset(FEP_MOVEMENT_DATASET_PATH, DTYPE) 34 | 35 | # Set up variables for simulation loop 36 | dataset_length = pos.shape[0] 37 | period = 1 / SAMPLING_RATE 38 | counter_seconds = -1 39 | 40 | # Set up variable to collect simulated tau values 41 | simulated_tau = np.ndarray(shape=pos.shape, dtype=DTYPE) 42 | 43 | # start simulation loop 44 | for i in range(dataset_length): 45 | # Print status update every second of the simulation 46 | if i % period == 0: 47 | counter_seconds += 1 48 | print("Passed time in simulation: {:>4} sec".format(counter_seconds)) 49 | 50 | # Select position, velocity and desired acceleration of current datapoint and convert to list as PyBullet does 51 | # not yet support numpy arrays as parameters. Save resulting simulated tau value in previously initialized 52 | # numpy array. 53 | current_pos = pos[i].tolist() 54 | current_vel = vel[i].tolist() 55 | current_desired_acc = desired_acc[i].tolist() 56 | simulated_tau[i] = panda_robot.calculate_inverse_dynamics(current_pos, current_vel, current_desired_acc) 57 | 58 | # Perform simulation step 59 | p.stepSimulation() 60 | 61 | # Exit Simulation 62 | p.disconnect() 63 | print("Simulation end") 64 | 65 | # Save simulated torques alongside their according position, velocity and desired acceleration as a full FEP 66 | # simulated dataset 67 | write_fep_dataset(FEP_SIM_OUTPUT_DATASET_PATH, 68 | datasets=[pos, vel, desired_acc, simulated_tau], 69 | dtype=DTYPE) 70 | 71 | 72 | if __name__ == '__main__': 73 | main() 74 | -------------------------------------------------------------------------------- /example_gravity_compensation.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pybullet as p 3 | import pybullet_data 4 | 5 | from panda_robot import PandaRobot 6 | 7 | INCLUDE_GRIPPER = True 8 | DTYPE = 'float64' 9 | SAMPLING_RATE = 1e-3 # 1000Hz sampling rate 10 | SIM_LENGTH_SEC = 60 11 | 12 | 13 | def main(): 14 | """ 15 | REMARK: 16 | Due to the I assume initial reset the simulation starts out with some velocity in the joints. It will therefore 17 | move in the beginning of the simulation. From there on after the simulation however should be properly gravity 18 | compensating when you move the joints with the mouse, making them stand still or giving them momentum. 19 | """ 20 | 21 | # Basic Setup of environment 22 | physics_client_id = p.connect(p.GUI) 23 | p.setTimeStep(SAMPLING_RATE) 24 | p.setGravity(0, 0, -9.81) 25 | 26 | # Setup plane 27 | p.setAdditionalSearchPath(pybullet_data.getDataPath()) 28 | plane_id = p.loadURDF("plane.urdf") 29 | 30 | # Setup robot 31 | panda_robot = PandaRobot(include_gripper=INCLUDE_GRIPPER) 32 | 33 | # Set up variables for simulation loop 34 | period = 1 / SAMPLING_RATE 35 | counter_seconds = -1 36 | sim_datapoints = int(SIM_LENGTH_SEC * period) 37 | 38 | # start simulation loop 39 | for i in range(sim_datapoints): 40 | # Print status update every second of the simulation 41 | if i % period == 0: 42 | counter_seconds += 1 43 | print("Passed time in simulation: {:>4} sec".format(counter_seconds)) 44 | 45 | # Determine current state (position and velocity) of robot. Set the desired acceleration to 0 in order to only 46 | # compensate for gravity but leave all other movement as is. 47 | pos, vel = panda_robot.get_position_and_velocity() 48 | desired_acc = [0. for _ in pos] 49 | 50 | # Determine proper torque for the desired gravity compensation acceleration and set it in the robot 51 | torques = panda_robot.calculate_inverse_dynamics(pos=pos, vel=vel, desired_acc=desired_acc) 52 | panda_robot.set_torques(torques) 53 | 54 | # Perform simulation step 55 | p.stepSimulation() 56 | time.sleep(SAMPLING_RATE) 57 | 58 | # Exit Simulation 59 | p.disconnect() 60 | print("Simulation end") 61 | 62 | 63 | if __name__ == '__main__': 64 | main() 65 | -------------------------------------------------------------------------------- /example_visualize_movement.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pybullet as p 3 | import pybullet_data 4 | 5 | from panda_robot import PandaRobot 6 | from movement_datasets import read_fep_dataset 7 | 8 | INCLUDE_GRIPPER = True 9 | DTYPE = 'float64' 10 | SAMPLING_RATE = 1e-3 # 1000Hz sampling rate 11 | FEP_MOVEMENT_DATASET_PATH = "./movement_datasets/fep_state_to_pid-corrected-torque_55s_dataset.csv" 12 | 13 | 14 | def main(): 15 | """""" 16 | 17 | # Basic Setup of environment 18 | physics_client_id = p.connect(p.GUI) 19 | p.setTimeStep(SAMPLING_RATE) 20 | p.setGravity(0, 0, -9.81) 21 | 22 | # Setup plane 23 | p.setAdditionalSearchPath(pybullet_data.getDataPath()) 24 | plane_id = p.loadURDF("plane.urdf") 25 | 26 | # Setup robot 27 | panda_robot = PandaRobot(include_gripper=INCLUDE_GRIPPER) 28 | 29 | # Read FEP movement dataset, discarding everything except the joint positions for each sampling point as PyBullet 30 | # can be set to figure joint torques out by itself and only requires desired joint positions. 31 | pos, _, _, _ = read_fep_dataset(FEP_MOVEMENT_DATASET_PATH, DTYPE) 32 | 33 | # Set up variables for simulation loop 34 | dataset_length = pos.shape[0] 35 | period = 1 / SAMPLING_RATE 36 | counter_seconds = -1 37 | 38 | # start simulation loop 39 | for i in range(dataset_length): 40 | # Print status update every second of the simulation 41 | if i % period == 0: 42 | counter_seconds += 1 43 | print("Passed time in simulation: {:>4} sec".format(counter_seconds)) 44 | 45 | # Select data of current position, then convert to list as PyBullet does not yet support numpy arrays as 46 | # parameters 47 | current_pos = pos[i].tolist() 48 | panda_robot.set_target_positions(current_pos) 49 | 50 | # Perform simulation step 51 | p.stepSimulation() 52 | time.sleep(SAMPLING_RATE) 53 | 54 | # Exit Simulation 55 | p.disconnect() 56 | print("Simulation end") 57 | 58 | 59 | if __name__ == '__main__': 60 | main() 61 | -------------------------------------------------------------------------------- /illustrations/create_dataset_plots.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | from movement_datasets import read_fep_dataset 6 | 7 | DTYPE = 'float64' 8 | FEP_REAL_DATASET_PATH = "../movement_datasets/fep_state_to_pid-corrected-torque_55s_dataset.csv" 9 | FEP_SIM_DATASET_PATH = "../movement_datasets/fep_state_to_simulated-torque_55s_dataset.csv" 10 | PLOT_OUTPUT_DIR_PATH = "./" 11 | PLOT_FIGSIZE = (20, 10) 12 | PLOT_DPI = 128 13 | 14 | 15 | def main(): 16 | """""" 17 | # Ensure Plot directory exists 18 | os.makedirs(PLOT_OUTPUT_DIR_PATH, exist_ok=True) 19 | 20 | # Read dataset position, velocity, acceleration and real as well as simulated torque arrays 21 | print("Reading FEP real and simulated datasets...") 22 | pos, vel, acc, real_tau = read_fep_dataset(FEP_REAL_DATASET_PATH, DTYPE) 23 | _, _, _, sim_tau = read_fep_dataset(FEP_SIM_DATASET_PATH, DTYPE) 24 | 25 | # Create plots 26 | print("Creating FEP dataset plots...") 27 | 28 | # Set timesteps to 55000, as 55000 datapoints 29 | timesteps = np.arange(0, 55, 1e-3) 30 | 31 | plt.figure(figsize=PLOT_FIGSIZE) 32 | for i in range(7): 33 | plt.plot(timesteps, pos[:, i], label=f'j{i + 1}', zorder=2) 34 | plt.axhline(0, color='lightgrey', zorder=1) 35 | plt.xlabel('Time [s]', fontsize=20) 36 | plt.xticks(range(0, 60, 5)) 37 | plt.ylabel('q(t)\u1D62 [rad]', fontsize=20) 38 | plt.yticks([-2.5, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5]) 39 | plt.ylim(-2.75, 2.75) 40 | plt.legend(loc=1) 41 | plt.savefig(fname=PLOT_OUTPUT_DIR_PATH + 'fep_dataset_pos_plot.svg', 42 | dpi=PLOT_DPI, 43 | bbox_inches='tight', 44 | format='svg') 45 | 46 | plt.figure(figsize=PLOT_FIGSIZE) 47 | for i in range(7): 48 | plt.plot(timesteps, vel[:, i], label=f'j{i + 1}', zorder=2) 49 | plt.axhline(0, color='lightgrey', zorder=1) 50 | plt.xlabel('Time [s]', fontsize=20) 51 | plt.xticks(range(0, 60, 5)) 52 | plt.ylabel('\u0071\u0307(t)\u1D62 [rad/s]', fontsize=20) 53 | plt.yticks([-2.5, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5]) 54 | plt.ylim(-2.75, 2.75) 55 | plt.legend(loc=1) 56 | plt.savefig(fname=PLOT_OUTPUT_DIR_PATH + 'fep_dataset_vel_plot.svg', 57 | dpi=PLOT_DPI, 58 | bbox_inches='tight', 59 | format='svg') 60 | 61 | plt.figure(figsize=PLOT_FIGSIZE) 62 | for i in range(7): 63 | plt.plot(timesteps, acc[:, i], label=f'j{i + 1}', zorder=2) 64 | plt.axhline(0, color='lightgrey', zorder=1) 65 | plt.xlabel('Time [s]', fontsize=20) 66 | plt.xticks(range(0, 60, 5)) 67 | plt.ylabel('\u0071\u0308(t)\u1D62 [rad/s\u00b2]', fontsize=20) 68 | plt.yticks([-2.5, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5]) 69 | plt.ylim(-2.75, 2.75) 70 | plt.legend(loc=1) 71 | plt.savefig(fname=PLOT_OUTPUT_DIR_PATH + 'fep_dataset_acc_plot.svg', 72 | dpi=PLOT_DPI, 73 | bbox_inches='tight', 74 | format='svg') 75 | 76 | plt.figure(figsize=PLOT_FIGSIZE) 77 | for i in range(7): 78 | plt.plot(timesteps, real_tau[:, i], label=f'j{i + 1}', zorder=2) 79 | plt.axhline(0, color='lightgrey', zorder=1) 80 | plt.xlabel('Time [s]', fontsize=20) 81 | plt.xticks(range(0, 60, 5)) 82 | plt.ylabel('\u03c4(t)\u1D62 [Nm]', fontsize=20) 83 | plt.yticks([-70, -60, -50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70]) 84 | plt.ylim(-78, 78) 85 | plt.legend(loc=1) 86 | plt.savefig(fname=PLOT_OUTPUT_DIR_PATH + 'fep_dataset_pid-corrected-torque_plot.svg', 87 | dpi=PLOT_DPI, 88 | bbox_inches='tight', 89 | format='svg') 90 | 91 | plt.figure(figsize=PLOT_FIGSIZE) 92 | for i in range(7): 93 | plt.plot(timesteps, sim_tau[:, i], label=f'j{i + 1}', zorder=2) 94 | plt.axhline(0, color='lightgrey', zorder=1) 95 | plt.xlabel('Time [s]', fontsize=20) 96 | plt.xticks(range(0, 60, 5)) 97 | plt.ylabel('RNEA \u03c4(t)\u1D62 [Nm]', fontsize=20) 98 | plt.yticks([-70, -60, -50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70]) 99 | plt.ylim(-78, 78) 100 | plt.legend(loc=1) 101 | plt.savefig(fname=PLOT_OUTPUT_DIR_PATH + 'fep_dataset_simulated-torque_plot.svg', 102 | dpi=PLOT_DPI, 103 | bbox_inches='tight', 104 | format='svg') 105 | 106 | print("Creation of FEP dataset plots finished") 107 | 108 | 109 | if __name__ == '__main__': 110 | main() 111 | -------------------------------------------------------------------------------- /illustrations/visualize_movement_illustration.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/illustrations/visualize_movement_illustration.gif -------------------------------------------------------------------------------- /movement_datasets/__init__.py: -------------------------------------------------------------------------------- 1 | from movement_datasets.read_write_helper import read_fep_dataset 2 | from movement_datasets.read_write_helper import write_fep_dataset 3 | -------------------------------------------------------------------------------- /movement_datasets/read_write_helper.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | 5 | def read_fep_dataset(fep_movement_dataset_path, dtype): 6 | """""" 7 | pd_dataset = pd.read_csv(fep_movement_dataset_path, sep=' ', header=None, dtype=dtype) 8 | 9 | print("\nSample of the first 10 lines of the FEP movement dataset:") 10 | print(pd_dataset.head(10)) 11 | 12 | pos = pd_dataset.iloc[:, 0:7].to_numpy(dtype=dtype) 13 | vel = pd_dataset.iloc[:, 7:14].to_numpy(dtype=dtype) 14 | acc = pd_dataset.iloc[:, 14:21].to_numpy(dtype=dtype) 15 | tau = pd_dataset.iloc[:, 21:28].to_numpy(dtype=dtype) 16 | 17 | return pos, vel, acc, tau 18 | 19 | 20 | def write_fep_dataset(fep_sim_output_dataset_path, datasets, dtype): 21 | """""" 22 | np_dataset = np.concatenate(datasets, axis=1, dtype=dtype) 23 | pd_dataset = pd.DataFrame(data=np_dataset, dtype=dtype) 24 | 25 | print("\nSample of the first 10 lines of the created dataset:") 26 | print(pd_dataset.head(10)) 27 | 28 | pd_dataset.to_csv(fep_sim_output_dataset_path, sep=' ', index=False, header=False) 29 | -------------------------------------------------------------------------------- /panda_robot/__init__.py: -------------------------------------------------------------------------------- 1 | from panda_robot.panda_robot import PandaRobot 2 | -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/finger.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/finger.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/hand.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/hand.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link0.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link0.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link1.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link2.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link3.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link3.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link4.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link4.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link5.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link5.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link6.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link6.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/collision/link7.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/collision/link7.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/finger.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/finger.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/hand.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/hand.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link0.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link0.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link1.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link2.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link3.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link3.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link4.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link4.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link5.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link5.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link6.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link6.stl -------------------------------------------------------------------------------- /panda_robot/model_description/meshes/visual/link7.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulPauls/franka_emika_panda_pybullet/7387bcf9bca9730c0a0f2caa6c7b91a39f39fcb1/panda_robot/model_description/meshes/visual/link7.stl -------------------------------------------------------------------------------- /panda_robot/model_description/panda.urdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /panda_robot/model_description/panda_with_gripper.urdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | -------------------------------------------------------------------------------- /panda_robot/panda_robot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import math 3 | import pybullet as p 4 | 5 | 6 | class PandaRobot: 7 | """""" 8 | 9 | def __init__(self, include_gripper): 10 | """""" 11 | p.setAdditionalSearchPath(os.path.dirname(__file__) + '/model_description') 12 | panda_model = "panda_with_gripper.urdf" if include_gripper else "panda.urdf" 13 | self.robot_id = p.loadURDF(panda_model, useFixedBase=True, flags=p.URDF_USE_SELF_COLLISION) 14 | 15 | # Set maximum joint velocity. Maximum joint velocity taken from: 16 | # https://s3-eu-central-1.amazonaws.com/franka-de-uploads/uploads/Datasheet-EN.pdf 17 | p.changeDynamics(bodyUniqueId=self.robot_id, linkIndex=0, maxJointVelocity=150 * (math.pi / 180)) 18 | p.changeDynamics(bodyUniqueId=self.robot_id, linkIndex=1, maxJointVelocity=150 * (math.pi / 180)) 19 | p.changeDynamics(bodyUniqueId=self.robot_id, linkIndex=2, maxJointVelocity=150 * (math.pi / 180)) 20 | p.changeDynamics(bodyUniqueId=self.robot_id, linkIndex=3, maxJointVelocity=150 * (math.pi / 180)) 21 | p.changeDynamics(bodyUniqueId=self.robot_id, linkIndex=4, maxJointVelocity=180 * (math.pi / 180)) 22 | p.changeDynamics(bodyUniqueId=self.robot_id, linkIndex=5, maxJointVelocity=180 * (math.pi / 180)) 23 | p.changeDynamics(bodyUniqueId=self.robot_id, linkIndex=6, maxJointVelocity=180 * (math.pi / 180)) 24 | 25 | # Set DOF according to the fact that either gripper is supplied or not and create often used joint list 26 | self.dof = p.getNumJoints(self.robot_id) - 1 27 | self.joints = range(self.dof) 28 | 29 | # Reset Robot 30 | self.reset_state() 31 | 32 | def reset_state(self): 33 | """""" 34 | for j in range(self.dof): 35 | p.resetJointState(self.robot_id, j, targetValue=0) 36 | p.setJointMotorControlArray(bodyUniqueId=self.robot_id, 37 | jointIndices=self.joints, 38 | controlMode=p.VELOCITY_CONTROL, 39 | forces=[0. for _ in self.joints]) 40 | 41 | def get_dof(self): 42 | """""" 43 | return self.dof 44 | 45 | def get_joint_info(self, j): 46 | """""" 47 | return p.getJointInfo(self.robot_id, j) 48 | 49 | def get_base_position_and_orientation(self): 50 | """""" 51 | return p.getBasePositionAndOrientation(self.robot_id) 52 | 53 | def get_position_and_velocity(self): 54 | """""" 55 | joint_states = p.getJointStates(self.robot_id, self.joints) 56 | joint_pos = [state[0] for state in joint_states] 57 | joint_vel = [state[1] for state in joint_states] 58 | return joint_pos, joint_vel 59 | 60 | def calculate_inverse_kinematics(self, position, orientation): 61 | """""" 62 | return p.calculateInverseKinematics(self.robot_id, self.dof, position, orientation) 63 | 64 | def calculate_inverse_dynamics(self, pos, vel, desired_acc): 65 | """""" 66 | assert len(pos) == len(vel) and len(vel) == len(desired_acc) 67 | vector_length = len(pos) 68 | 69 | # If robot set up with gripper, set those positions, velocities and desired accelerations to 0 70 | if self.dof == 9 and vector_length != 9: 71 | pos = pos + [0., 0.] 72 | vel = vel + [0., 0.] 73 | desired_acc = desired_acc + [0., 0.] 74 | 75 | simulated_torque = list(p.calculateInverseDynamics(self.robot_id, pos, vel, desired_acc)) 76 | 77 | # Remove unnecessary simulated torques for gripper if robot set up with gripper 78 | if self.dof == 9 and vector_length != 9: 79 | simulated_torque = simulated_torque[:7] 80 | return simulated_torque 81 | 82 | def set_target_positions(self, desired_pos): 83 | """""" 84 | # If robot set up with gripper, set those positions to 0 85 | if self.dof == 9: 86 | desired_pos = desired_pos + [0., 0.] 87 | p.setJointMotorControlArray(bodyUniqueId=self.robot_id, 88 | jointIndices=self.joints, 89 | controlMode=p.POSITION_CONTROL, 90 | targetPositions=desired_pos) 91 | 92 | def set_torques(self, desired_torque): 93 | """""" 94 | p.setJointMotorControlArray(bodyUniqueId=self.robot_id, 95 | jointIndices=self.joints, 96 | controlMode=p.TORQUE_CONTROL, 97 | forces=desired_torque) 98 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cycler==0.10.0 2 | kiwisolver==1.3.1 3 | matplotlib==3.3.4 4 | numpy==1.20.1 5 | pandas==1.2.2 6 | Pillow==8.1.0 7 | pybullet==3.0.8 8 | pyparsing==2.4.7 9 | python-dateutil==2.8.1 10 | pytz==2021.1 11 | six==1.15.0 12 | --------------------------------------------------------------------------------