1414; - Roll Right (Rotate around X)
1515u - Reset Scene
1616space - Press to close gripper, release to open gripper
17- esc - Quit
1817"""
1918
20- import threading
2119import argparse
22- import numpy as np
2320import csv
2421import os
2522from datetime import datetime
26- from pynput import keyboard
27- from scipy . spatial . transform import Rotation as R
23+
24+ import numpy as np
2825from huggingface_hub import snapshot_download
2926
3027import genesis as gs
31-
32-
33- class KeyboardDevice :
34- def __init__ (self ):
35- self .pressed_keys = set ()
36- self .lock = threading .Lock ()
37- self .listener = keyboard .Listener (on_press = self .on_press , on_release = self .on_release )
38-
39- def start (self ):
40- self .listener .start ()
41-
42- def stop (self ):
43- self .listener .stop ()
44- self .listener .join ()
45-
46- def on_press (self , key : keyboard .Key ):
47- with self .lock :
48- self .pressed_keys .add (key )
49-
50- def on_release (self , key : keyboard .Key ):
51- with self .lock :
52- self .pressed_keys .discard (key )
53-
54- def get_cmd (self ):
55- return self .pressed_keys
28+ import genesis .utils .geom as gu
29+ from genesis .vis .keybindings import Key , KeyAction , Keybind
5630
5731
5832def build_scene (use_ipc = False , show_viewer = False , enable_ipc_gui = False ):
@@ -109,18 +83,12 @@ def build_scene(use_ipc=False, show_viewer=False, enable_ipc_gui=False):
10983 euler = (0 , 0 , 0 ),
11084 ),
11185 )
112- scene .sim .coupler .set_ipc_link_filter (
113- entity = entities ["robot" ],
114- link_names = ["left_finger" , "right_finger" ],
115- )
116-
117- material = (
118- gs .materials .FEM .Elastic (E = 1.0e4 , nu = 0.45 , rho = 1000.0 , model = "stable_neohookean" )
119- if use_ipc
120- else gs .materials .Rigid ()
121- )
12286
12387 if use_ipc :
88+ scene .sim .coupler .set_ipc_link_filter (
89+ entity = entities ["robot" ],
90+ link_names = ["left_finger" , "right_finger" ],
91+ )
12492 cloth = scene .add_entity (
12593 morph = gs .morphs .Mesh (
12694 file = "meshes/grid20x20.obj" ,
@@ -171,14 +139,15 @@ def build_scene(use_ipc=False, show_viewer=False, enable_ipc_gui=False):
171139 return scene , entities
172140
173141
174- def run_sim (scene , entities , clients , mode = "interactive" , trajectory_file = None ):
142+ def run_sim (scene , entities , add_keybinds , mode = "interactive" , trajectory_file = None ):
175143 robot = entities ["robot" ]
176144 target_entity = entities ["target" ]
145+ is_running = True
177146
178147 robot_init_pos = np .array ([0.5 , 0 , 0.55 ])
179- robot_init_R = R . from_euler ( "y" , np .pi )
148+ robot_init_quat = gu . xyz_to_quat ( np . array ([ 0 , np .pi , 0 ])) # Rotation around Y axis
180149 target_pos = robot_init_pos .copy ()
181- target_R = robot_init_R
150+ target_quat = robot_init_quat . copy ()
182151
183152 n_dofs = robot .n_dofs
184153 motors_dof = np .arange (n_dofs - 2 )
@@ -189,18 +158,63 @@ def run_sim(scene, entities, clients, mode="interactive", trajectory_file=None):
189158 trajectory = []
190159 recording = mode == "record"
191160
161+ # Gripper state (use list for mutability in closures)
162+ gripper_closed = [False ]
163+
164+ # Control parameters
165+ dpos = 0.002
166+ drot = 0.01
167+
192168 def reset_scene ():
193- nonlocal target_pos , target_R
194- target_pos = robot_init_pos .copy ()
195- target_R = robot_init_R
196- target_quat = target_R .as_quat (scalar_first = True )
169+ target_pos [:] = robot_init_pos
170+ target_quat [:] = robot_init_quat
197171 target_entity .set_qpos (np .concatenate ([target_pos , target_quat ]))
198172 q = robot .inverse_kinematics (link = ee_link , pos = target_pos , quat = target_quat )
199173 robot .set_qpos (q [:- 2 ], motors_dof )
200174
201175 # entities["cube"].set_pos((random.uniform(0.2, 0.4), random.uniform(-0.2, 0.2), 0.05))
202176 # entities["cube"].set_quat(R.from_euler("z", random.uniform(0, np.pi * 2)).as_quat(scalar_first=True))
203177
178+ # Register keybindings
179+ if add_keybinds :
180+
181+ def move (dpos_delta : tuple [float , float , float ]):
182+ target_pos [:] += np .array (dpos_delta , dtype = gs .np_float )
183+
184+ def rotate (axis : str , angle : float ):
185+ # Create rotation quaternion for the specified axis
186+ euler = np .zeros (3 )
187+ axis_map = {"x" : 0 , "y" : 1 , "z" : 2 }
188+ euler [axis_map [axis ]] = angle
189+ drot_quat = gu .xyz_to_quat (euler )
190+ target_quat [:] = gu .transform_quat_by_quat (target_quat , drot_quat )
191+
192+ def toggle_gripper (close : bool = True ):
193+ gripper_closed [0 ] = close
194+
195+ def stop ():
196+ nonlocal is_running
197+ is_running = False
198+
199+ scene .viewer .register_keybinds (
200+ Keybind ("move_forward" , Key .UP , KeyAction .HOLD , callback = move , args = ((- dpos , 0 , 0 ),)),
201+ Keybind ("move_backward" , Key .DOWN , KeyAction .HOLD , callback = move , args = ((dpos , 0 , 0 ),)),
202+ Keybind ("move_left" , Key .LEFT , KeyAction .HOLD , callback = move , args = ((0 , - dpos , 0 ),)),
203+ Keybind ("move_right" , Key .RIGHT , KeyAction .HOLD , callback = move , args = ((0 , dpos , 0 ),)),
204+ Keybind ("move_up" , Key .N , KeyAction .HOLD , callback = move , args = ((0 , 0 , dpos ),)),
205+ Keybind ("move_down" , Key .M , KeyAction .HOLD , callback = move , args = ((0 , 0 , - dpos ),)),
206+ Keybind ("yaw_left" , Key .J , KeyAction .HOLD , callback = rotate , args = ("z" , drot )),
207+ Keybind ("yaw_right" , Key .K , KeyAction .HOLD , callback = rotate , args = ("z" , - drot )),
208+ Keybind ("pitch_up" , Key .I , KeyAction .HOLD , callback = rotate , args = ("y" , drot )),
209+ Keybind ("pitch_down" , Key .O , KeyAction .HOLD , callback = rotate , args = ("y" , - drot )),
210+ Keybind ("roll_left" , Key .L , KeyAction .HOLD , callback = rotate , args = ("x" , drot )),
211+ Keybind ("roll_right" , Key .SEMICOLON , KeyAction .HOLD , callback = rotate , args = ("x" , - drot )),
212+ Keybind ("reset_scene" , Key .U , KeyAction .HOLD , callback = reset_scene ),
213+ Keybind ("close_gripper" , Key .SPACE , KeyAction .PRESS , callback = toggle_gripper , args = (True ,)),
214+ Keybind ("open_gripper" , Key .SPACE , KeyAction .RELEASE , callback = toggle_gripper , args = (False ,)),
215+ Keybind ("quit" , Key .ESCAPE , KeyAction .PRESS , callback = stop ),
216+ )
217+
204218 # Load trajectory if in playback mode
205219 if mode == "playback" :
206220 if not trajectory_file or not os .path .exists (trajectory_file ):
@@ -232,7 +246,7 @@ def reset_scene():
232246
233247 print (f"\n Mode: { mode .upper ()} " )
234248 if mode == "record" :
235- print ("Recording trajectory... Press ESC to stop and save. " )
249+ print ("Recording trajectory..." )
236250 elif mode == "playback" :
237251 print ("Playing back trajectory..." )
238252
@@ -248,99 +262,59 @@ def reset_scene():
248262 print ("l/;\t - Roll Left/Right (Rotate around X axis)" )
249263 print ("u\t - Reset Scene" )
250264 print ("space\t - Press to close gripper, release to open gripper" )
251- print ("esc\t - Quit" )
265+ if mode in ["interactive" , "record" ]:
266+ print ("\n Plus all default viewer controls (press 'i' to see them)" )
252267
253268 # reset scene before starting teleoperation
254269 reset_scene ()
255270
256271 # start teleoperation or playback
257- stop = False
258272 step_count = 0
259273
260- while not stop :
261- if mode == "playback" :
262- # Playback mode: replay recorded trajectory
263- if step_count < len (trajectory ):
264- step_data = trajectory [step_count ]
265- target_pos = step_data ["target_pos" ]
266- target_R = R .from_quat (step_data ["target_quat" ])
267- is_close_gripper = step_data ["gripper_closed" ]
268- step_count += 1
269- print (f"\r Playback step: { step_count } /{ len (trajectory )} " , end = "" )
270- # Check if user wants to stop playback
271- pressed_keys = clients ["keyboard" ].pressed_keys .copy ()
272- stop = keyboard .Key .esc in pressed_keys
274+ try :
275+ while is_running :
276+ if mode == "playback" :
277+ # Playback mode: replay recorded trajectory
278+ if step_count < len (trajectory ):
279+ step_data = trajectory [step_count ]
280+ target_pos [:] = step_data ["target_pos" ]
281+ target_quat [:] = step_data ["target_quat" ]
282+ gripper_closed [0 ] = step_data ["gripper_closed" ]
283+ step_count += 1
284+ print (f"\r Playback step: { step_count } /{ len (trajectory )} " , end = "" )
285+ else :
286+ print ("\n Playback finished!" )
287+ break
273288 else :
274- print ("\n Playback finished!" )
275- break
276- else :
277- # Interactive or recording mode
278- pressed_keys = clients ["keyboard" ].pressed_keys .copy ()
279-
280- # reset scene:
281- reset_flag = False
282- reset_flag |= keyboard .KeyCode .from_char ("u" ) in pressed_keys
283- if reset_flag :
284- reset_scene ()
285-
286- # stop teleoperation
287- stop = keyboard .Key .esc in pressed_keys
288-
289- # get ee target pose
290- is_close_gripper = False
291- dpos = 0.002
292- drot = 0.01
293- for key in pressed_keys :
294- if key == keyboard .Key .up :
295- target_pos [0 ] -= dpos
296- elif key == keyboard .Key .down :
297- target_pos [0 ] += dpos
298- elif key == keyboard .Key .right :
299- target_pos [1 ] += dpos
300- elif key == keyboard .Key .left :
301- target_pos [1 ] -= dpos
302- elif key == keyboard .KeyCode .from_char ("n" ):
303- target_pos [2 ] += dpos
304- elif key == keyboard .KeyCode .from_char ("m" ):
305- target_pos [2 ] -= dpos
306- elif key == keyboard .KeyCode .from_char ("j" ):
307- target_R = R .from_euler ("z" , drot ) * target_R
308- elif key == keyboard .KeyCode .from_char ("k" ):
309- target_R = R .from_euler ("z" , - drot ) * target_R
310- elif key == keyboard .KeyCode .from_char ("i" ):
311- target_R = R .from_euler ("y" , drot ) * target_R
312- elif key == keyboard .KeyCode .from_char ("o" ):
313- target_R = R .from_euler ("y" , - drot ) * target_R
314- elif key == keyboard .KeyCode .from_char ("l" ):
315- target_R = R .from_euler ("x" , drot ) * target_R
316- elif key == keyboard .KeyCode .from_char (";" ):
317- target_R = R .from_euler ("x" , - drot ) * target_R
318- elif key == keyboard .Key .space :
319- is_close_gripper = True
320-
321- # Record current state if recording
322- if recording :
323- step_data = {
324- "target_pos" : target_pos .copy (),
325- "target_quat" : target_R .as_quat (), # x,y,z,w format
326- "gripper_closed" : is_close_gripper ,
327- "step" : step_count ,
328- }
329- trajectory .append (step_data )
289+ # Interactive or recording mode
290+ # Movement is handled by keybinding callbacks
291+ # Record current state if recording
292+ if recording :
293+ step_data = {
294+ "target_pos" : target_pos .copy (),
295+ "target_quat" : target_quat .copy (),
296+ "gripper_closed" : gripper_closed [0 ],
297+ "step" : step_count ,
298+ }
299+ trajectory .append (step_data )
300+
301+ # control arm
302+ target_entity .set_qpos (np .concatenate ([target_pos , target_quat ]))
303+ q , err = robot .inverse_kinematics (link = ee_link , pos = target_pos , quat = target_quat , return_error = True )
304+ robot .control_dofs_position (q [:- 2 ], motors_dof )
305+ # control gripper
306+ if gripper_closed [0 ]:
307+ robot .control_dofs_force (np .array ([- 1.0 , - 1.0 ]), fingers_dof )
308+ else :
309+ robot .control_dofs_force (np .array ([1.0 , 1.0 ]), fingers_dof )
330310
331- # control arm
332- target_quat = target_R .as_quat (scalar_first = True )
333- target_entity .set_qpos (np .concatenate ([target_pos , target_quat ]))
334- q , err = robot .inverse_kinematics (link = ee_link , pos = target_pos , quat = target_quat , return_error = True )
335- robot .control_dofs_position (q [:- 2 ], motors_dof )
336- # control gripper
337- if is_close_gripper :
338- robot .control_dofs_force (np .array ([- 1.0 , - 1.0 ]), fingers_dof )
339- else :
340- robot .control_dofs_force (np .array ([1.0 , 1.0 ]), fingers_dof )
311+ scene .step ()
312+ step_count += 1
341313
342- scene .step ()
343- step_count += 1
314+ if "PYTEST_VERSION" in os .environ :
315+ break
316+ except KeyboardInterrupt :
317+ gs .logger .info ("Simulation interrupted, exiting." )
344318
345319 # Save trajectory if recording
346320 if recording and len (trajectory ) > 0 :
@@ -436,12 +410,14 @@ def main():
436410 elif not os .path .isabs (trajectory_file ):
437411 trajectory_file = os .path .join (traj_dir , os .path .basename (trajectory_file ))
438412
439- clients = dict ()
440- clients ["keyboard" ] = KeyboardDevice ()
441- clients ["keyboard" ].start ()
442-
443413 scene , entities = build_scene (use_ipc = args .ipc , show_viewer = args .vis , enable_ipc_gui = False )
444- run_sim (scene , entities , clients , mode = args .mode , trajectory_file = trajectory_file )
414+ run_sim (
415+ scene ,
416+ entities ,
417+ add_keybinds = args .vis or args .mode in ["interactive" , "record" ],
418+ mode = args .mode ,
419+ trajectory_file = trajectory_file ,
420+ )
445421
446422
447423if __name__ == "__main__" :
0 commit comments