webots
调研环境
Ubuntu 16.04 虚拟机镜像
Docker version 20.10.7 + webots R2023a
运行 webots
如果是通过 docker 安装的 webots ,一般安装在
/usr/local/webots
在命令行进入该目录后运行
./webots
即可打开仿真软件$ cd /path/to/webots # e.g. cd /usr/local/webots
$ ./webots也可以在终端中的任何目录下运行以下命令打开仿真软件
$ webots
如果不是通过 docker 安装,找到 webots 的安装目录一般也可以找到 webots 的可执行文件,运行即可
软件基本使用介绍
菜单栏涵盖以下功能
File
创建、打开、保存场景以及组件
文件的基本操作(撤销、重做、创建等)
截图、录制视频
Edit
- 编辑的基本功能
View
切换/聚焦物体和整个场景的视角
边界、碰撞和关节轴的显示,雷达、相机和测距等传感器图层的显示
物体是否可选,可移动等
Simulation
- 显示与现实时间的倍率,可以加速、暂停、暂停后恢复以及单步运行
Overlays
- 显示/隐藏所有相机、渲染和设备
Tools
隐藏/显示三维视图交互显示、场景树、编辑区的窗口
控制台新建、清屏
编辑物理组件
首选项
Help
加入 solid 实体,配置 children 节点添加 “形状” 节点 ,添加 “物体边界”、“物理属性” 等项
创建项目,设置地板等环境。从 webots 库中添加模型
从场景树视图中添加节点,示例了 “实体”、“物理”、“形状”、“球体”和“长方体”节点
- “物理” 属性决定其是静态实体还是动态实体,比如墙应设置为静态实体
DEF-USE 机制用于减少场景树节点的冗余
- e.g. 将 Shape 节点 DEF ,可以在 bounding Object 项 USE ,即不需要给 bounding Object 设置一个 Shape 节点导致节点太多,影响仿真速率
调节环境灯光,修改外观节点 PBRAppearance 给球加纹理,纹理图需要导入
controller
控制器,仿真运行时 webots 会自动启动每个 robot 节点的控制器字段中指定的 controller
根据 教程 重写了一个小车模型的控制器,小车带有距离传感器雷达,实现避障效果
文件菜单新建一个 controller 文件,创建传感器设备
wb_robot_get_device
,wb_distance_sensor_enable
赋予距离传感器特性wb_motor_set_position(left_motor, INFINITY)
,创建车轮对象当传感器
wb_distance_sensor_get_value
获取到的值大于一定值时,wb_motor_set_velocity
设置车轮电机速度,形成差速使之转弯
场景中实体的结合和实体赋予物理属性
实体的 children 节点定义为 Group,在 Group 的 children 中可以建多个子节点例如 shape 和 transform(为其子节点定义相对于其父节点坐标系的坐标系)
实体中 physics 字段可以设置 density 密度和 mass 质量,仅能存在其一
- centerOfMass 修改重心偏移量
制作、导入模型(四轮小车)
制作四轮机器人模型:
创建 robot 节点作为四轮机器人,它的子节点包含本体(长方体表示)和四个关节(hingejoint)分别连接一个圆柱形车轮(shape)
hingejointparameter 定义了绕圆柱的中心点旋转,device 选择旋转电机(RotationalMotor),endPoint 定义为圆柱车轮的实体,包含车轮的外观属性和位置等
- device 字段的 name 字段作为程序中获取车轮的标识
定义两个距离传感器在车头
得到一个四轮避障机器人
导入模型
在 webots 创建,
在环境中制作模型,比如上面提到的小车模型,保存在当前项目的 world 里,在 world 中的对应场景的 .wbt 包含了该 robot 节点的内容保存。教程中在 proto 文件夹下新建了 .proto 文件,用编辑器将 .wbt 文件中的 robot 节点内容复制到 .proto 文件中保存,形如:
#VRML_SIM R2023a utf8
PROTO FourWheelsRobot [
#这里将这三个参数暴露出来,可以在导入模型节点后修改这部分参数
field SFVec3f translation 0 0 0 #定义位置
field SFRotation rotation 0 0 1 0 #定义旋转
field SFFloat bodyMass 1 #定义模型质量
]
{
Robot {
translation IS translation
rotation IS rotation
children [
# list of children nodes
]
boundingObject USE BODY
physics Physics {
density -1
mass IS bodyMass
}
controller "four_wheels_collision_avoidance" #控制器
}
}将 proto 文件保存,在添加节点的功能中会存在一项 PROTO nodes (Current Project) 可以导入该模型
外部导入,需要从 3D 模型文件中导入各部件再在软件中进行拼接。如 solidwork 等生成的 wrl、stl 等格式的文件
找到一个视频,关于导入 3D 模型文件的教程,因为安装的软件版本是 R2023a,网上大部分是 R2022a 的 3D 模型导入步骤,而新的版本取消了 File 菜单下的 import 3D model 选项
定义一个 Shape 节点修改 geometry 字段为 Mesh,在 Mesh 的 url 项可从 3D 模型保存的路径导入模型
Roban 模型搭建(部分实现)
定义 robot 节点
定义了父节点(robot 节点)的 Shape (Mesh 从本地导入 3D 模型)和 HingeJoint (铰链关节)
4 个 HingeJoint 表示躯干通过 4 个关节与四肢相连(未考虑头部)
在 HingeJoint 里修改 jointParameters(设置关节位置和轴)、device(设置电机设备)和 endPoint(末端实体)
按照上面的方法逐一连接四肢的各个关节和实体
supervisor 超级管理
supervisor
可以通过添加或删除场景中的节点来修改环境,它可以通过编程方式修改参数的值来改变环境的属性,例如允许以某种方式移动或设置机器人,它可以开始创建电影、动画。由于其无限的访问权限,它可以用来获取关于模拟状态的测量值,并跟踪其演变。然而,即使是监督员也不能访问安装在不同机器人上的设备的内部信息和测量,例如摄像头图像和距离传感器测量。
碰撞事件检测
调用
getContactPositions()
可以得到接触点在世界坐标系中的坐标官方文档 描述
其中提到
The node argument must be a Solid node (or a derived node), which moreover has no Solid parent.
而Solid
在官方文档中有如此 描述Solid 包含了 Robot 节点,所以可以用
getContactPositions()
检测 Robot 节点的碰撞,其它 Solid (e.g. 箱子、柱子) 亦可通过该方法获取接触点
通过
getContactPositions()
返回一个 Contact Point 对象,可以通过getPoint()
得到包含接触点的列表
获取实体位置
调用
getPosition()
可以得到接触点在世界坐标系中的坐标官方文档 描述
其中提到
The node argument must be a Transform node (or a derived node), otherwise the function will print a warning message and return 3 NaN (Not a Number) values. This function returns a vector containing exactly 3 values.
而Transform
在官方文档中有如此 描述Solid
是其衍生节点,而Robot
又是Solid
的衍生节点,所以可以用getPosition()
检测Robot
节点在世界坐标系中的位置
通过
getContactPositions()
返回一个包含 3 个元素的列表代表 x、 y 和 z
控制关节转动
有控制 rotationMotor 设备的接口,先对 rotationMotor 设备通过
getDevice
进行实例,再通过setPosition
函数可以设置转动到的位置,参数是弧度"""test_controller controller."""
from controller import Robot
import math
# create the Robot instance.
robot = Robot()
# get the time step of the current world.
timestep = int(robot.getBasicTimeStep())
servo_list = []
servo_id = [
'id7', 'id8', 'id9',
'id10', 'id11', 'id12'
]
ps_list = []
ps_name = [
'ps7', 'ps8', 'ps9',
'ps10', 'ps11', 'ps12'
]
for index, id in enumerate(servo_id):
servo_list.append(robot.getDevice(id))
ps_list.append(robot.getDevice(ps_name[index]))
ps_list[index].enable(timestep)
# You should insert a getDevice-like function in order to get the
# instance of a device of the robot. Something like:
# motor = robot.getDevice('motorname')
# ds = robot.getDevice('dsname')
# ds.enable(timestep)
# Main loop:
# - perform simulation steps until Webots is stopping the controller
i = 0
angle = 80
choose_id = -1
while robot.step(timestep) != -1:
# Read the sensors:
# Enter here functions to read sensor data, like:
# val = ds.getValue()
# Process sensor data here.
# Enter here functions to send actuator commands, like:
# motor.setPosition(10.0)
if i == 100:
print(ps_list[choose_id].getValue())
servo_list[choose_id].setPosition(angle * math.pi / 180)
if i == 300:
print(ps_list[choose_id].getValue() * 180 / math.pi)
i += 1
pass
# Enter here exit cleanup code.
ros package
作为 ros 的一个功能包使用,提供了一部分 ros 实现的示例
通过 ros 实现仿真外的控制
在机器人的控制器中初始化 ros 节点
通过但不限于提供服务或者发布消息的方式,与其它节点进行通信
在服务或消息的回调函数中对仿真行为进行调用