URDF Assembly Tool#
The URDF Assembly Tool is a modular system for building and assembling Unified Robot Description Format (URDF) files for robotic systems. It enables users to combine individual robot components (chassis, arms, legs, sensors, etc.) into a complete robot description.
Overview#
The tool provides a programmatic way to:
Add robot components: Import URDF files for different robot parts (chassis, legs, torso, head, arms, hands)
Attach sensors: Add sensors (camera, lidar, IMU, GPS, force/torque) to specific components
Merge URDFs: Combine multiple component URDFs into a single unified robot description
Apply transformations: Position and orient components using 4x4 transformation matrices
Validate assemblies: Ensure URDF integrity and format compliance
Cache assemblies: Use signature checking to skip redundant processing
Quick Start#
from pathlib import Path
import numpy as np
from embodichain.toolkits.urdf_assembly import URDFAssemblyManager
# Initialize the assembly manager
manager = URDFAssemblyManager()
# Add robot components
manager.add_component(
component_type="chassis",
urdf_path="path/to/chassis.urdf",
)
manager.add_component(
component_type="torso",
urdf_path="path/to/torso.urdf",
transform=np.eye(4) # 4x4 transformation matrix
)
manager.add_component(
component_type="left_arm",
urdf_path="path/to/arm.urdf"
)
# Attach sensors
manager.attach_sensor(
sensor_name="front_camera",
sensor_source="path/to/camera.urdf",
parent_component="chassis",
parent_link="base_link",
transform=np.eye(4)
)
# Merge all components into a single URDF
manager.merge_urdfs(output_path="assembly_robot.urdf")
Supported Components#
The tool supports the following robot component types:
Component |
Description |
|---|---|
|
Base platform of the robot |
|
Leg system for legged robots |
|
Main body/torso section |
|
Head/upper section |
|
Left arm manipulator |
|
Right arm manipulator |
|
Left end-effector/gripper |
|
Right end-effector/gripper |
|
Single arm (bimanual robots) |
|
Single end-effector/gripper |
Wheel Types#
For chassis components, the following wheel types are supported:
omni: Omnidirectional wheelsdifferential: Differential drivetracked: Tracked locomotion
Supported Sensors#
The following sensor types can be attached to robot components:
Sensor |
Description |
|---|---|
|
RGB/depth cameras |
|
2D/3D LIDAR sensors |
|
Inertial measurement units |
|
GPS receivers |
|
Force/torque sensors |
Connection Rules#
The tool automatically generates connection rules based on available components. The default rules include:
Chassis → Legs → Torso (for legged robots)
Chassis → Torso (for wheeled robots)
Torso → Head
Torso → Arms → Hands
Chassis → Arms (when no torso exists)
Mesh Formats#
Supported mesh file formats for visual and collision geometries:
STL
OBJ
PLY
DAE
GLB
API Reference#
URDFAssemblyManager#
Located in: embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py
The main class for managing URDF assembly operations.
Methods#
add_component()#
Add a URDF component to the component registry.
manager.add_component(
component_type: str,
urdf_path: Union[str, Path],
transform: np.ndarray = None,
**params
) -> bool
Parameters:
component_type(str): Type of component (e.g., ‘chassis’, ‘head’)urdf_path(str or Path): Path to the component’s URDF filetransform(np.ndarray, optional): 4x4 transformation matrix for positioning**params: Additional component-specific parameters (e.g.,wheel_typefor chassis)
Returns: bool - True if component added successfully
attach_sensor()#
Attach a sensor to a specific component and link.
manager.attach_sensor(
sensor_name: str,
sensor_source: Union[str, ET.Element],
parent_component: str,
parent_link: str,
transform: np.ndarray = None,
**kwargs
) -> bool
Parameters:
sensor_name(str): Unique name for the sensorsensor_source(str or ET.Element): Path to sensor URDF or XML elementparent_component(str): Component to attach sensor toparent_link(str): Link within the parent componenttransform(np.ndarray, optional): Sensor transformation matrix
Returns: bool - True if sensor attached successfully
merge_urdfs()#
Merge all registered components into a single URDF file.
manager.merge_urdfs(
output_path: str = "./assembly_robot.urdf",
use_signature_check: bool = True
) -> ET.Element
Parameters:
output_path(str): Path where the merged URDF will be saveduse_signature_check(bool): Whether to check signatures to avoid redundant processing
Returns: ET.Element - Root element of the merged URDF
get_component()#
Retrieve a registered component by type.
manager.get_component(component_type: str) -> URDFComponent | None
get_attached_sensors()#
Get all attached sensors.
manager.get_attached_sensors() -> dict
Component name prefixes (component_prefix)#
URDFAssemblyManager uses component_prefix to configure name prefixes for
each supported component type. This attribute is a list of 2-tuples:
Form:
[(component_name, prefix), ...]The default value is:
[ ("chassis", None), ("legs", None), ("torso", None), ("head", None), ("left_arm", "left_"), ("right_arm", "right_"), ("left_hand", "left_"), ("right_hand", "right_"), ("arm", None), ("hand", None), ]
You can configure it in a patch-style manner via the property:
# Only override prefixes for existing components; do not introduce
# new component names.
manager.component_prefix = [
("left_arm", "L_"),
("right_arm", "R_"),
("left_hand", "L_"),
("right_hand", "R_"),
]
Semantics:
Only components that already exist in the default configuration (e.g.
chassis/torso/left_arm/...) may be overridden; new component names are not allowed.Components not listed in
new_prefixeskeep their original prefix.If
new_prefixescontains an unknown component name, aValueErroris raised indicating that new component types cannot be introduced.
Name casing policy (name_case)#
URDFAssemblyManager supports a global name casing policy that controls how
link and joint names are normalized during assembly. This is configured on
the manager instance after construction:
manager = URDFAssemblyManager()
manager.name_case = {
"joint": "upper", # or "lower" / "none"
"link": "lower", # or "upper" / "none"
}
Semantics:
- Valid keys: `"joint"`, `"link"`.
- Valid values: `"upper"`, `"lower"`, `"none"`.
- Default behavior matches the legacy implementation:
- joints are normalized to **UPPERCASE**,
- links are normalized to **lowercase**.
- This policy is propagated to the internal component and connection managers,
and is also included in the assembly signature. Changing `name_case` will
therefore force a rebuild of the assembled URDF.
## Using with URDFCfg for Robot Creation
The URDF Assembly Tool can be used directly with `URDFCfg` to create robots with multiple components in the simulation. This is the recommended approach when building robots from assembled URDF files.
### URDFCfg Overview
The `URDFCfg` class provides a convenient way to define multi-component robots:
```python
from embodichain.lab.sim.cfg import RobotCfg, URDFCfg
cfg = RobotCfg(
uid="my_robot",
urdf_cfg=URDFCfg(
components=[
{
"component_type": "arm",
"urdf_path": "path/to/arm.urdf",
},
{
"component_type": "hand",
"urdf_path": "path/to/hand.urdf",
"transform": hand_transform, # 4x4 transformation matrix
},
]
),
control_parts={...},
drive_pros={...},
)
When using URDFCfg to build multi-component robots, you can pass custom
component prefixes to the internal URDFAssemblyManager via
URDFCfg.component_prefix. Its semantics are identical to
URDFAssemblyManager.component_prefix:
Each element is a
(component_name, prefix)tuple.Only prefixes for components that exist in the default configuration may be overridden; no new component names can be added.
Components not explicitly listed keep their original prefix.
Example:
urdf_cfg = URDFCfg(
components=[...],
)
urdf_cfg.component_prefix = [
("left_arm", "L_"),
("right_arm", "R_"),
]
Complete Example#
Here’s a complete example from scripts/tutorials/sim/create_robot.py:
import numpy as np
import torch
from scipy.spatial.transform import Rotation as R
from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
from embodichain.lab.sim.objects import Robot
from embodichain.lab.sim.cfg import (
JointDrivePropertiesCfg,
RobotCfg,
URDFCfg,
)
from embodichain.data import get_data_path
def create_robot(sim):
"""Create and configure a robot with arm and hand components."""
# Get URDF paths for robot components
arm_urdf_path = get_data_path("Rokae/SR5/SR5.urdf")
hand_urdf_path = get_data_path(
"BrainCoHandRevo1/BrainCoLeftHand/BrainCoLeftHand.urdf"
)
# Define control parts - joint names can be regex patterns
CONTROL_PARTS = {
"arm": ["JOINT[1-6]"], # Matches JOINT1, JOINT2, ..., JOINT6
"hand": ["LEFT_.*"], # Matches all joints starting with LEFT_
}
# Define transformation for hand attachment
hand_transform = np.eye(4)
hand_transform[:3, :3] = R.from_rotvec([90, 0, 0], degrees=True).as_matrix()
# Create robot configuration
cfg = RobotCfg(
uid="sr5_with_hand",
urdf_cfg=URDFCfg(
components=[
{
"component_type": "arm",
"urdf_path": arm_urdf_path,
},
{
"component_type": "hand",
"urdf_path": hand_urdf_path,
"transform": hand_transform,
},
]
),
control_parts=CONTROL_PARTS,
drive_pros=JointDrivePropertiesCfg(
stiffness={"JOINT[1-6]": 1e4, "LEFT_.*": 1e3},
damping={"JOINT[1-6]": 1e3, "LEFT_.*": 1e2},
),
)
# Add robot to simulation
robot: Robot = sim.add_robot(cfg=cfg)
return robot
# Initialize simulation and create robot
sim = SimulationManager(SimulationManagerCfg(headless=True, num_envs=4))
robot = create_robot(sim)
print(f"Robot created with {robot.dof} joints")
import numpy as np
import torch
from scipy.spatial.transform import Rotation as R
from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
from embodichain.lab.sim.objects import Robot
from embodichain.lab.sim.cfg import (
JointDrivePropertiesCfg,
RobotCfg,
URDFCfg,
)
from embodichain.data import get_data_path
def create_robot(sim):
"""Create and configure a robot with arm and hand components."""
# Get URDF paths for robot components
arm_urdf_path = get_data_path("Rokae/SR5/SR5.urdf")
hand_urdf_path = get_data_path(
"BrainCoHandRevo1/BrainCoLeftHand/BrainCoLeftHand.urdf"
)
# Define transformation for hand attachment
hand_transform = np.eye(4)
hand_transform[:3, :3] = R.from_rotvec([90, 0, 0], degrees=True).as_matrix()
left_arm_base_xpos = np.eye(4)
left_arm_base_xpos[1, 3] = 0.3
right_arm_base_xpos = np.eye(4)
right_arm_base_xpos[1, 3] = -0.3
# Create robot configuration
cfg = RobotCfg(
uid="dual_sr5",
urdf_cfg=URDFCfg(
components=[
{
"component_type": "left_arm",
"urdf_path": arm_urdf_path,
"transform": left_arm_base_xpos,
},
{
"component_type": "right_arm",
"urdf_path": arm_urdf_path,
"transform": right_arm_base_xpos,
},
{
"component_type": "left_hand",
"urdf_path": hand_urdf_path,
"transform": hand_transform,
},
{
"component_type": "right_hand",
"urdf_path": hand_urdf_path,
"transform": hand_transform,
},
],
component_prefix=[("left_arm", "L_"), ("right_arm", "R_"), ("left_hand", "left_"), ("right_hand", "right_")],
name_case={
"joint": "lower",
"link": "lower",
}
),
)
# Add robot to simulation
robot: Robot = sim.add_robot(cfg=cfg)
return robot
# Initialize simulation and create robot
sim = SimulationManager(SimulationManagerCfg(headless=True, num_envs=4))
robot = create_robot(sim)
print(f"Robot created with {robot.dof} joints")
Component Configuration#
Each component in the components list supports the following keys:
Key |
Type |
Description |
|---|---|---|
|
str |
Type of component (arm, hand, chassis, etc.) |
|
str |
Path to the component’s URDF file |
|
np.ndarray |
Optional 4x4 transformation matrix for positioning |
Control Parts#
Control parts define which joints belong to different subsystems of the robot. Joint names can be specified as:
Exact match:
"JOINT1"matches onlyJOINT1Regex patterns:
"JOINT[1-6]"matchesJOINT1throughJOINT6Wildcards:
"LEFT_.*"matches all joints starting withLEFT_
Drive Properties#
Drive properties control the joint behavior:
stiffness: Position control gain (P gain) for each joint/groupdamping: Velocity control gain (D gain) for each joint/group
Both support regex pattern matching for convenient configuration.
File Structure#
embodichain/toolkits/urdf_assembly/
├── __init__.py # Package exports
├── urdf_assembly_manager.py # Main assembly manager
├── component.py # Component classes and registry
├── sensor.py # Sensor attachment and registry
├── connection.py # Connection/joint management
├── mesh.py # Mesh file handling
├── file_writer.py # URDF file output
├── signature.py # Assembly signature checking
├── logging_utils.py # Logging configuration
└── utils.py # Utility functions