Skip to main content

Loading from URDF

Loads a robot directly from a .urdf file and simulates it. Collision meshes can be plain .stl/.obj/.ply/.off files with no preprocessing required; an SDF collider is baked on the fly when the bot is created.

Source: examples/basic/example_urdf_loading.py

Pitfalls of the runtime URDF loader

The runtime URDF loader only understands mesh collision geometry and silently ignores primitive shapes (<box>, <cylinder>, <sphere>). The bundled FR3 URDF defines its gripper fingers as <box> primitives, so the loaded fingers have no collision geometry.

Baking an SDF on the fly also assumes each collision mesh is a closed (watertight) surface. Raw URDF meshes often are not, so loading may warn that a collider mesh is not topologically closed. The SDF sign may then be incorrect, causing unreliable collision detection near the open boundaries. The default FR3 URDF trips this on some links.

For production assets, prefer importing URDFs through SuperDex Studio. Its importer remeshes collision (or visual) geometry, bakes watertight SDFs offline, and emits a .superdex_bot with preprocessed assets. This runtime path is best for quick prototyping.

Key Concepts

The URDF file

The example resolves the bundled FR3 test URDF, but any .urdf path works:

from superdex.physics.paths import resolve_asset

urdf_path = str(resolve_asset("test/urdf/fr3v2_1_urdf/robots/fr3v2_1_franka_hand.urdf"))

Initializing the engine and scene

Initialize the physics engine before creating scenes or actors. num_worker_threads=0 runs single-threaded; pass -1 to auto-select. SuperDex robots use a Z-up convention, so gravity points down the -Z axis:

physics.initialize(num_worker_threads=0)

scene = physics.create_scene("URDF Bot Loading Example")
scene.set_gravity([0, 0, -9.81])

Parsing the URDF and welding the base

Parsing the URDF produces a bot prefab: a template describing links and joints. Collision mesh paths are kept as-is; no mesh conversion happens here:

bot_prefab = robotics.load_bot_prefab_from_urdf_file(urdf_path)

URDF loading adds a free world_joint at index 0, leaving the base free to fall. Making it a HARD (0-DoF weld) joint anchors the base to the world. The arm still sags under gravity since there is no controller:

bot_prefab.joints[0].type = physics.ArticulatedJointType.HARD

Instantiating the bot

Instantiating the prefab builds the robot's articulated actor and bakes each link's SDF collider from its mesh. The robotics context tracks every bot and controller you create:

robotics_context = robotics.create_context()
bot = robotics.create_bot(scene, bot_prefab, robotics_context)
bot_actor = bot.get_articulated_actor()

plane_shape = physics.create_plane_shape(normal=[0, 0, 1], distance=0)
scene.create_rigid_actor(name="ground", shape=plane_shape, is_static=True)

The prefab exposes the robot's links and joints, and the actor reports its degrees of freedom:

print(f"Robot: {bot_prefab.name}")
print(f" Links: {len(bot_prefab.links)}")
print(f" Joints: {len(bot_prefab.joints)}")
print(f" DOFs: {bot_actor.get_num_dofs()}")

Simulating with the debugger

Simulate at 60 Hz. Declare the scene's coordinate convention so the debugger renders it the right way up: SuperDex is X-forward, Y-left, Z-up (FLU). This must come before attach(), which starts the server. The loop runs until you close the debugger; attach() returns False if it can't connect:

time_step = 1.0 / 60.0

physics.get_debug_server().set_coordinate_space(
physics.CoordinateSpace(axes=physics.CoordinateSpaceAxes.FLU)
)

if physics.debugger.attach():
while physics.debugger.is_attached():
scene.step(time_step)

Teardown

Destroy the bot, then shut the engine down cleanly:

robotics.destroy_bot(scene, bot)
physics.shutdown()

Running

uv run python superdex_robotics/examples/basic/example_urdf_loading.py

To load your own robot, pass a path to a .urdf file:

uv run python superdex_robotics/examples/basic/example_urdf_loading.py /path/to/robot.urdf