Rerun 机器人 URDF 加载与关节动画实战:用 UrdfTree 驱动 3D 可视化
2026/9/16 19:56:42 网站建设 项目流程

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)」,因此一个自然的方案是:

  1. 将 URDF 的几何与静态变换作为一次性资源记录(只写一次,不随时间变化);
  2. 将关节角度作为动态数据,每个时间步写入一条新的Transform3D
  3. 由 Rerun Viewer 在时间轴上重放,形成机械臂动画。

animated_urdf示例演示的正是这条完整链路,其 README 将核心步骤概括为三点:

  1. 将 URDF 文件作为静态资源加载并记录;
  2. 使用UrdfTree解析 URDF 结构;
  3. 通过记录动态变换来驱动关节动画。
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),依次为meshboxcylindercapsulesphere。视觉与碰撞几何分离的设计,正是为了让使用者能在 Viewer 中一键隐藏碰撞体(示例的 blueprint 就是这么做的,见下文)。


三、第二步:用UrdfTree解析模型结构

UrdfTree把整个 URDF 模型读入内存,并提供统一的查询接口:

urdf_tree = UrdfTree.from_file_path(urdf_path)

从 Python 绑定(urdf.rs)可以看出,from_file_path实际接受三个可选参数:

参数类型作用
entity_path_prefixstr给机器人实体路径加前缀(多机器人共存时隔离实体树)
frame_prefixstr(关键字参数)给所有坐标系帧 ID 加前缀(如"left/"left/base
static_transform_entity_pathstr(关键字参数)自定义静态变换写入的实体路径,默认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_typeURDF 类型变换计算支持
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/baseright/base…),避免两套相同的帧名在帧图中冲突。该前缀最终由 urdf.rs 的apply_prefix拼接到compute_transform返回的parent_frame/child_frame上。

动画时左右臂分别用不同相位(phase=0.0phase=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(含帧名、四元数、平移)

这一架构带来的直接好处:

  1. 解析与索引在 Rust 侧一次性完成,Python 层只是薄封装,遍历joints()get_joint_by_name()等查询都基于内存中的 HashMap 索引;
  2. 变换计算与 Rerun 数据格式零距离compute_transform直接产出带parent_frame/child_frameTransform3D,与 Viewer 的帧图机制无缝衔接,无需手动换算旋转矩阵;
  3. 绑定层还暴露了面向批处理的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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询