Rerun 机器人 URDF 加载与关节动画实战:用 UrdfTree 驱动 3D 可视化
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
导读
本篇文章基于 Rerun 仓库中的animated_urdf示例(示例说明、示例主程序),完整讲解如何将一个 URDF(Unified Robot Description Format,统一机器人描述格式)机械臂模型加载进 Rerun,并在时间轴上通过关节角度驱动各关节持续运动。读完本文,你将掌握「一次性记录静态 URDF 资源 → 用UrdfTree解析模型结构 → 按时间步计算并写入关节变换」这一套可直接复用的机器人数据可视化流程,同时了解其底层 Python 绑定与 Rust 实现原理。
一、要解决什么问题:给静态 URDF 加上“时间轴”
URDF 是 ROS 生态中最常见的机器人模型描述格式,它用<link>描述刚体部件(含 mesh/几何体),用<joint>描述部件间的运动副(旋转、平移等)。但 URDF 本身只描述模型的静态结构——它不包含任何随时间变化的关节角度数据。
Rerun 的核心数据模型是「实体(entity)+ 时间轴(timeline)」,因此一个自然的方案是:
- 将 URDF 的几何与静态变换作为一次性资源记录(只写一次,不随时间变化);
- 将关节角度作为动态数据,每个时间步写入一条新的
Transform3D; - 由 Rerun Viewer 在时间轴上重放,形成机械臂动画。
animated_urdf示例演示的正是这条完整链路,其 README 将核心步骤概括为三点:
- 将 URDF 文件作为静态资源加载并记录;
- 使用
UrdfTree解析 URDF 结构; - 通过记录动态变换来驱动关节动画。
import rerun as rr from rerun.urdf import UrdfTree # 1. 将 URDF 文件作为静态资源记录一次 rec.log_file_from_path(urdf_path, static=True) # 2. 把 URDF 树结构载入内存 urdf_tree = UrdfTree.from_file_path(urdf_path) # 3. 通过记录变换来驱动关节 for joint in urdf_tree.joints(): if joint.joint_type == "revolute": # compute_transform 根据关节原点与当前角度 # 计算出可直接记录、且帧名已设置好的完整变换 transform = joint.compute_transform(angle) rec.log("transforms", transform)小提示:示例 README 中的导入语句写作
import rerun.urdf import UrdfTree,正确的写法为from rerun.urdf import UrdfTree(与 animated_urdf.py 中的用法一致)。
二、第一步:用log_file_from_path记录静态 URDF
Rerun 支持直接加载.urdf文件并将其内容(几何体 + 静态关节变换)作为实体写入当前 recording。在示例中:
# Log the URDF file once rec.log_file_from_path(urdf_path)关键点在于“只记录一次”:示例主循环中没有任何对几何体的重复写入,几何数据因此天然成为静态资源,不会随动画时间轴变化。README 中给出的static=True是显式声明“该实体为静态”的写法,二者效果一致——核心诉求都是把 URDF 的几何与默认姿态固定下来,把动态性全部留给关节变换。
示例使用的模型是 SO-ARM100 机械臂,URDF 文件位于 examples/rust/animated_urdf/data/so100.urdf(仓库中通过 Git LFS 托管)。从 Rust 实现(urdf_tree.rs)可以看到,加载时除了解析<link>/<joint>,还会记录 URDF 文件所在目录(urdf_dir),用于解析.stl/.dae等 mesh 文件的相对路径——因此移动 URDF 文件时请连同其 mesh 目录一起迁移。
记录时自动生成的实体路径
从 urdf_tree.rs 的实现可以看到,URDF 被记录为以下实体结构:
| 实体路径 | 含义 |
|---|---|
{robot_name}(或{prefix}/{robot_name}) | 机器人根实体 |
{root}/visual_geometries/... | 所有视觉几何体(mesh/box/cylinder/capsule/sphere),便于整体开关显隐 |
{root}/collision_geometries/... | 所有碰撞几何体,与视觉几何分离存储 |
tf_static | 默认静态关节变换统一写入的实体路径 |
几何类型到路径段的映射定义在geometry_type_name()(urdf_tree.rs),依次为mesh、box、cylinder、capsule、sphere。视觉与碰撞几何分离的设计,正是为了让使用者能在 Viewer 中一键隐藏碰撞体(示例的 blueprint 就是这么做的,见下文)。
三、第二步:用UrdfTree解析模型结构
UrdfTree把整个 URDF 模型读入内存,并提供统一的查询接口:
urdf_tree = UrdfTree.from_file_path(urdf_path)从 Python 绑定(urdf.rs)可以看出,from_file_path实际接受三个可选参数:
| 参数 | 类型 | 作用 |
|---|---|---|
entity_path_prefix | str | 给机器人实体路径加前缀(多机器人共存时隔离实体树) |
frame_prefix | str(关键字参数) | 给所有坐标系帧 ID 加前缀(如"left/"→left/base) |
static_transform_entity_path | str(关键字参数) | 自定义静态变换写入的实体路径,默认tf_static |
UrdfTree核心 API
结合 urdf.rs 与 类型声明,UrdfTree提供以下能力:
name:URDF<robot>名称;root_link():返回根<link>;joints():遍历全部关节;get_joint_by_name(name):按名字查关节;get_joint_child(joint):取某关节的子 link(动画中常用于按关节定位部件);get_link_by_name(name):按名字查 link;get_visual_geometry_paths(link)/get_collision_geometry_paths(link):取某 link 的视觉/碰撞几何实体路径(用于改颜色、控制显隐);log(recording):把整棵机器人(几何 + 静态变换)一次性写入 recording;stream(...):以惰性流方式产出该模型的所有 chunk,供批量管道使用。
UrdfJoint属性一览
每个关节(urdf.rs)暴露如下属性,几乎完整对应 URDF<joint>的语义:
| 属性 | 说明 |
|---|---|
name | 关节名 |
joint_type | 关节类型,取值见下方表格 |
parent_link/child_link | 父/子 link 名 |
axis | 关节轴((x, y, z)) |
origin_xyz/origin_rpy | 关节原点:平移(米)与旋转(rpy 弧度) |
limit_lower/limit_upper | 关节行程下限/上限(角度制关节为弧度) |
limit_effort/limit_velocity | 力/速度限制 |
mimic | <mimic>标签(联动关节),含joint/multiplier/offset子属性 |
joint_type与 URDF 规范的映射(urdf.rs)为:
| joint_type | URDF 类型 | 变换计算支持 |
|---|---|---|
revolute | 有限角度旋转关节 | ✅ 按角度(弧度)驱动 |
continuous | 无限旋转关节 | ✅ 按角度(弧度)驱动 |
prismatic | 平移关节 | ✅ 按距离(米)驱动 |
fixed | 固定关节 | 无自由度 |
floating/planar/spherical | 浮动/平面/球关节 | ❌compute_transform会抛NotImplementedError |
后三种类型在compute_transform中不被支持(对应 Rust 侧 joint_transform.rs 的UnsupportedJointType错误),动画时应只针对revolute/continuous/prismatic关节写入动态角度。
四、第三步:compute_transform生成可记录变换
这是整个示例的核心 API:
transform = joint.compute_transform(angle, clamp=True) rec.log("transforms", transform)从 urdf.rs 可以看到,compute_transform(value, clamp=False)会计算并返回一个包含以下字段的字典:
quaternion_xyzw:旋转四元数(x, y, z, w);translation:平移(x, y, z);parent_frame/child_frame:已经设置好的父子坐标系帧名(若配置了frame_prefix会自动拼接前缀);warning:可选警告信息。
其底层 Rust 实现在 joint_transform.rs:compute_joint_transform会综合关节原点(origin)与当前关节值,输出一个带帧名关系的Transform3D。这正是 README 中强调的:“compute_transform 给你一个完整可记录、由关节原点与当前角度算出、且帧名已设置好的变换”。Rerun 通过 Transform3D 的父子帧关系自动维护运动学链,你只需要为每个动态关节写一条变换,其余全靠帧名连接。
clamp参数的行为
clamp=True:超出[limit_lower, limit_upper]的关节值会被截断到边界,同时产生一条 warning(在绑定层以warning字段暴露,底层通过re_log::warn!输出);clamp=False(默认):越界值按原样参与计算,不产生警告。
示例的_fake_angle(animated_urdf.py)本身就是按limit_lower/limit_upper生成正弦振荡角度,因此配合clamp=True使用是安全的:
def _fake_angle(joint, step, joint_index, phase): """在关节行程限制内生成平滑振荡角度。""" sin_value = math.sin(step * (0.02 + joint_index / 100.0) + phase) return joint.limit_lower + (sin_value + 1.0) / 3.0 * (joint.limit_upper - joint.limit_lower)实际项目中,这里的角度应替换为真实测量值(如电机编码器读数、仿真输出),示例只是用正弦函数模拟。
五、完整示例:从记录到动画的完整流程
将前三步串起来,并加入时间轴推进、蓝图(blueprint)与关节联动颜色,得到示例的完整主体逻辑(animated_urdf.py):
duration = 0.0 rec = rr.script_setup(args, "rerun_example_animated_urdf") rec.set_time(TIMELINE, duration=duration) # 1. 一次性记录 URDF 几何与静态变换 rec.log_file_from_path(urdf_path) # 2. 载入 URDF 树 urdf_tree = rr.urdf.UrdfTree.from_file_path(urdf_path) # 3. 蓝图:3D 视图中默认隐藏碰撞几何 blueprint = rrb.Grid( rrb.Spatial3DView( name="Animated URDF", overrides={"so_arm100/collision_geometries": rrb.EntityBehavior(visible=False)}, ) ) rec.send_blueprint(blueprint) # 4. 动画主循环 for step in range(10000): for joint_index, joint in enumerate(urdf_tree.joints()): if joint.joint_type == "revolute": dynamic_angle = _fake_angle(joint, step, joint_index, phase=0.0) transform = joint.compute_transform(dynamic_angle, clamp=True) rec.log("transforms", transform) # 进阶:按关节角度动态改变 "jaw" link 的视觉网格颜色 link = urdf_tree.get_joint_child(joint) if link.name == "jaw": for visual_path in urdf_tree.get_visual_geometry_paths(link): normalized_angle = (dynamic_angle - joint.limit_lower) / ( joint.limit_upper - joint.limit_lower ) rgba = [1.0 - normalized_angle, normalized_angle, 0, 0.5] rec.log(visual_path, rr.Asset3D.from_fields(albedo_factor=rgba)) duration += 0.03 rec.set_time(TIMELINE, duration=duration)值得注意的几个设计点:
- 时间轴:以
example_time为时间轴,每步推进0.03秒;几何只记录一次,变换按帧写入; - 蓝图控制:利用第一节提到的“视觉/碰撞几何分离”实体结构,通过 blueprint 的
EntityBehavior(visible=False)默认隐藏collision_geometries; - 按 link 定位部件:
get_joint_child(joint)拿到子 link,再通过get_visual_geometry_paths(link)精确改写某个部件的视觉属性(这里是按归一化关节角给jaw上色)。
运行方式
示例的入口与依赖配置在 pyproject.toml:
- 依赖仅
rerun-sdk; - 注册了控制台入口
animated_urdf = "animated_urdf:main"; - 脚本 docstring 给出的运行方式为
python -m animated_urdf。
脚本支持 Rerun 标准的rr.script_add_args通用参数,并额外提供:
| 参数 | 作用 |
|---|---|
--dual | 同时加载两份相同的 URDF(双机械臂演示,帧前缀区分左右) |
六、进阶:双机械臂与frame_prefix
示例的--dual模式(animated_urdf.py)展示了多机器人场景的标准做法——用前缀隔离命名空间:
left = rr.urdf.UrdfTree.from_file_path(urdf_path, entity_path_prefix="left", frame_prefix="left/") right = rr.urdf.UrdfTree.from_file_path(urdf_path, entity_path_prefix="right", frame_prefix="right/") # 一次性写入两台机器人的几何与静态变换 left.log_urdf_to_recording() right.log_urdf_to_recording() # 水平错开两个机械臂 rec.log("left", rr.Transform3D(translation=[-0.2, 0, 0]), static=True) rec.log("right", rr.Transform3D(translation=[ 0.2, 0, 0]), static=True)这里两个参数的分工非常清晰:
entity_path_prefix:让两台机器人的实体树互不干扰(left/so_arm100/...与right/so_arm100/...);frame_prefix:让坐标系帧 ID唯一(left/base、right/base…),避免两套相同的帧名在帧图中冲突。该前缀最终由 urdf.rs 的apply_prefix拼接到compute_transform返回的parent_frame/child_frame上。
动画时左右臂分别用不同相位(phase=0.0与phase=2.0)驱动,形成对称摆动效果。这种「前缀 + 复用同一份 URDF」的模式,可直接推广到任意多机器人的数字孪生场景。
七、底层原理:Python API 背后的 Rust 链路
整个rerun.urdf模块并非纯 Python 实现,而是通过 PyO3 直接封装 Rust 侧re_importercrate 的UrdfTree(见 urdf.rs 的use re_sdk::external::re_importer::{UrdfTree, ...}):
Python: rerun.urdf.UrdfTree │ PyO3 绑定(rerun_py/src/urdf.rs) ▼ Rust: re_importer::UrdfTree(crates/data_flow/re_importer/src/importer_urdf/urdf_tree.rs) │ ├─ urdf_rs::read_file() 解析 XML ├─ 构建 links / joints / children / materials 索引 └─ 记录 urdf_dir 以便解析 mesh 相对路径 ▼ 关节变换:importer_urdf/joint_transform.rs → Transform3D(含帧名、四元数、平移)这一架构带来的直接好处:
- 解析与索引在 Rust 侧一次性完成,Python 层只是薄封装,遍历
joints()、get_joint_by_name()等查询都基于内存中的 HashMap 索引; - 变换计算与 Rerun 数据格式零距离:
compute_transform直接产出带parent_frame/child_frame的Transform3D,与 Viewer 的帧图机制无缝衔接,无需手动换算旋转矩阵; - 绑定层还暴露了面向批处理的
compute_transform_columns(一次算多组值)与compute_joint_transform_batches(按 joint 名数组批量计算),为大规模/流式场景(配合stream()惰性管道)预留了接口。
八、总结:一套可复用的机器人可视化范式
animated_urdf示例给出一套可直接套用的范式:
- 静态归静态:几何体、材质、默认姿态通过
log_file_from_path(或UrdfTree.log_urdf_to_recording())只写一次; - 动态归动态:关节角作为时间序列数据,每个时间步用
compute_transform(angle, clamp=True)生成带帧名的Transform3D写入; - 命名空间靠前缀:多机器人用
entity_path_prefix隔离实体、用frame_prefix隔离帧 ID; - 细节控制靠路径:借助
get_visual_geometry_paths/get_collision_geometry_paths按 link 精准操作部件属性。
无论你接下来要接入的是真实机械臂的编码器数据、仿真器的关节状态,还是 ROS 的/joint_states话题,都可以用这套流程在 Rerun 中快速得到带时间轴的 3D 机器人动画。想深入源码,建议从 urdf_tree.rs、joint_transform.rs 与 urdf.rs 三处入手。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考