软体机器人#

软体机器人是一种内部嵌有肌肉纤维的可变形体。它不是靠关节电机驱动刚体骨架,而是通过收缩这些纤维来驱动:每个驱动信号都会沿纤维方向施加一个主动应力,物体随之变形。

Genesis World 用两个可变形求解器来仿真肌肉;通过实体的材质选择其中一个:

两者共享相同的控制接口,因此更换求解器不会改变你的控制循环。超越刚体 教程更深入地介绍了这些底层求解器。

Note

MPM 和 FEM 计算量很大。若要获得交互式帧率,请在 gs.init() 中传入 backend=gs.gpu 以在 GPU 上运行。

最小示例#

完整脚本为 examples/tutorials/advanced_muscle.py。它把两个球体(一个 MPM、一个 FEM)放入零重力场景,并用正弦波驱动它们脉动,以便并排比较两个求解器。

软体机器人与普通可变形体之间只有两点区别。第一,为实体指定 Muscle 材质:

E, nu = 3.0e4, 0.45  # Young's modulus (Pa) and Poisson ratio
rho = 1000.0  # density, kg/m3

robot_mpm = scene.add_entity(
    morph=gs.morphs.Sphere(pos=(0.5, 0.2, 0.3), radius=0.1),
    material=gs.materials.MPM.Muscle(E=E, nu=nu, rho=rho, model="neohooken"),
)
robot_fem = scene.add_entity(
    morph=gs.morphs.Sphere(pos=(0.5, -0.2, 0.3), radius=0.1),
    material=gs.materials.FEM.Muscle(E=E, nu=nu, rho=rho, model="stable_neohookean"),
)

第二,每一步调用 set_actuation,而不是某个关节控制方法:

for i in range(1000):
    actu = [0.2 * (0.5 + np.sin(0.01 * np.pi * i))]  # one value per muscle group
    robot_mpm.set_actuation(actu)
    robot_fem.set_actuation(actu)
    scene.step()

其余部分(平面、场景、build、step)都是 你好,Genesis World 中的标准流程。

Note

本构 model 的名称在不同求解器之间有所不同。MPM 使用 "corotation" 或 "neohooken";FEM 使用 "linear" 或 "stable_neohookean"。("stable_neohooken" 是 FEM 模型已弃用的拼写,使用时会记录一条警告。)

时间步与重力#

软体动力学需要较小的时间步和若干子步才能保证数值稳定。在每个软体求解器的选项上设置时间步,场景其余部分仍使用全局时间步:

dt = 5e-4  # seconds
scene = gs.Scene(
    sim_options=gs.options.SimOptions(
        substeps=10,
        gravity=(0, 0, 0),  # float freely so the muscle motion is easy to see
    ),
    mpm_options=gs.options.MPMOptions(
        dt=dt,
        lower_bound=(-1.0, -1.0, -0.2),  # MPM simulates on a fixed grid;
        upper_bound=(1.0, 1.0, 1.0),  # entities must stay inside these bounds
    ),
    fem_options=gs.options.FEMOptions(dt=dt, damping=45.0),
    show_viewer=True,
)

MPM 求解器将空间离散到背景网格上;lower_bound 和 upper_bound 以米为单位(Z 轴向上)设定其范围。到达边界的粒子会被钳制在那里,其速度被墙体吸收,因此请设置足够大的边界以容纳机器人的完整运动范围。

肌肉组与纤维方向#

默认情况下,软体机器人只有一个覆盖整个身体的肌肉组,所有纤维都沿 +Z([0, 0, 1])方向。此时单个驱动值会让整个身体沿该轴收缩:这对球体演示很有用,但对运动毫无帮助。

要让机器人有目的地运动,需要把它划分为若干组,并为每个部分指定纤维方向。在材质上声明组的数量,然后在 build 之后调用 set_muscle(它会读取构建后的粒子位置):

worm = scene.add_entity(
    morph=gs.morphs.Mesh(
        file="meshes/worm/worm.obj",
        pos=(0.3, 0.3, 0.001),
        scale=0.1,
        euler=(90, 0, 0),  # extrinsic x-y-z, degrees
    ),
    material=gs.materials.MPM.Muscle(
        E=5e5,
        nu=0.45,
        rho=10000.0,
        model="neohooken",
        n_groups=4,  # at most 4 independently actuated muscles
    ),
)

scene.build(n_envs=3)

set_muscle 接受两个逐单位(unit)的数组,其中单位在 MPM 中是粒子,在 FEM 中是单元:

  • muscle_group:每个单位一个位于 [0, n_groups) 范围内的整数,指明该单位属于哪块肌肉。

  • muscle_direction:每个单位一个纤维方向(或一个共享向量)。Genesis 不会对其归一化;请传入单位向量。

蠕虫示例根据粒子位置把身体切分为上/下与前/后四个部分,然后让所有纤维指向 +Y:

pos = worm.get_state().pos[0]  # ([n_envs,] n_particles, 3) - take env 0
n_units = worm.n_particles  # FEM instead uses worm.n_elements

pos_max, pos_min = pos.max(dim=0).values, pos.min(dim=0).values
pos_range = pos_max - pos_min

lu_thr, fh_thr = 0.3, 0.6
muscle_group = torch.zeros((n_units,), dtype=gs.tc_int, device=gs.device)
mask_upper = pos[:, 2] > (pos_min[2] + pos_range[2] * lu_thr)
mask_fore = pos[:, 1] < (pos_min[1] + pos_range[1] * fh_thr)
muscle_group[mask_upper & mask_fore] = 0  # upper fore body
muscle_group[mask_upper & ~mask_fore] = 1  # upper hind body
muscle_group[~mask_upper & mask_fore] = 2  # lower fore body
muscle_group[~mask_upper & ~mask_fore] = 3  # lower hind body

worm.set_muscle(
    muscle_group=muscle_group,
    muscle_direction=(0.0, 1.0, 0.0),  # fibers along +Y, shared by all units
)

此时 set_actuation 每组接受一个值,因此其输入形状为 (n_groups,)。只让下后部这一组脉动,就能让蠕虫向前爬行:

for i in range(1000):
    actu = (0.0, 0.0, 0.0, 1.0 * (0.5 + math.sin(0.005 * math.pi * i)))  # shape (n_groups,)
    worm.set_actuation(actu)
    scene.step()

完整脚本为 examples/tutorials/advanced_worm.py。

刚-软混合机器人#

混合机器人用内部的刚体骨架驱动软体外皮:骨架承载自由度,外皮围绕它变形。使用 gs.materials.Hybrid 构建混合机器人,它会把 gs.materials.Rigid 骨架与一种软体材质配对,后者必须是 gs.materials.MPM.Muscle:

robot = scene.add_entity(
    morph=gs.morphs.URDF(
        file="urdf/simple/two_link_arm.urdf",
        pos=(0.5, 0.5, 0.3),
        scale=0.2,
        fixed=True,
    ),
    material=gs.materials.Hybrid(
        material_rigid=gs.materials.Rigid(gravity_compensation=1.0),
        material_soft=gs.materials.MPM.Muscle(E=1e4, nu=0.45, rho=1000.0, model="neohooken"),
        thickness=0.05,  # skin thickness grown around the skeleton, meters
        damping=1000.0,
    ),
)

由于驱动来自刚体骨架,混合机器人通过普通的刚体接口控制(control_dofs_velocity、control_dofs_position、control_dofs_force),传入的数值个数要与骨架的自由度数量一致:

for i in range(1000):
    dofs_ctrl = [1.0 * np.sin(2 * np.pi * i * 0.001)] * robot.n_dofs
    robot.control_dofs_velocity(dofs_ctrl)  # drive the inner skeleton
    scene.step()

完整脚本为 examples/tutorials/advanced_hybrid_robot.py。关于外皮如何从骨架生长出来(或骨架如何从网格生成)以及如何自定义这种关联,请参见 混合实体。

另请参阅#

  • 混合实体:从 URDF 或网格构建刚-软混合实体。

  • 超越刚体:流体、布料和可变形体背后的 MPM、FEM、SPH 与 PBD 求解器。