code: def setup_inverse_kinematics(self, urdf_path):
          
  
          # These indices come from the urdf file we're using
          self.effector_right = 27
          self.effector_left = 45
  
          # Use PyBullet to handle inverse kinematics.
          # Set up a connection to the PyBullet simulator.
          p.connect(p.DIRECT)
          p.resetSimulation()
  
          self.ik_robot = p.loadURDF(urdf_path, (0, 0, 0), useFixedBase=1)
  
          # Relevant joints we care about. Many of the joints are fixed and don't count, so
          # we need this second map to use the right ones.
          self.actual = [13, 14, 15, 16, 17, 19, 20, 31, 32, 33, 34, 35, 37, 38]
  
          self.num_joints = p.getNumJoints(self.ik_robot)
          n = p.getNumJoints(self.ik_robot)
          self.rest = []
          self.lower = []
          self.upper = []
          self.ranges = []
          for i in range(n):
              info = p.getJointInfo(self.ik_robot, i)
              # Retrieve lower and upper ranges for each relevant joint
              if info[3] > -1:
                  self.rest.append(p.getJointState(self.ik_robot, i)[0])
                  self.lower.append(info[8])
                  self.upper.append(info[9])
                  self.ranges.append(info[9] - info[8])
  
          # Simulation will update as fast as it can in real time, instead of waiting for
          # step commands like in the non-realtime case.
          p.setRealTimeSimulation(1)
docs: This function is responsible for doing any setup for inverse kinematics.
          Inverse Kinematics maps end effector (EEF) poses to joint angles that
          are necessary to achieve those poses.