2121"""
2222
2323from typing import Callable
24+ from typing import TYPE_CHECKING
2425from dexsim .types import TransformMask
2526
27+ if TYPE_CHECKING :
28+ from embodichain .lab .sim .objects import Robot
29+
2630
2731def create_gizmo_callback () -> Callable :
2832 """Create a standard gizmo transform callback function.
@@ -44,3 +48,176 @@ def gizmo_transform_callback(node, translation, rotation, flag):
4448 node .set_rotation_rpy (rotation )
4549
4650 return gizmo_transform_callback
51+
52+
53+ def run_gizmo_robot_control_loop (
54+ robot : "Robot" , control_part : str = "arm" , end_link_name : str | None = None
55+ ):
56+ """Run a control loop for testing gizmo controls on a robot.
57+
58+ This function implements a control loop that allows users to manipulate a robot
59+ using gizmo controls with keyboard input for additional commands.
60+
61+ Args:
62+ robot (Robot): The robot to control with the gizmo.
63+ control_part (str, optional): The part of the robot to control. Defaults to "arm".
64+ end_link_name (str | None, optional): The name of the end link for FK calculations. Defaults to None.
65+
66+ Keyboard Controls:
67+ Q/ESC: Exit the control loop
68+ P: Print current robot state (joint positions, end-effector pose)
69+ G: Toggle gizmo visibility
70+ R: Reset robot to initial pose
71+ I: Print control information
72+ """
73+ import select
74+ import sys
75+ import tty
76+ import termios
77+ import time
78+ import numpy as np
79+
80+ np .set_printoptions (precision = 5 , suppress = True )
81+
82+ from embodichain .lab .sim import SimulationManager
83+ from embodichain .lab .sim .objects import Robot
84+ from embodichain .lab .sim .solvers import PinkSolverCfg
85+
86+ from embodichain .utils .logger import log_info , log_warning , log_error
87+
88+ sim = SimulationManager .get_instance ()
89+
90+ # Enter auto-update mode.
91+ sim .set_manual_update (False )
92+
93+ # Replace robot's default solver with PinkSolver for gizmo control.
94+ robot_solver = robot .get_solver (name = control_part )
95+ control_part_link_names = robot .get_control_part_link_names (name = control_part )
96+ end_link_name = (
97+ control_part_link_names [- 1 ] if end_link_name is None else end_link_name
98+ )
99+ pink_solver_cfg = PinkSolverCfg (
100+ urdf_path = robot .cfg .fpath ,
101+ end_link_name = end_link_name ,
102+ root_link_name = robot_solver .root_link_name ,
103+ pos_eps = 1e-2 ,
104+ rot_eps = 5e-2 ,
105+ max_iterations = 300 ,
106+ dt = 0.1 ,
107+ )
108+ robot .init_solver (cfg = {control_part : pink_solver_cfg })
109+
110+ # Enable gizmo for the robot
111+ gizmo = sim .enable_gizmo (uid = robot .uid , control_part = control_part )
112+
113+ # Store initial robot configuration
114+ initial_qpos = robot .get_qpos (name = control_part )
115+
116+ gizmo_visible = True
117+
118+ log_info ("\n === Gizmo Robot Control ===" )
119+ log_info ("Gizmo Controls:" )
120+ log_info (" Use the 3D gizmo to drag and manipulate the robot" )
121+ log_info ("\n Keyboard Controls:" )
122+ log_info (" Q/ESC: Exit control loop" )
123+ log_info (" P: Print current robot state" )
124+ log_info (" G: Toggle gizmo visibility" )
125+ log_info (" R: Reset robot to initial pose" )
126+ log_info (" I: Print this information again" )
127+
128+ # Save terminal settings
129+ old_settings = termios .tcgetattr (sys .stdin )
130+ tty .setcbreak (sys .stdin .fileno ())
131+
132+ def get_key ():
133+ """Non-blocking keyboard input."""
134+ if select .select ([sys .stdin ], [], [], 0 )[0 ]:
135+ return sys .stdin .read (1 )
136+ return None
137+
138+ try :
139+ while True :
140+ time .sleep (0.033 ) # ~30Hz
141+ sim .update_gizmos ()
142+
143+ # Check for keyboard input
144+ key = get_key ()
145+
146+ if key :
147+ # Exit controls
148+ if key in ["q" , "Q" , "\x1b " ]: # Q or ESC
149+ log_info ("Exiting gizmo control loop..." )
150+ sim .disable_gizmo (uid = robot .uid , control_part = control_part )
151+ if robot_solver :
152+ robot .init_solver (
153+ cfg = {control_part : robot_solver .cfg }
154+ ) # Restore original solver
155+ break
156+
157+ # Print robot state
158+ elif key in ["p" , "P" ]:
159+ current_qpos = robot .get_qpos (name = control_part )
160+ eef_pose = robot .compute_fk (name = control_part , qpos = current_qpos )
161+ log_info (f"\n === Robot State ===" )
162+ log_info (f"Control part: { control_part } " )
163+ log_info (f"Joint positions: { current_qpos .squeeze ().tolist ()} " )
164+ log_info (f"End-effector pose:\n { eef_pose .squeeze ().numpy ()} " )
165+
166+ if eef_pose is None :
167+ log_info (
168+ "End-effector pose unavailable: compute_fk returned None "
169+ f"for control part '{ control_part } '."
170+ )
171+ else :
172+ eef_pose_np = eef_pose .detach ().cpu ().numpy ().squeeze ()
173+ log_info (f"End-effector pose:\n { eef_pose_np } " )
174+ elif key in ["g" , "G" ]:
175+ if gizmo_visible :
176+ sim .set_gizmo_visibility (
177+ uid = robot .uid , control_part = control_part , visible = False
178+ )
179+ log_info ("Gizmo hidden" )
180+ gizmo_visible = False
181+ else :
182+ sim .set_gizmo_visibility (
183+ uid = robot .uid , control_part = control_part , visible = True
184+ )
185+ log_info ("Gizmo shown" )
186+ gizmo_visible = True
187+
188+ # Reset to initial pose
189+ elif key in ["r" , "R" ]:
190+ # TODO: Workaround for reset. Gizmo pose should be fixed in the future.
191+ sim .disable_gizmo (uid = robot .uid , control_part = control_part )
192+ robot .clear_dynamics ()
193+ robot .set_qpos (qpos = initial_qpos , name = control_part , target = False )
194+ sim .enable_gizmo (uid = robot .uid , control_part = control_part )
195+ log_info ("Robot reset to initial pose" )
196+
197+ # Print info
198+ elif key in ["i" , "I" ]:
199+ log_info ("\n === Gizmo Robot Control ===" )
200+ log_info ("Gizmo Controls:" )
201+ log_info (" Use the 3D gizmo to drag and manipulate the robot" )
202+ log_info ("\n Keyboard Controls:" )
203+ log_info (" Q/ESC: Exit control loop" )
204+ log_info (" P: Print current robot state" )
205+ log_info (" G: Toggle gizmo visibility" )
206+ log_info (" R: Reset robot to initial pose" )
207+ log_info (" I: Print this information again" )
208+
209+ except KeyboardInterrupt :
210+ sim .disable_gizmo (uid = robot .uid , control_part = control_part )
211+ if robot_solver :
212+ robot .init_solver (
213+ cfg = {control_part : robot_solver .cfg }
214+ ) # Restore original solver
215+ log_info ("\n Control loop interrupted by user (Ctrl+C)" )
216+
217+ finally :
218+ try :
219+ # Restore terminal settings
220+ termios .tcsetattr (sys .stdin , termios .TCSADRAIN , old_settings )
221+ except :
222+ pass
223+ log_info ("Gizmo control loop terminated" )
0 commit comments