├── 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 | 
4 | 
5 |
6 | --------------------------------------------------------------------------------
7 |
8 |
9 |
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 |
29 |
30 |
31 | --------------------------------------------------------------------------------
32 | #### Velocity Plot ####
33 |
34 |
35 |
36 |
37 |
38 | --------------------------------------------------------------------------------
39 | #### Acceleration Plot ####
40 |
41 |
42 |
43 |
44 |
45 | --------------------------------------------------------------------------------
46 | #### PID-corrected Torque Plot ####
47 |
48 |
49 |
50 |
51 |
52 | --------------------------------------------------------------------------------
53 | #### PyBullet Simulated Torque Plot ####
54 |
55 |
56 |
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 |
--------------------------------------------------------------------------------