chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<!--[metadata]
|
||||
title = "URDF"
|
||||
tags = ["3D", "Mesh", "URDF", "Animation"]
|
||||
channel = "main"
|
||||
include_in_manifest = true
|
||||
thumbnail = "https://static.rerun.io/animated-urdf-thumbnail/02cd73ad1155db0a202392b1fd8f8036070ad888/480w.png"
|
||||
thumbnail_dimensions = [480, 480]
|
||||
-->
|
||||
|
||||
<picture>
|
||||
<img src="https://static.rerun.io/animated_urdf/ebdefa158ab6f26f9dc1cb1924fce4b846fe8db2/full.png" alt="">
|
||||
<source media="(max-width: 480px)" srcset="https://static.rerun.io/animated_urdf/ebdefa158ab6f26f9dc1cb1924fce4b846fe8db2/480w.png">
|
||||
<source media="(max-width: 768px)" srcset="https://static.rerun.io/animated_urdf/ebdefa158ab6f26f9dc1cb1924fce4b846fe8db2/768w.png">
|
||||
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/animated_urdf/ebdefa158ab6f26f9dc1cb1924fce4b846fe8db2/1024w.png">
|
||||
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/animated_urdf/ebdefa158ab6f26f9dc1cb1924fce4b846fe8db2/1200w.png">
|
||||
</picture>
|
||||
|
||||
An example of how to load and animate a URDF given some changing joint angles.
|
||||
|
||||
## Logging and visualizing with Rerun
|
||||
|
||||
This example demonstrates how to:
|
||||
|
||||
1. Load and log a URDF file as a static resource
|
||||
2. Parse the URDF structure using `UrdfTree`
|
||||
3. Animate joints by logging dynamic transforms
|
||||
|
||||
The key steps are:
|
||||
|
||||
```python
|
||||
import rerun as rr
|
||||
import rerun.urdf import UrdfTree
|
||||
|
||||
# Log the URDF file once, as a static resource
|
||||
rec.log_file_from_path(urdf_path, static=True)
|
||||
|
||||
# Load the URDF tree structure into memory
|
||||
urdf_tree = UrdfTree.from_file_path(urdf_path)
|
||||
|
||||
# Animate joints by logging transforms
|
||||
for joint in urdf_tree.joints():
|
||||
if joint.joint_type == "revolute":
|
||||
# compute_transform gives you a complete transform that is ready to log,
|
||||
# calculated from joint origin and the current angle and with the frame names set.
|
||||
transform = joint.compute_transform(angle)
|
||||
rec.log("transforms", transform)
|
||||
```
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
An example of how to load and animate a URDF given some changing joint angles.
|
||||
|
||||
Usage:
|
||||
python -m animated_urdf
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
TIMELINE = "example_time"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="An example of how to load and animate a URDF given some changing joint angles.",
|
||||
)
|
||||
rr.script_add_args(parser)
|
||||
parser.add_argument(
|
||||
"--dual",
|
||||
action="store_true",
|
||||
help="Load the same URDF twice with different frame prefixes (dual-arm demo).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
urdf_path = Path(__file__).parent.parent.parent / "rust" / "animated_urdf" / "data" / "so100.urdf"
|
||||
|
||||
if args.dual:
|
||||
run_dual(args, urdf_path)
|
||||
else:
|
||||
run_single(args, urdf_path)
|
||||
|
||||
|
||||
def run_single(args: argparse.Namespace, urdf_path: Path) -> None:
|
||||
duration = 0.0
|
||||
rec = rr.script_setup(args, "rerun_example_animated_urdf")
|
||||
rec.set_time(TIMELINE, duration=duration)
|
||||
|
||||
# Log the URDF file once
|
||||
rec.log_file_from_path(urdf_path)
|
||||
|
||||
# Load the URDF tree structure into memory
|
||||
urdf_tree = rr.urdf.UrdfTree.from_file_path(urdf_path)
|
||||
|
||||
# Hide the collision geometries by default in the viewer.
|
||||
blueprint = rrb.Grid(
|
||||
rrb.Spatial3DView(
|
||||
name="Animated URDF", overrides={"so_arm100/collision_geometries": rrb.EntityBehavior(visible=False)}
|
||||
)
|
||||
)
|
||||
rec.send_blueprint(blueprint)
|
||||
|
||||
for step in range(10000):
|
||||
for joint_index, joint in enumerate(urdf_tree.joints()):
|
||||
if joint.joint_type == "revolute":
|
||||
# Usually this angle would come from a measurement, here we just fake something
|
||||
dynamic_angle = _fake_angle(joint, step, joint_index, phase=0.0)
|
||||
|
||||
# Rerun loads the URDF transforms with child/parent frame relations.
|
||||
# To move a joint, we just need to log a new transform between those frames.
|
||||
# Here, we use the `compute_transform` method that automatically takes care
|
||||
# of setting the frame names and calculating the full transform from the joint angle.
|
||||
transform = joint.compute_transform(dynamic_angle, clamp=True)
|
||||
rec.log("transforms", transform)
|
||||
|
||||
# We can also work with links from the URDF tree.
|
||||
# Here, we change the color of the visual mesh entities of the "jaw" link based on the joint angle.
|
||||
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)
|
||||
|
||||
rr.script_teardown(args)
|
||||
|
||||
|
||||
def run_dual(args: argparse.Namespace, urdf_path: Path) -> None:
|
||||
"""Load the same URDF twice with different frame prefixes (dual-arm demo)."""
|
||||
rec = rr.script_setup(args, "rerun_example_animated_urdf")
|
||||
|
||||
# Load the same URDF twice with different prefixes.
|
||||
# - entity_path_prefix separates geometry in the entity tree
|
||||
# - frame_prefix makes frame IDs unique ("left/base", "right/base", …)
|
||||
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/")
|
||||
|
||||
# Log both robots (geometry + static transforms with prefixed frame IDs).
|
||||
left.log_urdf_to_recording()
|
||||
right.log_urdf_to_recording()
|
||||
|
||||
# Offset the arms so they don't overlap.
|
||||
rec.log("left", rr.Transform3D(translation=[-0.2, 0, 0]), static=True)
|
||||
rec.log("right", rr.Transform3D(translation=[0.2, 0, 0]), static=True)
|
||||
|
||||
blueprint = rrb.Grid(
|
||||
rrb.Spatial3DView(
|
||||
name="Dual Arm",
|
||||
overrides={
|
||||
"left/so_arm100/collision_geometries": rrb.EntityBehavior(visible=False),
|
||||
"right/so_arm100/collision_geometries": rrb.EntityBehavior(visible=False),
|
||||
},
|
||||
)
|
||||
)
|
||||
rec.send_blueprint(blueprint)
|
||||
|
||||
for step in range(10000):
|
||||
rec.set_time("step", sequence=step)
|
||||
|
||||
for joint_index, joint in enumerate(left.joints()):
|
||||
if joint.joint_type == "revolute":
|
||||
angle = _fake_angle(joint, step, joint_index, phase=0.0)
|
||||
rec.log("left/joint_transforms", joint.compute_transform(angle, clamp=True))
|
||||
|
||||
for joint_index, joint in enumerate(right.joints()):
|
||||
if joint.joint_type == "revolute":
|
||||
angle = _fake_angle(joint, step, joint_index, phase=2.0)
|
||||
rec.log("right/joint_transforms", joint.compute_transform(angle, clamp=True))
|
||||
|
||||
rr.script_teardown(args)
|
||||
|
||||
|
||||
def _fake_angle(joint: rr.urdf.UrdfJoint, step: int, joint_index: int, phase: float) -> float:
|
||||
"""Generate a smooth oscillating angle within the joint's limits."""
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "animated_urdf"
|
||||
version = "0.1.0"
|
||||
readme = "README.md"
|
||||
dependencies = ["rerun-sdk"]
|
||||
|
||||
[project.scripts]
|
||||
animated_urdf = "animated_urdf:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
Reference in New Issue
Block a user