🎑 查看器交互#

Genesis viewer支持鼠标和键盘交互,用于相机控制、录制、可视化切换等功能。 此功能可以通过自定义按键绑定和查看器插件轻松扩展。

添加按键绑定#

scene.build() 之前使用 scene.viewer.register_keybinds(...) 注册按键绑定。

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()
查看器覆盖层显示键盘说明,包括插件按键绑定

注册的按键绑定会显示在查看器的说明覆盖层中,以便用户一目了然地发现控制方式。

要在创建场景时禁用默认查看器控件和/或隐藏帮助文本,请设置相应的选项以便于使用:

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 标志启用弹簧力代替位置控制。

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()

查看器关闭时调用

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(),以便光线投射器在每个步骤与场景保持同步。

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 找到。

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()