Skip to content

Commit 343e8cb

Browse files
committed
implement keybindings
1 parent 9190933 commit 343e8cb

File tree

6 files changed

+388
-621
lines changed

6 files changed

+388
-621
lines changed

examples/keyboard_teleop.py

Lines changed: 110 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -18,167 +18,10 @@
1818
import random
1919

2020
import numpy as np
21-
import pyglet
2221
from scipy.spatial.transform import Rotation as R
23-
from typing_extensions import override
2422

2523
import 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

18326
if __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("\nKeyboard 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("\nPress '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

Comments
 (0)