Skip to content

Commit 342f284

Browse files
authored
Add gizmo helper tool for robot control (#132)
1 parent 7d22a64 commit 342f284

File tree

2 files changed

+180
-1
lines changed

2 files changed

+180
-1
lines changed

embodichain/lab/sim/sim_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,7 @@ def get_robot_uid_list(self) -> List[str]:
11301130

11311131
def enable_gizmo(
11321132
self, uid: str, control_part: str | None = None, gizmo_cfg: object = None
1133-
) -> None:
1133+
) -> Gizmo:
11341134
"""Enable gizmo control for any simulation object (Robot, RigidObject, Camera, etc.).
11351135
11361136
Args:
@@ -1190,6 +1190,8 @@ def enable_gizmo(
11901190
f"Failed to create gizmo for {object_type} '{uid}' with control_part '{control_part}': {e}"
11911191
)
11921192

1193+
return gizmo
1194+
11931195
def disable_gizmo(self, uid: str, control_part: str | None = None) -> None:
11941196
"""Disable and remove gizmo for a robot.
11951197

embodichain/lab/sim/utility/gizmo_utils.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121
"""
2222

2323
from typing import Callable
24+
from typing import TYPE_CHECKING
2425
from dexsim.types import TransformMask
2526

27+
if TYPE_CHECKING:
28+
from embodichain.lab.sim.objects import Robot
29+
2630

2731
def 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("\nKeyboard 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("\nKeyboard 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("\nControl 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

Comments
 (0)