rerun-urdf
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRerun URDF ingestion
Rerun URDF 数据导入
A URDF gives you a robot's geometry and its kinematic tree.
Ingesting it means two API calls on one : stream the static model, then drive it with forward kinematics from joint states. The transforms it produces are derived, so they are a layer, never base (the row of the table).
rerun.urdf.UrdfTreeURDF + joints (computed)rerun-data-modelThis skill is the API and the two judgment calls it cannot make for
you: how your joint values map to URDF joints, and how the disconnected frames
in the scene connect to one root. The stream/lens plumbing (,
, , writing and optimizing RRDs) is in
; reach for it, do not re-derive it. Nothing below is
tied to a data format: where your joint names, joint values, and calibration
come from is yours to wire in.
UrdfTreeLazyChunkStreamDeriveLensSelectorrerun-chunk-processingURDF 包含机器人的几何结构和运动学树。
导入URDF意味着对一个执行两次API调用:先流式传输静态模型,再根据关节状态通过正向运动学驱动模型。它生成的变换是派生的,因此属于图层,而非基础层(对应表格中的行)。
rerun.urdf.UrdfTreererun-data-modelURDF + joints (computed)本技能围绕 API展开,同时涉及两个API无法自动完成的判断:关节值如何映射到URDF关节,以及场景中不连通的帧如何连接到同一个根节点。流/透镜相关的底层实现(、、、RRD文件的写入与优化)位于中,直接使用即可,无需重复实现。以下内容不绑定特定数据格式:关节名称、关节值和校准数据的来源由您自行对接。
UrdfTreeLazyChunkStreamDeriveLensSelectorrerun-chunk-processingThe API
API说明
python
from rerun.urdf import UrdfTree
urdf = UrdfTree.from_file_path(
urdf_path,
entity_path_prefix="robot", # links log under /robot/<link>
frame_prefix="", # prepended to every frame name
static_transform_entity_path="robot/tf_static",
)- namespaces the entity tree. One per robot instance.
entity_path_prefix - namespaces the frame names (
frame_prefix->base_link). Two robots in one recording need different prefixes or their roots collide and transforms cross-wire. Leave it empty for a single robot.arm_base_link - is where the URDF's fixed-joint transforms log (defaults to
static_transform_entity_path)./tf_static
The tree is also introspectable (full surface in )
For one-off, non-stream use there are , (feeds ), and to log the whole model through the classic logging API (the example, , is that style).
help(UrdfTree)joint.compute_transform(value)joint.compute_transform_columns(values)rr.send_columnsurdf.log_urdf_to_recording()animated_urdfhttps://github.com/rerun-io/rerun/tree/main/examples/python/animated_urdfFor pipelines, a does two things.
UrdfTree1. Stream the static model. Emits the visual meshes () and the
fixed-joint transforms as chunks. This is the whole "log the URDF" step:
Asset3Dpython
model = (
urdf.stream(include_joint_transforms=True).drop( # rest-pose joint transforms too
content="/robot/**/collision_geometries/**"
) # unless you need collision meshes
)Recolor a robot's meshes with a on (see ).
MutateLensAsset3D:albedo_factorrerun-chunk-processing2. Solve forward kinematics. Given joint names and the matching joint values, it returns one per input row. Each batch is a list of per-joint entries with , , , and
:
rerun.urdf.JointTransformBatchparent_framechild_frametranslationquaternionpython
batches = urdf.compute_joint_transform_batches(names, values, clamp=False)python
from rerun.urdf import UrdfTree
urdf = UrdfTree.from_file_path(
urdf_path,
entity_path_prefix="robot", # 连杆记录在/robot/<link>路径下
frame_prefix="", # 为每个帧名称添加前缀
static_transform_entity_path="robot/tf_static",
)- 用于为实体树添加命名空间,每个机器人实例对应一个前缀。
entity_path_prefix - 用于为帧名称添加命名空间(例如
frame_prefix变为base_link)。若录制中包含两个机器人,需使用不同前缀,否则它们的根节点会冲突,导致变换交叉关联。单个机器人时可留空。arm_base_link - 是URDF中固定关节变换的记录路径(默认值为
static_transform_entity_path)。/tf_static
该树支持内省(完整接口可查看)。
对于一次性、非流式的使用场景,提供了、(为提供数据)和方法,可通过传统日志API记录整个模型(示例,地址:,即采用这种方式)。
help(UrdfTree)joint.compute_transform(value)joint.compute_transform_columns(values)rr.send_columnsurdf.log_urdf_to_recording()animated_urdfhttps://github.com/rerun-io/rerun/tree/main/examples/python/animated_urdf对于流水线场景,主要完成两项工作:
UrdfTree1. 流式传输静态模型。输出视觉网格()和固定关节变换的数据块。这就是完整的“记录URDF”步骤:
Asset3Dpython
model = (
urdf.stream(include_joint_transforms=True).drop( # 同时包含静止姿态的关节变换
content="/robot/**/collision_geometries/**"
) # 除非需要碰撞网格,否则可移除
)可通过修改来重新为机器人网格着色(详见)。
MutateLensAsset3D:albedo_factorrerun-chunk-processing2. 求解正向运动学。给定关节名称和对应的关节值,它会为每一行输入返回一个。每个批次是一组关节条目,包含、、和:
rerun.urdf.JointTransformBatchparent_framechild_frametranslationquaternionpython
batches = urdf.compute_joint_transform_batches(names, values, clamp=False)names, values: pyarrow arrays, one list per timestamp (names aligned to values)
names、values:pyarrow数组,每个时间戳对应一个列表(names与values对齐)
clamp=True clamps out-of-limit values and warns, useful while debugging units
clamp=True会限制超出范围的值并发出警告,调试单位时非常有用
You will almost always run this inside a stream so it stays columnar and lazy,
via the two-lens pattern: derive the batch, then `scatter=True` it into
`Transform3D.descriptor_translation/quaternion/parent_frame/child_frame`. The
full shape is the "Minimal shape" section below; the `robot_data_preprocessing`
example (References) is a complete working instance. The only URDF-specific
part is the `compute_joint_transform_batches` call inside the first lens;
everything else is generic stream mechanics (`rerun-chunk-processing`).
Merge the model stream and the FK stream, `collect(optimize=OBJECT_STORE)`, and
`write_rrd(..., recording_id=<segment_id>)`. The `recording_id` must equal the
base segment id or the layer never attaches; `application_id` is discarded on
registration.
您几乎总是会在流中运行此方法,以保持列存储和惰性计算,这通过双透镜模式实现:先派生批次,再通过`scatter=True`将其转换为`Transform3D.descriptor_translation/quaternion/parent_frame/child_frame`。完整结构见下文“最小实现结构”部分;参考示例`robot_data_preprocessing`(见参考链接)是一个完整的可运行实例。唯一与URDF相关的部分是第一个透镜中的`compute_joint_transform_batches`调用;其余均为通用的流机制(`rerun-chunk-processing`)。
合并模型流和FK流,调用`collect(optimize=OBJECT_STORE)`,再调用`write_rrd(..., recording_id=<segment_id>)`。`recording_id`必须与基础段ID一致,否则图层将无法关联;`application_id`在注册时会被忽略。Mapping joint state to URDF joints (you supply this; the data will not)
将关节状态映射到URDF关节(需自行实现,数据本身不包含此映射)
compute_joint_transform_batchesnamesvalues- Order. Build an explicit array aligned to the
namesyou read. Never assume your message's field order matches the URDF'svaluesorder.<joint> - Count. The URDF's non-joint count rarely equals your reported value count. A gripper sent as one value is often two prismatic joints in the URDF; a mimic joint may be omitted from the message. Reconcile explicitly, and use the API to do it: iterate
fixed, partition byurdf.joints()andjoint_type. A joint withmimicset derives its value from the driver joint asmimic; feed it that, not a message field. Confirm the count againstdriver * multiplier + offset, not the message length.urdf.joints() - Units. URDF joints are radians (revolute) and meters (prismatic). Convert if your source differs.
Get any of these wrong and FK runs and writes a confident, wrong pose.
compute_joint_transform_batchesnamesvalues- 顺序问题:构建与读取到的对齐的显式
values数组。永远不要假设消息的字段顺序与URDF中names的顺序一致。<joint> - 数量问题:URDF中非类型的关节数量很少与您上报的值数量一致。例如,作为单个值发送的夹具在URDF中通常对应两个棱柱关节;模拟关节可能未包含在消息中。需显式协调,可使用API完成:遍历
fixed,按urdf.joints()和joint_type进行分区。设置了mimic的关节其值由驱动关节派生而来,公式为mimic;需传入该派生值,而非消息中的字段。请根据driver * multiplier + offset确认数量,而非消息长度。urdf.joints() - 单位问题:URDF关节的旋转单位为弧度(revolute类型),移动单位为米(prismatic类型)。若数据源单位不同,需进行转换。
以上任何一点出错,都会导致FK计算完成并输出一个看似正确但实际错误的姿态。
Make the joint states readable first
先确保关节状态数据源可读
Where the joint values come from is not this skill's problem, but a dead joint
source produces an empty FK layer with no error: a source path that
matched nothing, a decoder that yielded zero rows, or a reader that dropped the
message silently. Whatever the source, confirm the joint-state stream yields
rows before debugging FK. The importer skill for your source format covers its
own empty-stream failure modes.
关节值的来源不属于本技能的范畴,但失效的关节数据源会生成空的FK图层且无任何错误提示:可能是源路径匹配失败、解码器输出零行,或读取器静默丢弃了消息。无论数据源是什么,在调试FK之前,请先确认关节状态流能输出数据。对应数据源格式的导入技能会处理其自身的空流故障模式。
How transforms compose (reason about this before logging anything)
变换的组合方式(记录前需先理清)
Rerun resolves a pose by chaining transforms from a frame up to a root. There
are two ways an edge in that chain gets defined, and a URDF ingest mixes both:
- By entity-path hierarchy. A on
Transform3Dwith no frame names is the transform of/a/brelative to its parent path/a/b. Composition follows the entity tree./a - By explicit frame graph. A that carries
Transform3Dandparent_framedefines an edge between two named frames, independent of where in the entity tree it is logged. URDF FK uses this: every joint transform names its parent and child link frames.child_frame
So a URDF ingest is a graph of named frames. An edge exists only if some
names that exact pair. A frame with
no incoming edge is a root. The viewer renders every root at the world origin,
which is why two unconnected robots silently overlap instead of erroring.
Transform3Dparent_frame -> child_frameRerun通过将帧到根节点的变换链来解析姿态。变换链中的边有两种定义方式,URDF导入会混合使用这两种方式:
- 通过实体路径层级。上的
/a/b若未指定帧名称,则表示Transform3D相对于其父路径/a/b的变换。组合遵循实体树结构。/a - 通过显式帧图。包含和
parent_frame的child_frame定义了两个命名帧之间的边,与它在实体树中的记录位置无关。URDF的FK计算使用这种方式:每个关节变换都会指定其父连杆和子连杆的帧名称。Transform3D
因此,URDF导入的结果是一个命名帧构成的图。只有当某个指定了确切的对时,对应的边才存在。没有入边的帧即为根节点。查看器会将每个根节点渲染在世界原点,这就是为什么两个未连接的机器人会静默重叠而非报错。
Transform3Dparent_frame -> child_frameResolving the transform forest (the part the data cannot always give you)
解决变换森林问题(数据无法始终提供这部分信息)
A URDF is one tree rooted at its base link. FK and fixed joints supply every
edge inside that tree. A real scene is a forest of roots the URDF never
connects: a world or scene frame, each robot's base, every camera or sensor
frame. The edges that join those roots come from calibration (extrinsics in a
sidecar, a TF static publisher, a hand-eye result), not from the URDF, and some
are simply absent. A single connected tree is not always solvable. Resolve it
deliberately:
- Enumerate every frame. Iterate and collect the
urdf.joints()/parent_linkpairs (the in-tree edges, frame-prefixed);child_linkis that URDF's root. Add every sensor/world frame the scene needs (from theurdf.root_link()table). Decide the one intended root.rerun-data-model - Classify each edge by source. In-URDF edges (FK joints, joints) come from the URDF plus joint values. Inter-root edges (root to each robot base, root to each fixed sensor, an arm link to a wrist-mounted camera) come from calibration and you must log them yourself.
fixed - Compose fixed chains from the URDF when you need the transform between two
links joined only by joints (a camera bracket, a tool mount): walk parent links across
fixedjoints viafixed, turning each joint'surdf.joints()/origin_xyzinto a homogeneous matrix and multiplying along the chain. If the walk cannot reach the target link, stop and say so ("no fixed chain from A to B; stuck at C"). A broken chain is a wrong pose, not a missing one.origin_rpy - Build the edge set and find the roots. Collect every pair you will log (URDF + calibration). Walk parents from each frame; any frame that does not reach the intended root is an unconnected root and names a missing edge. This is a pure graph check you can run before writing the RRD.
parent_frame -> child_frame - Resolve every missing edge, or fail loudly. For each one:
- If calibration supplies the transform, log it (next section).
- If the data does not, the tree is unsolvable. Do not leave the frame disconnected (it collapses onto the origin and reads as one merged scene). Either abort and name the missing edge, or log identity and emit a loud warning naming the assumed edge. State which you did.
- Match frame names across sources. FK-derived /
parent_framemust equal the nameschild_frameemits for the static geometry, and your calibration edges must use those same names, or links float off the mesh. Theurdf.stream()is what keeps them identical; reuse it everywhere.frame_prefix
A correct ingest has exactly one root, and a path from every frame to it.
URDF是以基础连杆为根的单棵树。FK和固定关节提供了树内的所有边。实际场景是一个森林,包含URDF从未连接的多个根节点:世界或场景帧、每个机器人的基础帧、每个相机或传感器帧。连接这些根节点的边来自校准数据(如附带文件中的外参、TF静态发布器、手眼标定结果),而非URDF,且部分边可能缺失。并非总能构建出单一的连通树。需有针对性地解决:
- 枚举所有帧。遍历并收集
urdf.joints()/parent_link对(树内边,已添加帧前缀);child_link是该URDF的根节点。添加场景所需的所有传感器/世界帧(来自urdf.root_link()表格)。确定一个目标根节点。rerun-data-model - 按来源分类每条边。URDF内的边(FK关节、关节)来自URDF和关节值。根节点间的边(根节点到每个机器人基础帧、根节点到每个固定传感器、机械臂连杆到腕部相机)来自校准数据,需自行记录。
fixed - 从URDF构建固定链。当需要仅由关节连接的两个连杆之间的变换时(如相机支架、工具安装座):通过
fixed遍历父连杆,将每个关节的urdf.joints()/origin_xyz转换为齐次矩阵并沿链相乘。若无法遍历到目标连杆,请立即终止并提示(“无法从A到B构建固定链;在C处中断”)。断裂的链会导致错误的姿态,而非缺失姿态。origin_rpy - 构建边集并查找根节点。收集所有将要记录的对(URDF边 + 校准边)。从每个帧向上遍历父节点;任何无法到达目标根节点的帧都是未连接的根节点,意味着存在缺失的边。这是一个纯图检查,可在写入RRD文件前执行。
parent_frame -> child_frame - 解决所有缺失的边,或明确报错。对于每条缺失的边:
- 若校准数据提供了变换,则记录它(见下一节)。
- 若数据中没有,则树无法求解。不要让帧处于未连接状态(它会坍缩到原点,导致场景看起来是合并的)。要么终止并指出缺失的边,要么记录单位变换并发出明确警告说明假设的边。请明确说明您采取的操作。
- 跨源匹配帧名称。FK派生的/
parent_frame必须与child_frame输出的静态几何结构的帧名称一致,且校准边必须使用相同的名称,否则连杆会与网格分离。urdf.stream()是确保名称一致的关键,请在所有地方复用它。frame_prefix
正确的导入结果应只有一个根节点,且所有帧都能到达该根节点。
Logging a connection correctly
正确记录连接边
A connecting edge is a static carrying the bridging frame
names. Static (no time index) so it holds for the whole recording; the frame
names, not the entity path, are what create the graph edge. This
is the rare sidecar exception — a calibration transform
no reader or FK lens can produce; do not generalize it to transforms a reader
emits (a topic → ) or that FK derives:
Transform3DChunk.from_columnsframe_transformsTransform3Dpython
import rerun as rr
from rerun.experimental import Chunk, LazyChunkStream连接边是一个静态的,包含桥接帧的名称。静态(无时间索引)意味着它在整个录制过程中有效;帧名称而非实体路径才是创建图边的关键。以下是罕见的附加数据例外——校准变换无法由读取器或FK透镜生成;请勿将其推广到读取器输出的变换(如主题→)或FK派生的变换:
Transform3DChunk.from_columnsframe_transformsTransform3Dpython
import rerun as rr
from rerun.experimental import Chunk, LazyChunkStreamworld -> this robot's base, from your calibration (translation + xyzw quaternion)
世界帧 -> 机器人基础帧,来自校准数据(平移 + xyzw四元数)
edge = Chunk.from_columns(
"/world/robot_base", # any sensible bridging entity path
indexes=[], # no index == static
columns=rr.Transform3D.columns(
translation=[translation],
quaternion=[quaternion_xyzw],
parent_frame=["world"], # must match the root frame name
child_frame=["arm_base_link"], # must match the URDF root frame (prefixed)
),
)
edges = LazyChunkStream.from_iter([edge]) # merge alongside model + FK streams
Log a fixed-chain result (step 3) the same way, with `parent_frame` and
`child_frame` set to the two link frames the chain spans. Merge all edge chunks
into the same recording as the model and FK streams so they share the graph.edge = Chunk.from_columns(
"/world/robot_base", # 任何合理的桥接实体路径
indexes=[], # 无索引 == 静态
columns=rr.Transform3D.columns(
translation=[translation],
quaternion=[quaternion_xyzw],
parent_frame=["world"], # 必须匹配根帧名称
child_frame=["arm_base_link"], # 必须匹配URDF根帧(已添加前缀)
),
)
edges = LazyChunkStream.from_iter([edge]) # 与模型流 + FK流合并
记录固定链结果(步骤3)的方式相同,将`parent_frame`和`child_frame`设置为链所跨越的两个连杆帧。将所有边数据块合并到与模型流和FK流相同的录制中,以确保它们共享同一个图。Minimal shape (one robot, generic joint source)
最小实现结构(单个机器人,通用关节数据源)
python
import rerun as rr
from rerun.experimental import DeriveLens, LazyChunkStream, OptimizationProfile, Selector
from rerun.urdf import UrdfTree
urdf = UrdfTree.from_file_path(urdf_path, entity_path_prefix="robot", static_transform_entity_path="robot/tf_static")
model = urdf.stream(include_joint_transforms=True).drop(content="/robot/**/collision_geometries/**")
joints = source_joint_state_stream() # your reader; one message column of names+values
fk = (
joints
.lenses(
DeriveLens(JOINT_MSG_COMPONENT, output_entity="/tmp/batches").to_component(
"rerun.urdf.JointTransformBatch",
Selector(".").pipe(lambda msgs: urdf.compute_joint_transform_batches(read_names(msgs), read_values(msgs))),
),
content=JOINT_SOURCE_PATH,
output_mode="forward_all",
)
.lenses(
DeriveLens("rerun.urdf.JointTransformBatch", output_entity="/robot/transforms", scatter=True)
.to_component(rr.Transform3D.descriptor_translation(), Selector("[].translation"))
.to_component(rr.Transform3D.descriptor_quaternion(), Selector("[].quaternion"))
.to_component(rr.Transform3D.descriptor_parent_frame(), Selector("[].parent_frame"))
.to_component(rr.Transform3D.descriptor_child_frame(), Selector("[].child_frame")),
content="/tmp/batches",
output_mode="drop_unmatched",
)
.filter(content="/robot/transforms")
)
LazyChunkStream.merge(model, fk).collect(optimize=OptimizationProfile.OBJECT_STORE).write_rrd(
out_path,
application_id="urdf",
recording_id=segment_id,
)read_namesread_valuesJOINT_MSG_COMPONENTJOINT_SOURCE_PATHpython
import rerun as rr
from rerun.experimental import DeriveLens, LazyChunkStream, OptimizationProfile, Selector
from rerun.urdf import UrdfTree
urdf = UrdfTree.from_file_path(urdf_path, entity_path_prefix="robot", static_transform_entity_path="robot/tf_static")
model = urdf.stream(include_joint_transforms=True).drop(content="/robot/**/collision_geometries/**")
joints = source_joint_state_stream() # 您的读取器;包含名称+值的消息列
fk = (
joints
.lenses(
DeriveLens(JOINT_MSG_COMPONENT, output_entity="/tmp/batches").to_component(
"rerun.urdf.JointTransformBatch",
Selector(".").pipe(lambda msgs: urdf.compute_joint_transform_batches(read_names(msgs), read_values(msgs))),
),
content=JOINT_SOURCE_PATH,
output_mode="forward_all",
)
.lenses(
DeriveLens("rerun.urdf.JointTransformBatch", output_entity="/robot/transforms", scatter=True)
.to_component(rr.Transform3D.descriptor_translation(), Selector("[].translation"))
.to_component(rr.Transform3D.descriptor_quaternion(), Selector("[].quaternion"))
.to_component(rr.Transform3D.descriptor_parent_frame(), Selector("[].parent_frame"))
.to_component(rr.Transform3D.descriptor_child_frame(), Selector("[].child_frame")),
content="/tmp/batches",
output_mode="drop_unmatched",
)
.filter(content="/robot/transforms")
)
LazyChunkStream.merge(model, fk).collect(optimize=OptimizationProfile.OBJECT_STORE).write_rrd(
out_path,
application_id="urdf",
recording_id=segment_id,
)read_namesread_valuesJOINT_MSG_COMPONENTJOINT_SOURCE_PATHGotchas that cause real failures
导致实际故障的常见陷阱
- Empty layer, no error: dead joint-state source (decoded to zero rows; see the importer skill for your format) or wrong /component name.
JOINT_SOURCE_PATH - Confident wrong pose: joint count, order, or units wrong.
- Layer writes but never attaches: .
recording_id != segment_id - Frames collide: two robots sharing a .
frame_prefix - Scene looks merged at the origin: unconnected roots logged as identity without a calibration edge.
- Catalog ingest rejects or misorders chunks: optimization skipped.
OBJECT_STORE
- 图层为空但无错误:关节状态数据源失效(解码为零行;请查看对应格式的导入技能)或/组件名称错误。
JOINT_SOURCE_PATH - 姿态明显错误:关节数量、顺序或单位错误。
- 图层已写入但无法关联:。
recording_id != segment_id - 帧冲突:两个机器人共享同一个。
frame_prefix - 场景看起来在原点合并:未连接的根节点被记录为单位变换,且未添加校准边。
- 目录导入拒绝或乱序数据块:跳过了优化。
OBJECT_STORE
References
参考链接
- (FK two-lens pattern, two robots + scene URDFs, prefixes, recoloring, calibration offsets)
https://github.com/rerun-io/rerun/tree/main/examples/python/robot_data_preprocessing - (classic logging API:
https://github.com/rerun-io/rerun/tree/main/examples/python/animated_urdf, per-jointlog_urdf_to_recording)compute_transform - (the mapping table this skill consumes)
rerun-data-model - the importer skill for your joint-state source format (making the source readable)
- (lens/stream, write/optimize mechanics)
rerun-chunk-processing
- (双透镜FK模式、两个机器人+场景URDF、前缀、重着色、校准偏移)
https://github.com/rerun-io/rerun/tree/main/examples/python/robot_data_preprocessing - (传统日志API:
https://github.com/rerun-io/rerun/tree/main/examples/python/animated_urdf、单关节log_urdf_to_recording)compute_transform - (本技能依赖的映射表格)
rerun-data-model - 关节状态数据源格式对应的导入技能(确保数据源可读)
- (透镜/流机制、写入/优化方法)
rerun-chunk-processing