软体机器人#
软体机器人是一种内部嵌有肌肉纤维的可变形体。它不是靠关节电机驱动刚体骨架,而是通过收缩这些纤维来驱动:每个驱动信号都会沿纤维方向施加一个主动应力,物体随之变形。
Genesis World 用两个可变形求解器来仿真肌肉;通过实体的材质选择其中一个:
gs.materials.MPM.Muscle:物质点法求解器,按粒子驱动。gs.materials.FEM.Muscle:有限元法求解器,按四面体单元驱动。
两者共享相同的控制接口,因此更换求解器不会改变你的控制循环。超越刚体 教程更深入地介绍了这些底层求解器。
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()
刚-软混合机器人#
混合机器人用内部的刚体骨架驱动软体外皮:骨架承载自由度,外皮围绕它变形。使用 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。关于外皮如何从骨架生长出来(或骨架如何从网格生成)以及如何自定义这种关联,请参见 混合实体。