1818import random
1919
2020import numpy as np
21- import pyglet
2221from scipy .spatial .transform import Rotation as R
23- from typing_extensions import override
2422
2523import genesis as gs
26- from genesis .ext .pyrender .interaction import register_viewer_plugin
27- from genesis .ext .pyrender .interaction .plugins .viewer_controls import ViewerDefaultControls
28- from genesis .options .viewer_interactions import ViewerDefaultControls as ViewerDefaultControlsOptions
29-
30-
31- class FrankaTeleopOptions (ViewerDefaultControlsOptions ):
32- """Options for Franka teleoperation plugin."""
33-
34- pass
35-
36-
37- @register_viewer_plugin (FrankaTeleopOptions )
38- class FrankaTeleopPlugin (ViewerDefaultControls ):
39- """
40- Viewer plugin for teleoperating Franka robot with keyboard.
41- Extends ViewerDefaultControls to add robot-specific controls.
42- """
43-
44- def __init__ (self , viewer , options = None , camera = None , scene = None , viewport_size = None ):
45- super ().__init__ (viewer , options , camera , scene , viewport_size )
46-
47- # Robot control state
48- self .robot = None
49- self .target_entity = None
50- self .cube_entity = None
51- self .target_pos = None
52- self .target_R = None
53- self .robot_init_pos = np .array ([0.5 , 0 , 0.55 ])
54- self .robot_init_R = R .from_euler ("y" , np .pi )
55-
56- # Control parameters
57- self .dpos = 0.002
58- self .drot = 0.01
59- self .is_close_gripper = False
60-
61- # keybindings
62- self .keybindings .extend (
63- dict (
64- move_forward = pyglet .window .key .UP ,
65- move_backward = pyglet .window .key .DOWN ,
66- move_left = pyglet .window .key .LEFT ,
67- move_right = pyglet .window .key .RIGHT ,
68- move_up = pyglet .window .key .N ,
69- move_down = pyglet .window .key .M ,
70- rotate_ccw = pyglet .window .key .J ,
71- rotate_cw = pyglet .window .key .K ,
72- reset_scene = pyglet .window .key .U ,
73- close_gripper = pyglet .window .key .SPACE ,
74- )
75- )
76- self ._instr_texts = (
77- ["> [i]: show keyboard instructions" ],
78- ["< [i]: hide keyboard instructions" ]
79- + self .keybindings .as_instruction_texts (padding = 3 , exclude = ("toggle_keyboard_instructions" )),
80- )
81-
82- def set_entities (self , robot , target_entity , cube_entity ):
83- """Set references to scene entities."""
84- self .robot = robot
85- self .target_entity = target_entity
86- self .cube_entity = cube_entity
87-
88- # Initialize target pose
89- self .target_pos = self .robot_init_pos .copy ()
90- self .target_R = self .robot_init_R
91-
92- # Get DOF indices
93- n_dofs = robot .n_dofs
94- self .motors_dof = np .arange (n_dofs - 2 )
95- self .fingers_dof = np .arange (n_dofs - 2 , n_dofs )
96- self .ee_link = robot .get_link ("hand" )
97-
98- # Reset to initial pose
99- self ._reset_robot ()
100-
101- def _reset_robot (self ):
102- """Reset robot and cube to initial positions."""
103- if self .robot is None :
104- return
105-
106- self .target_pos = self .robot_init_pos .copy ()
107- self .target_R = self .robot_init_R
108- target_quat = self .target_R .as_quat (scalar_first = True )
109- self .target_entity .set_qpos (np .concatenate ([self .target_pos , target_quat ]))
110- q = self .robot .inverse_kinematics (link = self .ee_link , pos = self .target_pos , quat = target_quat )
111- self .robot .set_qpos (q [:- 2 ], self .motors_dof )
112-
113- # Randomize cube position
114- self .cube_entity .set_pos ((random .uniform (0.2 , 0.4 ), random .uniform (- 0.2 , 0.2 ), 0.05 ))
115- self .cube_entity .set_quat (R .from_euler ("z" , random .uniform (0 , np .pi * 2 )).as_quat (scalar_first = True ))
116-
117- @override
118- def on_key_press (self , symbol : int , modifiers : int ):
119- # First handle default viewer controls
120- result = super ().on_key_press (symbol , modifiers )
121-
122- if self .robot is None :
123- return result
124-
125- # Handle teleoperation controls
126- if symbol == pyglet .window .key .UP :
127- self .target_pos [0 ] -= self .dpos
128- elif symbol == pyglet .window .key .DOWN :
129- self .target_pos [0 ] += self .dpos
130- elif symbol == pyglet .window .key .RIGHT :
131- self .target_pos [1 ] += self .dpos
132- elif symbol == pyglet .window .key .LEFT :
133- self .target_pos [1 ] -= self .dpos
134- elif symbol == pyglet .window .key .N :
135- self .target_pos [2 ] += self .dpos
136- elif symbol == pyglet .window .key .M :
137- self .target_pos [2 ] -= self .dpos
138- elif symbol == pyglet .window .key .J :
139- self .target_R = R .from_euler ("z" , self .drot ) * self .target_R
140- elif symbol == pyglet .window .key .K :
141- self .target_R = R .from_euler ("z" , - self .drot ) * self .target_R
142- elif symbol == pyglet .window .key .U :
143- self ._reset_robot ()
144- elif symbol == pyglet .window .key .SPACE :
145- self .is_close_gripper = True
146-
147- return result
148-
149- @override
150- def on_key_release (self , symbol : int , modifiers : int ):
151- result = super ().on_key_release (symbol , modifiers )
152-
153- if symbol == pyglet .window .key .SPACE :
154- self .is_close_gripper = False
155-
156- return result
157-
158- @override
159- def update_on_sim_step (self ):
160- """Update robot control every simulation step."""
161- super ().update_on_sim_step ()
162-
163- if self .robot is None :
164- return
165-
166- # Update target entity visualization
167- target_quat = self .target_R .as_quat (scalar_first = True )
168- self .target_entity .set_qpos (np .concatenate ([self .target_pos , target_quat ]))
169-
170- # Control arm with inverse kinematics
171- q , err = self .robot .inverse_kinematics (
172- link = self .ee_link , pos = self .target_pos , quat = target_quat , return_error = True
173- )
174- self .robot .control_dofs_position (q [:- 2 ], self .motors_dof )
175-
176- # Control gripper
177- if self .is_close_gripper :
178- self .robot .control_dofs_force (np .array ([- 1.0 , - 1.0 ]), self .fingers_dof )
179- else :
180- self .robot .control_dofs_force (np .array ([1.0 , 1.0 ]), self .fingers_dof )
181-
24+ from genesis .ext .pyrender .interaction .keybindings import KeyAction , Keybind
18225
18326if __name__ == "__main__" :
18427 ########################## init ##########################
@@ -202,7 +45,6 @@ def update_on_sim_step(self):
20245 camera_lookat = (0.2 , 0.0 , 0.1 ),
20346 camera_fov = 50 ,
20447 max_FPS = 60 ,
205- viewer_plugin = FrankaTeleopOptions (),
20648 ),
20749 show_viewer = True ,
20850 show_FPS = False ,
@@ -242,23 +84,114 @@ def update_on_sim_step(self):
24284 ########################## build ##########################
24385 scene .build ()
24486
245- # Set up the teleoperation plugin with entity references
246- teleop_plugin = scene .viewer .viewer_interaction
247- teleop_plugin .set_entities (robot , target , cube )
248-
249- print ("\n Keyboard Controls:" )
250- print ("↑\t - Move Forward (North)" )
251- print ("↓\t - Move Backward (South)" )
252- print ("←\t - Move Left (West)" )
253- print ("→\t - Move Right (East)" )
254- print ("n\t - Move Up" )
255- print ("m\t - Move Down" )
256- print ("j\t - Rotate Counterclockwise" )
257- print ("k\t - Rotate Clockwise" )
258- print ("u\t - Reset Scene" )
259- print ("space\t - Press to close gripper, release to open gripper" )
260- print ("\n Press 'i' in the viewer to see all keyboard controls" )
87+ # Initialize robot control state
88+ robot_init_pos = np .array ([0.5 , 0 , 0.55 ])
89+ robot_init_R = R .from_euler ("y" , np .pi )
90+
91+ # Get DOF indices
92+ n_dofs = robot .n_dofs
93+ motors_dof = np .arange (n_dofs - 2 )
94+ fingers_dof = np .arange (n_dofs - 2 , n_dofs )
95+ ee_link = robot .get_link ("hand" )
96+
97+ # Initialize target pose
98+ target_pos = robot_init_pos .copy ()
99+ target_R = [robot_init_R ] # Use list to make it mutable in closures
100+
101+ # Control parameters
102+ dpos = 0.002
103+ drot = 0.01
104+
105+ # Helper function to reset robot
106+ def reset_robot ():
107+ """Reset robot and cube to initial positions."""
108+ target_pos [:] = robot_init_pos .copy ()
109+ target_R [0 ] = robot_init_R
110+ target_quat = target_R [0 ].as_quat (scalar_first = True )
111+ target .set_qpos (np .concatenate ([target_pos , target_quat ]))
112+ q = robot .inverse_kinematics (link = ee_link , pos = target_pos , quat = target_quat )
113+ robot .set_qpos (q [:- 2 ], motors_dof )
114+
115+ # Randomize cube position
116+ cube .set_pos ((random .uniform (0.2 , 0.4 ), random .uniform (- 0.2 , 0.2 ), 0.05 ))
117+ cube .set_quat (R .from_euler ("z" , random .uniform (0 , np .pi * 2 )).as_quat (scalar_first = True ))
118+
119+ # Initialize robot pose
120+ reset_robot ()
121+
122+ # Robot teleoperation callback functions
123+ def move_forward ():
124+ target_pos [0 ] -= dpos
125+
126+ def move_backward ():
127+ target_pos [0 ] += dpos
128+
129+ def move_left ():
130+ target_pos [1 ] -= dpos
131+
132+ def move_right ():
133+ target_pos [1 ] += dpos
134+
135+ def move_up ():
136+ target_pos [2 ] += dpos
137+
138+ def move_down ():
139+ target_pos [2 ] -= dpos
140+
141+ def rotate_ccw ():
142+ target_R [0 ] = R .from_euler ("z" , drot ) * target_R [0 ]
143+
144+ def rotate_cw ():
145+ target_R [0 ] = R .from_euler ("z" , - drot ) * target_R [0 ]
146+
147+ def close_gripper ():
148+ robot .control_dofs_force (np .array ([- 1.0 , - 1.0 ]), fingers_dof )
149+
150+ def open_gripper ():
151+ robot .control_dofs_force (np .array ([1.0 , 1.0 ]), fingers_dof )
152+
153+ # Register robot teleoperation keybindings
154+ from pyglet .window import key
155+
156+ scene .viewer .register_keybinds (
157+ (
158+ Keybind (key_code = key .UP , key_action = KeyAction .HOLD , name = "move_forward" , callback_func = move_forward ),
159+ Keybind (key_code = key .DOWN , key_action = KeyAction .HOLD , name = "move_backward" , callback_func = move_backward ),
160+ Keybind (key_code = key .LEFT , key_action = KeyAction .HOLD , name = "move_left" , callback_func = move_left ),
161+ Keybind (key_code = key .RIGHT , key_action = KeyAction .HOLD , name = "move_right" , callback_func = move_right ),
162+ Keybind (key_code = key .N , key_action = KeyAction .HOLD , name = "move_up" , callback_func = move_up ),
163+ Keybind (key_code = key .M , key_action = KeyAction .HOLD , name = "move_down" , callback_func = move_down ),
164+ Keybind (key_code = key .J , key_action = KeyAction .HOLD , name = "rotate_ccw" , callback_func = rotate_ccw ),
165+ Keybind (key_code = key .K , key_action = KeyAction .HOLD , name = "rotate_cw" , callback_func = rotate_cw ),
166+ Keybind (key_code = key .U , key_action = KeyAction .HOLD , name = "reset_scene" , callback_func = reset_robot ),
167+ Keybind (
168+ key_code = key .SPACE ,
169+ name = "close_gripper" ,
170+ callback_func = close_gripper ,
171+ key_action = KeyAction .PRESS ,
172+ ),
173+ Keybind (
174+ key_code = key .SPACE ,
175+ name = "open_gripper" ,
176+ callback_func = open_gripper ,
177+ key_action = KeyAction .RELEASE ,
178+ ),
179+ )
180+ )
261181
262182 ########################## run simulation ##########################
263- while True :
264- scene .step ()
183+ try :
184+ while True :
185+ # Update target entity visualization
186+ target_quat = target_R [0 ].as_quat (scalar_first = True )
187+ target .set_qpos (np .concatenate ([target_pos , target_quat ]))
188+
189+ # Control arm with inverse kinematics
190+ q , err = robot .inverse_kinematics (link = ee_link , pos = target_pos , quat = target_quat , return_error = True )
191+ robot .control_dofs_position (q [:- 2 ], motors_dof )
192+
193+ scene .step ()
194+ except KeyboardInterrupt :
195+ gs .logger .info ("Simulation interrupted, exiting." )
196+ finally :
197+ gs .logger .info ("Simulation finished." )
0 commit comments