Python SDK for RoboInfra Robotics API for URDF validation, kinematic analysis, 3D model conversion and mesh quality analysis.
API: https://roboinfra-api.azurewebsites.net
Dashboard: https://roboinfra-dashboard.azurewebsites.net
PyPI: https://pypi.org/project/roboinfra-sdk/
GitHub Action: uses: roboinfra/validate-urdf-action@v1
pip install roboinfra-sdkimport roboinfra as roboinfra
# Create client with your API key from the dashboard
client = roboinfra.Client("rk_your_api_key_here")
# 1. Check API health (no auth needed)
status = client.health()
print(status["status"]) # "Healthy"
# 2. Validate a URDF file (Free plan)
result = client.urdf.validate("robot.urdf")
if result.is_valid:
print("URDF is valid!")
else:
for error in result.errors:
print(f" Error: {error}")
# 3. Kinematic analysis (Basic + Pro plan)
analysis = client.urdf.analyze("robot.urdf")
print(f"DOF: {analysis.dof}")
print(f"End effectors: {analysis.end_effectors}")
# 4. Convert 3D model (Pro plan)
output = client.model.convert("robot.obj", "fbx", "robot.fbx")
print(f"Saved: {output}")
# 5. Mesh quality analysis (Pro plan)
mesh = client.model.analyze("robot.stl")
print(f"Triangles: {mesh.total_triangles}, Watertight: {mesh.is_watertight}")
# 6. URDF → SDF/MJCF conversion (Pro plan)
sdf = client.urdf.convert_format("robot.urdf", "sdf")
with open("robot.sdf", "w") as f:
f.write(sdf.converted_xml)
print(f"Converted to SDF: {sdf.link_count} links, {sdf.joint_count} joints")
mjcf = client.urdf.convert_format("robot.urdf", "mjcf")
with open("robot.xml", "w") as f:
f.write(mjcf.converted_xml)
print(f"Converted to MJCF: {mjcf.link_count} links, {mjcf.joint_count} joints")
# 7. MoveIt config generation (Pro plan)
cfg = client.urdf.moveit_config("robot.urdf")
with open("config/kinematics.yaml", "w") as f:
f.write(cfg.kinematics_yaml)
with open(f"config/{cfg.robot_name}.srdf", "w") as f:
f.write(cfg.srdf)
print(f"MoveIt: {len(cfg.planning_groups)} groups, "
f"{cfg.movable_joint_count} movable joints")
# 8. Auto-fix a broken URDF (Basic + Pro plan)
fix = client.urdf.auto_fix("broken_robot.urdf")
if not fix.was_already_valid:
with open("fixed_robot.urdf", "w") as f:
f.write(fix.fixed_urdf)
print(f"Auto-fix: {fix.total_fixes} fix(es) applied")
# 9. Convert URDF to USD for NVIDIA Isaac Sim (Pro plan)
usd = client.urdf.to_usd("robot.urdf")
with open(f"{usd.robot_name}.usda", "w") as f:
f.write(usd.usda_content)
print(f"USD: {usd.articulated_joint_count} articulated joints")- Register at roboinfra-dashboard.azurewebsites.net
- Go to API Keys → Create key
- Keys start with
rk_ - Free plan: 50 calls/month for URDF validation no credit card required
Check API connectivity and service status.
result = client.health()
# Returns dict:
# {
# "status": "Healthy",
# "version": "1.0.0",
# "uptime": "2d 4h 12m"
# }Validates a URDF file against 9 structural checks.
result = client.urdf.validate("robot.urdf")
result.is_valid # bool True if all 9 checks pass
result.errors # list empty when valid, list of strings when invalid9 checks performed:
- Root element must be
<robot> - At least one
<link>must exist - No duplicate link names
- No duplicate joint names
- All joint
parentlinks must reference a defined link - All joint
childlinks must reference a defined link - Joint
typemust be valid (revolute, continuous, prismatic, fixed, floating, planar) revoluteandprismaticjoints must include<limit>- Exactly one root link (no cycles, no orphans)
Valid URDF example:
result = client.urdf.validate("robot.urdf")
# result.is_valid → True
# result.errors → []Invalid URDF example:
result = client.urdf.validate("bad_robot.urdf")
# result.is_valid → False
# result.errors → [
# "Root element must be <robot>",
# "At least one <link> must exist"
# ]Kinematic analysis DOF, joint chain, end effectors.
result = client.urdf.analyze("robot.urdf")
result.robot_name # str name from <robot name="...">
result.link_count # int total links
result.joint_count # int total joints
result.dof # int degrees of freedom (non-fixed joints)
result.max_chain_depth # int longest kinematic chain
result.root_link # str root link name
result.end_effectors # list end effector link names
result.joints # list joint detailsExample:
result = client.urdf.analyze("robot.urdf")
# result.robot_name → "sample_arm"
# result.dof → 2
# result.end_effectors → ["tool0"]
# result.joints[0] → {"name": "joint_1", "type": "revolute",
# "parent": "base_link", "child": "link_1"}Convert URDF to Gazebo SDF or MuJoCo MJCF format.
# Convert to Gazebo SDF
sdf = client.urdf.convert_format("robot.urdf", "sdf")
with open("robot.sdf", "w") as f:
f.write(sdf.converted_xml)
# Convert to MuJoCo MJCF
mjcf = client.urdf.convert_format("robot.urdf", "mjcf")
with open("robot.xml", "w") as f:
f.write(mjcf.converted_xml)
# Check warnings (e.g. mesh file references)
for w in mjcf.warnings:
print(f" Warning: {w}")
sdf.target_format # str — "sdf" or "mjcf"
sdf.robot_name # str — robot name from URDF
sdf.converted_xml # str — the output XML
sdf.link_count # int
sdf.joint_count # int
sdf.warnings # list — non-fatal notesSupported formats: "sdf" (Gazebo Sim v1.7) and "mjcf" (MuJoCo / Google DeepMind).
What gets converted: all 6 joint types, inertia tensors, visual + collision geometry, materials, mesh references, joint limits/damping/friction, origin transforms. MJCF includes auto-generated actuators.
Generate MoveIt configuration starter files from a URDF — kinematics.yaml, joint_limits.yaml, an SRDF stub, and planning group suggestions. A faster starting point than the MoveIt Setup Assistant GUI (seconds instead of hours).
cfg = client.urdf.moveit_config("robot.urdf")
# Save the generated files into a moveit_config package
with open("config/kinematics.yaml", "w") as f:
f.write(cfg.kinematics_yaml)
with open("config/joint_limits.yaml", "w") as f:
f.write(cfg.joint_limits_yaml)
with open(f"config/{cfg.robot_name}.srdf", "w") as f:
f.write(cfg.srdf)
# Inspect detected planning groups
for g in cfg.planning_groups:
print(f"{g['name']}: {len(g['joints'])} joints "
f"({g.get('baseLink','')} -> {g.get('tipLink','')})")
cfg.robot_name # str
cfg.kinematics_yaml # str — config/kinematics.yaml (KDL solver)
cfg.joint_limits_yaml # str — config/joint_limits.yaml
cfg.srdf # str — config/<robot>.srdf
cfg.planning_groups # list — [{name, joints, baseLink, tipLink, reason}]
cfg.movable_joint_count # int
cfg.base_link # str
cfg.tip_link # str
cfg.warnings # listWhat it generates: KDL kinematics config per planning group, velocity limits read from the URDF (with sensible acceleration defaults), an SRDF stub with planning groups / virtual joint / home state / adjacent-link disable_collisions, and auto-detected planning groups (arm chain + gripper separated by joint name).
This is a starter, not a full replacement for the MoveIt Setup Assistant. Review the SRDF collision rules and tune acceleration limits for your hardware before real execution.
Validation tells you what's wrong — Auto-Fix repairs it. Returns a clean, valid URDF you can save and use immediately.
result = client.urdf.auto_fix("broken_robot.urdf")
if result.was_already_valid:
print("Nothing to fix — URDF was already valid.")
else:
print(f"Applied {result.total_fixes} fix(es):")
for fix in result.fixes_applied:
print(f" [{fix['category']}] {fix['description']}")
# Save the clean, repaired URDF (no manual cleanup needed)
with open("fixed_robot.urdf", "w") as f:
f.write(result.fixed_urdf)
# Issues that need manual attention (duplicate names, broken refs)
for issue in result.unfixable_issues:
print(f" MANUAL: {issue}")
result.robot_name # str
result.fixed_urdf # str — clean indented XML, ready to save
result.was_already_valid # bool
result.is_now_valid # bool
result.total_fixes # int
result.fixes_applied # list — [{category, target, description}]
result.unfixable_issues # list — needs human judgmentAuto-fixes: missing <inertial> blocks, zero/negative/missing mass, missing inertia tensors (computed from geometry), missing <limit> on revolute/prismatic joints, missing <axis>, invalid effort/velocity, multiple root links (orphan links welded with a fixed joint), and old Gazebo extension tags (<sensor:camera> etc.) that break XML parsing.
Reported but not auto-fixed (need human judgment): duplicate link/joint names, broken parent/child references, malformed XML, non-<robot> root.
Convert a URDF into an OpenUSD (.usda) stage for NVIDIA Isaac Sim / Isaac Lab. Isaac's built-in importer is notoriously fragile — this produces a clean, deterministic stage.
usd = client.urdf.to_usd("robot.urdf")
with open(f"{usd.robot_name}.usda", "w") as f:
f.write(usd.usda_content)
print(f"{usd.link_count} links, {usd.joint_count} joints, "
f"{usd.articulated_joint_count} articulated")
for w in usd.warnings:
print(" note:", w)
usd.robot_name # str
usd.usda_content # str — the .usda stage text
usd.link_count # int
usd.joint_count # int
usd.articulated_joint_count # int — drivable joints (RL)
usd.warnings # listGenerates: #usda 1.0 stage (meters, Z-up — Isaac default), PhysicsArticulationRootAPI (required for RL), rigid bodies with mass/inertia, UsdPhysics joints (revolute/prismatic/fixed), and PhysicsDriveAPI so policies can actuate joints. Revolute limits are emitted in degrees (USD convention). Mesh links produce an import-guidance warning. Open the .usda in Isaac Sim, usdview, or usdcat.
Convert 3D model files between formats. No Blender required.
output = client.model.convert("robot.stl", "obj", "robot.obj")
# Returns absolute path to saved output file
# All supported conversions:
# .fbx → obj, stl, glb, gltf
# .obj → stl, glb, gltf, fbx
# .stl → obj, glb, gltf
# .gltf → obj, stl, glb
# .glb → obj, stl, gltf
# .dae → obj, stl, glb
# .3ds → obj, stl, glb
# .blend→ obj, stl, glbExamples:
client.model.convert("robot.stl", "obj", "robot.obj")
client.model.convert("robot.obj", "fbx", "robot.fbx")
client.model.convert("robot.fbx", "glb", "robot.glb")
client.model.convert("robot.obj", "gltf", "robot.gltf")Mesh quality analysis for physics simulation readiness.
result = client.model.analyze("robot.stl")
result.mesh_count # int number of mesh objects
result.total_vertices # int total vertex count
result.total_triangles # int triangle count after triangulation
result.material_count # int number of materials
result.has_bones # bool True if skeletal data detected
result.is_watertight # bool True if ALL meshes are watertight (required for physics)
result.bounding_box # dict {"x": 0.42, "y": 0.38, "z": 0.75}
result.center_of_mass # dict {"x": 0.0, "y": 0.0, "z": 0.21}
result.meshes # list per-mesh breakdownExample:
result = client.model.analyze("robot_arm.stl")
# result.total_triangles → 30240
# result.is_watertight → True
# result.bounding_box → {"x": 0.42, "y": 0.38, "z": 0.75}
if not result.is_watertight:
print("WARNING: mesh has holes robot will fall through ground in Gazebo!")from roboinfra import (
RoboInfraError, # base any API error
AuthError, # 401 invalid API key
PlanError, # 403 endpoint requires higher plan
QuotaError, # 429 monthly quota exceeded
)
try:
result = client.urdf.validate("robot.urdf")
except AuthError:
print("Invalid API key get one at roboinfra-dashboard.azurewebsites.net/keys")
except PlanError:
print("Upgrade your plan at roboinfra-dashboard.azurewebsites.net/subscription")
except QuotaError:
print("Monthly quota exceeded upgrade or wait until next month")
except RoboInfraError as e:
print(f"API error: {e} (HTTP {e.status_code})")Note: 400 errors (bad file, oversized file) also count against your monthly quota. The SDK validates file size and extension locally before any HTTP call to avoid wasting quota on obvious errors.
| Plan | Price | Quota | Features |
|---|---|---|---|
| Free | $0/month | 50 calls | URDF validation only |
| Basic | $25/month | 500 calls | URDF validation + kinematic analysis |
| Pro | $75/month | 5,000 calls | All features (3D conversion + mesh analysis) |
| Endpoint | Max file size | Allowed extensions |
|---|---|---|
urdf.validate |
1 MB | .urdf |
urdf.analyze |
1 MB | .urdf |
model.convert |
20 MB | .fbx .obj .stl .gltf .glb .dae .3ds .blend |
model.analyze |
20 MB | .fbx .obj .stl .gltf .glb .dae .3ds .blend |
The SDK validates file size and extension locally before any HTTP call no wasted quota on obvious errors.
No Python required. Just add 3 lines to your workflow:
# .github/workflows/validate-urdf.yml
name: Validate URDF
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: roboinfra/validate-urdf-action@v1
with:
api-key: ${{ secrets.ROBOINFRA_API_KEY }}
file: urdf/robot.urdfFull docs: github.com/roboinfra/validate-urdf-action
Use this approach when you need custom logic multiple files, conditional checks, or integration with other tools:
- name: Validate URDF
run: |
pip install roboinfra-sdk
python - <<'EOF'
import roboinfra as roboinfra
import sys
client = roboinfra.Client("${{ secrets.ROBOINFRA_API_KEY }}")
result = client.urdf.validate("urdf/robot.urdf")
if not result.is_valid:
print("URDF validation failed:")
for e in result.errors:
print(f" - {e}")
sys.exit(1)
print("URDF valid!")
EOFMIT