# 🎑 查看器交互
Genesis viewer支持鼠标和键盘交互,用于相机控制、录制、可视化切换等功能。
此功能可以通过自定义按键绑定和查看器插件轻松扩展。
## 添加按键绑定
在 `scene.build()` 之前使用 `scene.viewer.register_keybinds(...)` 注册按键绑定。
```python
import genesis.vis.keybindings as kb
...
is_running = True
def stop():
global is_running
is_running = False
scene.viewer.register_keybinds(
kb.Keybind("greetings", kb.Key.G, kb.KeyAction.PRESS, callback=lambda: print("Hello!")),
kb.Keybind("quit", kb.Key.ESCAPE, kb.KeyAction.PRESS, callback=stop),
# 添加任意数量的按键绑定!
)
scene.build()
while is_running:
scene.step()
```
```{figure} ../../_static/images/keybindings_instructions.png
:alt: 查看器覆盖层显示键盘说明,包括插件按键绑定
```
注册的按键绑定会显示在查看器的说明覆盖层中,以便用户一目了然地发现控制方式。
要在创建场景时禁用默认查看器控件和/或隐藏帮助文本,请设置相应的选项以便于使用:
```python
scene = gs.Scene(
viewer_options=gs.options.ViewerOptions(
enable_help_text=False, # 隐藏说明文本
enable_default_keybinds=False, # 禁用默认查看器快捷键
),
)
```
## 查看器插件
查看器插件通过自定义输入处理和可视化来扩展交互式场景查看器。
它们接收鼠标和键盘事件,可以在每帧绘制调试几何体,并在每个模拟步骤运行逻辑——非常适合用于在网格上拾取点或用鼠标拖动刚体等工具。
在 `scene.build()` 之前使用 `scene.viewer.add_plugin(plugin)` 添加插件。
示例脚本可在 `examples/viewer_plugin/` 下找到。
### 鼠标交互插件
**`MouseInteractionPlugin`** 允许您点击并拖动场景中的刚体。
您可以通过直接设置刚体位置(默认)来移动刚体,或施加弹簧力以获得更真实的物理效果。
完整示例脚本可在 `examples/viewer_plugin/mouse_interaction.py` 找到。
使用 `--use_force` 标志启用弹簧力代替位置控制。
```python
scene.viewer.add_plugin(
gs.vis.viewer_plugins.MouseInteractionPlugin(
use_force=True, # False = 设置位置, True = 弹簧力
spring_const=1000.0,
color=(0.1, 0.6, 0.8, 0.6),
)
)
```
### 自定义插件
您可以通过继承 `ViewerPlugin`(如果需要屏幕到世界的光线投射,则继承 `RaycasterViewerPlugin`)来实现自定义插件,并使用 `scene.viewer.add_plugin()` 添加它们。
#### ViewerPlugin 基类
自定义插件继承 `gs.vis.viewer_plugins.ViewerPlugin`。
场景构建后,查看器会调用 `build(viewer, camera, scene)`;请重写此方法以存储引用并设置状态。
事件钩子返回 `EVENT_HANDLED`(或 `True`)表示事件已被消耗,或返回 `None` 以让其他插件或默认查看器处理。
| 方法 | 描述 |
|--------|-------------|
| `on_mouse_motion(x, y, dx, dy)` | 鼠标移动 |
| `on_mouse_drag(x, y, dx, dy, buttons, modifiers)` | 按住按钮拖动鼠标 |
| `on_mouse_press(x, y, buttons, modifiers)` | 鼠标按钮按下(在初始按下时调用一次) |
| `on_mouse_release(x, y, buttons, modifiers)` | 鼠标按钮释放 |
| `on_mouse_scroll(x, y, dx, dy)` | 鼠标滚轮 |
| `on_key_press(key, modifiers)` | 键盘按键按下 |
| `on_key_release(key, modifiers)` | 键盘按键释放 |
| `on_resize(width, height)` | 窗口大小改变 |
| `update_on_sim_step()` | 每次 `scene.step()` 时调用 |
| `on_draw()` | 每帧调用,用于自定义绘制 |
| `on_close()` | 查看器关闭时调用 |
```python
from genesis.vis.viewer_plugins import ViewerPlugin, EVENT_HANDLED, EVENT_HANDLE_STATE
class MyPlugin(ViewerPlugin):
def build(self, viewer, camera, scene):
super().build(viewer, camera, scene)
# self.viewer, self.camera, self.scene 已设置
def on_key_press(self, symbol: int, modifiers: int) -> EVENT_HANDLE_STATE:
if symbol == ord("x"):
# 执行某些操作
return EVENT_HANDLED
return None
def on_draw(self) -> None:
# 例如通过 self.scene.draw_debug_*() 绘制调试几何体
pass
```
#### RaycasterViewerPlugin 和屏幕空间光线
对于点击选择或 3D 拖动行为,您需要从相机穿过鼠标位置的光线。
继承 `RaycasterViewerPlugin` 而不是 `ViewerPlugin`;它维护一个光线投射器并提供 `_screen_position_to_ray(x, y)`,返回世界坐标系中的 `Ray`(原点和方向)。
`RaycasterViewerPlugin` 还会重写 `update_on_sim_step()`,以便光线投射器在每个步骤与场景保持同步。
```python
from genesis.vis.viewer_plugins import RaycasterViewerPlugin, EVENT_HANDLED
class PickerPlugin(RaycasterViewerPlugin):
def on_mouse_press(self, x: int, y: int, button: int, modifiers: int):
if button != 1:
return None
ray = self._screen_position_to_ray(x, y)
hit = self._raycaster.cast(*ray)
if hit is not None and hit.geom:
link = hit.geom.link
world_pos = hit.position
# ...
return EVENT_HANDLED
return None
```
使用 `scene.viewer.register_keybinds()` 注册按键(例如退出、切换模式);这些按键绑定会显示在查看器的键盘说明覆盖层中,以便用户轻松发现它们。
#### 示例:网格点选择器
**`MeshPointSelectorPlugin`** 使用鼠标光线投射来选择刚体网格上的点。
点击可添加或删除点;选中的点显示为球体,可以吸附到网格。
关闭时,插件将选中的点(以链接局部坐标表示)写入 CSV 文件,这在需要获取局部位置以在实体上放置传感器时非常有用。
完整示例脚本可在 `examples/viewer_plugin/mesh_point_selector.py` 找到。
```python
from genesis.vis.viewer_plugins import MeshPointSelectorPlugin
scene.viewer.add_plugin(
MeshPointSelectorPlugin(
sphere_radius=0.004,
sphere_color=(0.1, 0.3, 1.0, 1.0),
hover_color=(0.3, 0.5, 1.0, 1.0),
grid_snap=(-1.0, 0.01, 0.01), # -1 = 该轴不吸附
output_file="selected_points.csv",
)
)
scene.build()
```