命名与索引约定#
Genesis World 在其 API 和源码中使用一套规模不大、前后一致的命名规则。本页是其中三条的参考:实体如何被标识(name、uid、idx),索引如何在实体自身的编号与求解器的全局编号之间切换(*_idx_local 与 *_idx),以及你阅读求解器代码时会遇到的 i_* 循环变量与字段命名约定。
标识符回答“这是哪个实体”,索引回答“这是状态数组中的哪个槽位”。两者是互不相关的编号体系,所以把一个当作另一个传入,代码能读也能跑,但会作用到错误的实体上。
标识实体:name、uid 与 idx#
每个实体都带有三个标识符,各司其职:
name: 人类可读的字符串。通过scene.add_entity(..., name=...)传入,否则 Genesis World 会根据 morph 类型加一个 UID 前缀自动生成一个。之后你就用它来查找实体。uid: 创建时分配的全局唯一 ID。它绝不会在实体之间冲突,并在对象的整个生命周期内保持稳定。它在终端日志中以short()形式(7 个字符的前缀)出现。idx: 实体在场景创建顺序中的整数位置。它是一个索引,而不是你应该手写的标识符。它的存在是为了让引擎能够寻址该实体的数据。
命名教程 examples/tutorials/entity_name.py 展示了这三者的用法。可以用名称或短 UID 查找实体:
box = scene.add_entity(gs.morphs.Box(pos=(0, 0, 0.5), size=(0.2, 0.2, 0.2)))
ground = scene.add_entity(gs.morphs.Plane(), name="ground")
scene.get_entity(name="ground") # exact-name lookup
scene.get_entity(uid=box.uid.short()) # short-UID lookup (substring of the full uid)
scene.entity_names # tuple of every entity's name, in creation order
手写查找时优先用 name,把 uid 留给需要区分共享同一生成名称的实体。同一对 name / uid 也用于标识刚体内的连杆和关节。entity.get_link(name=...) 与 entity.get_joint(name=...) 的用法相同。
标识符 |
类型 |
由谁分配 |
用途 |
|---|---|---|---|
|
|
你,或自动生成 |
在场景中查找实体 |
|
UID 对象 |
Genesis World,创建时 |
消除歧义;通过 |
|
|
Genesis World,创建时 |
寻址实体的数据(很少手写) |
局部索引与全局索引#
刚体求解器把每个实体的状态存放在扁平、共享的数组中:每个 dof(自由度)一行、每个连杆一行、每个配置变量 q 一行,依此类推。因此同一个 dof 有两种编号方式:
全局索引(
*_idx): dof 在求解器数组中的绝对行号,跨所有实体计数。双臂场景中,第二条臂的 dof 排在第一条臂的 dof 之后。局部索引(
*_idx_local): dof 在其所属实体内部的位置,总是从零开始。任何实体的第一个 dof 都是局部索引0,与它之前添加过什么无关。
两者的差是一个按实体计算的偏移量。对 dof 来说这个偏移量是 entity.dof_start;实体还暴露 link_start 和 q_start,分别对应连杆数组和 q 数组。关节的 dofs_idx(全局)与 dofs_idx_local(局部)属性正是按这个偏移定义的:
# genesis/engine/entities/rigid_entity/rigid_joint.py
dofs_idx = list(range(dof_start, dof_end))
dofs_idx_local = list(range(dof_start - entity.dof_start, dof_end - entity.dof_start))
由于无论场景中还共享了多少其他实体,局部索引都保持不变,所以控制目标应以它们为基础来构建。控制教程 examples/tutorials/control_your_robot.py 按关节名读取 Franka 的电机 dof,然后以局部索引寻址它们:
motors_dof_idx = [franka.get_joint(name).dofs_idx_local[0] for name in joints_name]
franka.set_dofs_kp(
kp=np.array([4500, 4500, 3500, 3500, 2000, 2000, 2000, 100, 100]),
dofs_idx_local=motors_dof_idx,
)
索引在 API 中的体现#
读取或写入批量状态的实体方法接受局部选择器,并在内部通过 RigidEntity._get_global_idx 将其转换为全局索引,再调用求解器。参数命名为 *_idx_local,以便在调用处明确编号方式:
franka.set_dofs_position(position, dofs_idx_local=motors_dof_idx)
franka.get_links_pos(links_idx_local=[0, 1])
franka.set_qpos(qpos, qs_idx_local=...)
实体方法接受局部索引。
dofs_idx_local、links_idx_local、qs_idx_local。传入None会选择该实体的完整范围。求解器方法接受全局索引。
RigidSolver中对应的方法接受已经解析好的dofs_idx、links_idx等。只有在求解器层面跨实体操作时才使用它们。envs_idx没有局部形式。 环境(env)选择是场景范围的,所以批量方法接受单一的envs_idx,直接按环境索引。批维度的运作方式见 并行仿真。
Note
不带后缀的属性(关节的 dof_start、连杆的 idx、实体的 idx)都是全局的。_local 后缀只在需要选择实体相对编号时才会加上。拿不准时,索引就是全局的。
源码中的字段与循环变量命名#
上述约定会以你在阅读求解器代码时也能看到的名字出现。如果你只使用公共 API,读到这里就可以停下;其余内容是为 genesis/engine/solvers/ 下的源码准备的路线图。
数据字段遵循 <representation>_<computation-type>。 representation 总是复数形式(dofs、links、particles、elements),computation-type 后缀标明字段的用途:
*_state: 每次求解器步进都会更新的动态状态(例如dofs_state、links_state)。*_info: 在整个仿真期间保持固定的静态属性,例如质量或刚度(dofs_info、geom_info)。*_render: 仅供可视化使用、从不参与物理的字段(particles_render)。*_ng: 禁用了requires_grad的状态变体(particles_state_ng);*_reordered标记经过空间重排的缓存。
循环索引是 i_<representation-initial>。 内核用短名称遍历扁平数组,名称中的字母指明了被索引的对象。这些是上文所述的全局索引,而不是局部索引:
变量 |
索引对象 |
|---|---|
|
batch:环境 |
|
实体 |
|
连杆 |
|
关节 |
|
dof |
|
几何体(碰撞对中的两个几何体分别用 |
|
顶点 |
各个求解器具体的字段布局(完整的 state 与 info 结构体)请直接阅读求解器源码,从 rigid_solver.py 和 genesis/engine/entities/ 下的实体类开始。
另见#
Hello, Genesis World:首次介绍
name与实体的地方。控制你的机器人:完整控制循环中的局部 dof 索引。
RigidJoint API 与 RigidEntity API:
dofs_idx/dofs_idx_local属性以及*_idx_local方法。