@@ -220,6 +220,44 @@ def _apply_velocity_to_pybullet(robot_id, v_des, pb_to_pin_joint_vel):
220220 )
221221
222222
223+ def _disable_pybullet_joint_motors (robot_id , pb_to_pin_joint_vel ):
224+ """
225+ Disable Bullet's built-in motors before using explicit torque control.
226+
227+ Args:
228+ robot_id (int): PyBullet body id.
229+ pb_to_pin_joint_vel (Dict[int,int]): Bullet joint id -> Pinocchio velocity index map.
230+ """
231+ for j_id in pb_to_pin_joint_vel :
232+ pb .setJointMotorControl2 (robot_id , j_id , pb .VELOCITY_CONTROL , force = 0.0 )
233+
234+
235+ def _apply_torque_to_pybullet (robot_id , tau , pb_to_pin_joint_vel ):
236+ """
237+ Apply actuator torques to Bullet joints using Pinocchio/TSID ordering.
238+
239+ Args:
240+ robot_id (int): PyBullet body id.
241+ tau (np.ndarray): Actuated joint torques, ordered like Pinocchio velocities after free-flyer.
242+ pb_to_pin_joint_vel (Dict[int,int]): Bullet joint id -> Pinocchio velocity index map.
243+
244+ Notes:
245+ TSID returns only actuated torques, while Pinocchio velocity indices include
246+ the 6D floating base first. The torque index is therefore ``idx_v - 6``.
247+ """
248+ for j_id , v_id in pb_to_pin_joint_vel .items ():
249+ tau_id = v_id - 6
250+ if tau_id < 0 or tau_id >= len (tau ):
251+ continue
252+
253+ torque = float (tau [tau_id ])
254+ joint_max = pb .getJointInfo (robot_id , j_id )[10 ]
255+ if joint_max > 0.0 :
256+ torque = float (np .clip (torque , - joint_max , joint_max ))
257+
258+ pb .setJointMotorControl2 (robot_id , j_id , pb .TORQUE_CONTROL , force = torque )
259+
260+
223261def _reset_pybullet_position (robot_id , q_des , j_to_q_idx ):
224262 """
225263 Hard reset of Bullet joint positions.
@@ -273,6 +311,44 @@ def _get_q_from_pybullet(robot_id, nq, map_joint_idx_to_q_idx):
273311 return q
274312
275313
314+ def _get_v_from_pybullet (robot_id , nv , pb_to_pin_joint_vel ):
315+ """
316+ Read a Pinocchio-style generalized velocity vector from PyBullet.
317+
318+ Args:
319+ robot_id (int): PyBullet body id.
320+ nv (int): size of the generalized velocity vector.
321+ pb_to_pin_joint_vel (Dict[int,int]): Bullet joint id -> Pinocchio velocity index map.
322+
323+ Returns:
324+ np.ndarray: generalized velocity with the free-flyer first, then joints.
325+ """
326+ v = np .zeros (nv )
327+
328+ com_pos , com_quat = pb .getBasePositionAndOrientation (robot_id )
329+ lin_com_world , ang_world = pb .getBaseVelocity (robot_id )
330+
331+ # Convert Bullet's inertial/CoM base velocity to the base_link velocity.
332+ _ , _ , _ , p_li , q_li , * _ = pb .getDynamicsInfo (robot_id , - 1 )
333+ p_ib , q_ib = pb .invertTransform (p_li , q_li )
334+ _ , q_base = pb .multiplyTransforms (com_pos , com_quat , p_ib , q_ib )
335+
336+ base_rot = np .array (pb .getMatrixFromQuaternion (q_base )).reshape (3 , 3 )
337+ com_offset_world = base_rot @ np .array (p_li )
338+ lin_base_world = np .array (lin_com_world ) - np .cross (np .array (ang_world ), com_offset_world )
339+
340+ # Pinocchio free-flyer velocities are expressed in the local base frame.
341+ v [:3 ] = base_rot .T @ lin_base_world
342+ v [3 :6 ] = base_rot .T @ np .array (ang_world )
343+
344+ for j_id , v_id in pb_to_pin_joint_vel .items ():
345+ if v_id < 0 :
346+ continue
347+ v [v_id ] = pb .getJointState (robot_id , j_id )[1 ]
348+
349+ return v
350+
351+
276352def _build_pb_to_pin_joints_map (robot_id , model ):
277353 """
278354 Build Bullet joint id -> Pinocchio q index map using joint names.
@@ -526,6 +602,25 @@ def apply_joints_vel_to_robot(self, v: np.ndarray):
526602 robot_id = self .robot_id , v_des = v , pb_to_pin_joint_vel = self .pb_to_pin_joint_vel
527603 )
528604
605+ def disable_joint_motors (self ):
606+ """
607+ Disable PyBullet's default joint motors before explicit torque control.
608+ """
609+ _disable_pybullet_joint_motors (self .robot_id , self .pb_to_pin_joint_vel )
610+
611+ def apply_joint_torques (self , tau : np .ndarray ):
612+ """
613+ Send joint torques to PyBullet in TSID/Pinocchio actuator ordering.
614+
615+ Parameters
616+ ----------
617+ tau : np.ndarray, shape (nv - 6,)
618+ Actuated joint torques returned by TSID.
619+ """
620+ _apply_torque_to_pybullet (
621+ robot_id = self .robot_id , tau = tau , pb_to_pin_joint_vel = self .pb_to_pin_joint_vel
622+ )
623+
529624 def get_q (self , nq : int ) -> np .ndarray :
530625 """
531626 Read the current configuration vector from PyBullet.
@@ -538,10 +633,26 @@ def get_q(self, nq: int) -> np.ndarray:
538633 Returns
539634 -------
540635 np.ndarray, shape (nq,)
541- Configuration with base pose first (x,y,z, qw, qx,qy,qz), then joints.
636+ Configuration with base pose first (x,y,z, qx,qy,qz,qw ), then joints.
542637 """
543638 return _get_q_from_pybullet (self .robot_id , nq , self .pb_to_pin_joints_pos )
544639
640+ def get_v (self , nv : int ) -> np .ndarray :
641+ """
642+ Read the current generalized velocity vector from PyBullet.
643+
644+ Parameters
645+ ----------
646+ nv : int
647+ Total velocity size expected by Pinocchio.
648+
649+ Returns
650+ -------
651+ np.ndarray, shape (nv,)
652+ Floating-base and joint velocities in Pinocchio ordering.
653+ """
654+ return _get_v_from_pybullet (self .robot_id , nv , self .pb_to_pin_joint_vel )
655+
545656 def update_camera_to_follow_pos (self , x : float , y : float , z : float ):
546657 """
547658 Aim the debug camera at a world position.
0 commit comments