using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using UnityEngine;
namespace URDF
{
///
/// Parser for URDF XML files
///
public static class URDFParser
{
///
/// Parse a URDF file and return a URDFRobot model
///
public static URDFRobot ParseURDF(string filePath)
{
if (!File.Exists(filePath))
{
Debug.LogError($"URDF file not found: {filePath}");
return null;
}
XmlDocument doc = new XmlDocument();
try
{
doc.Load(filePath);
}
catch (Exception e)
{
Debug.LogError($"Failed to load URDF XML: {e.Message}");
return null;
}
XmlNode robotNode = doc.SelectSingleNode("robot");
if (robotNode == null)
{
Debug.LogError("URDF file does not contain a root element");
return null;
}
string robotName = GetAttribute(robotNode, "name", "robot");
URDFRobot robot = new URDFRobot(robotName);
// Parse all links
XmlNodeList linkNodes = robotNode.SelectNodes("link");
foreach (XmlNode linkNode in linkNodes)
{
string linkName = GetAttribute(linkNode, "name");
if (string.IsNullOrEmpty(linkName))
{
Debug.LogWarning("Found link without name attribute, skipping");
continue;
}
URDFLink link = ParseLink(linkNode, linkName);
robot.links[linkName] = link;
}
// Parse all joints
XmlNodeList jointNodes = robotNode.SelectNodes("joint");
foreach (XmlNode jointNode in jointNodes)
{
string jointName = GetAttribute(jointNode, "name");
if (string.IsNullOrEmpty(jointName))
{
Debug.LogWarning("Found joint without name attribute, skipping");
continue;
}
URDFJoint joint = ParseJoint(jointNode, jointName);
robot.joints[jointName] = joint;
}
Debug.Log($"Parsed URDF: {robot.links.Count} links, {robot.joints.Count} joints");
return robot;
}
private static URDFLink ParseLink(XmlNode linkNode, string linkName)
{
URDFLink link = new URDFLink(linkName);
// Parse visual elements
XmlNodeList visualNodes = linkNode.SelectNodes("visual");
foreach (XmlNode visualNode in visualNodes)
{
URDFVisual visual = ParseVisual(visualNode);
if (visual != null)
{
link.visuals.Add(visual);
}
}
// Parse inertial properties
XmlNode inertialNode = linkNode.SelectSingleNode("inertial");
if (inertialNode != null)
{
XmlNode originNode = inertialNode.SelectSingleNode("origin");
if (originNode != null)
{
link.inertialOrigin = ParseOrigin(originNode);
}
XmlNode massNode = inertialNode.SelectSingleNode("mass");
if (massNode != null)
{
link.mass = ParseFloat(GetAttribute(massNode, "value"), 0f);
}
XmlNode inertiaNode = inertialNode.SelectSingleNode("inertia");
if (inertiaNode != null)
{
link.inertia = new Vector3(
ParseFloat(GetAttribute(inertiaNode, "ixx"), 0f),
ParseFloat(GetAttribute(inertiaNode, "iyy"), 0f),
ParseFloat(GetAttribute(inertiaNode, "izz"), 0f)
);
}
}
return link;
}
private static URDFVisual ParseVisual(XmlNode visualNode)
{
URDFVisual visual = new URDFVisual();
// Parse origin
XmlNode originNode = visualNode.SelectSingleNode("origin");
if (originNode != null)
{
visual.origin = ParseOrigin(originNode);
}
else
{
visual.origin = new URDFOrigin();
}
// Parse geometry
XmlNode geometryNode = visualNode.SelectSingleNode("geometry");
if (geometryNode != null)
{
visual.geometry = ParseGeometry(geometryNode);
}
// Parse material
XmlNode materialNode = visualNode.SelectSingleNode("material");
if (materialNode != null)
{
visual.materialName = GetAttribute(materialNode, "name");
XmlNode colorNode = materialNode.SelectSingleNode("color");
if (colorNode != null)
{
visual.color = ParseColor(GetAttribute(colorNode, "rgba"));
}
}
return visual;
}
private static URDFGeometry ParseGeometry(XmlNode geometryNode)
{
URDFGeometry geometry = new URDFGeometry();
// Check for mesh
XmlNode meshNode = geometryNode.SelectSingleNode("mesh");
if (meshNode != null)
{
geometry.type = URDFGeometry.GeometryType.Mesh;
geometry.meshFilename = GetAttribute(meshNode, "filename");
geometry.scale = ParseVector3(GetAttribute(meshNode, "scale"), Vector3.one);
}
// Check for box
else if (geometryNode.SelectSingleNode("box") != null)
{
geometry.type = URDFGeometry.GeometryType.Box;
XmlNode boxNode = geometryNode.SelectSingleNode("box");
geometry.size = ParseVector3(GetAttribute(boxNode, "size"), Vector3.one);
}
// Check for sphere
else if (geometryNode.SelectSingleNode("sphere") != null)
{
geometry.type = URDFGeometry.GeometryType.Sphere;
XmlNode sphereNode = geometryNode.SelectSingleNode("sphere");
geometry.radius = ParseFloat(GetAttribute(sphereNode, "radius"), 0.5f);
}
// Check for cylinder
else if (geometryNode.SelectSingleNode("cylinder") != null)
{
geometry.type = URDFGeometry.GeometryType.Cylinder;
XmlNode cylinderNode = geometryNode.SelectSingleNode("cylinder");
geometry.radius = ParseFloat(GetAttribute(cylinderNode, "radius"), 0.5f);
geometry.length = ParseFloat(GetAttribute(cylinderNode, "length"), 1f);
}
return geometry;
}
private static URDFJoint ParseJoint(XmlNode jointNode, string jointName)
{
URDFJoint joint = new URDFJoint(jointName);
// Parse joint type
string typeStr = GetAttribute(jointNode, "type", "fixed").ToLower();
switch (typeStr)
{
case "revolute":
joint.type = URDFJoint.JointType.Revolute;
break;
case "continuous":
joint.type = URDFJoint.JointType.Continuous;
break;
case "prismatic":
joint.type = URDFJoint.JointType.Prismatic;
break;
case "fixed":
joint.type = URDFJoint.JointType.Fixed;
break;
case "floating":
joint.type = URDFJoint.JointType.Floating;
break;
case "planar":
joint.type = URDFJoint.JointType.Planar;
break;
default:
joint.type = URDFJoint.JointType.Fixed;
break;
}
// Parse parent and child links
XmlNode parentNode = jointNode.SelectSingleNode("parent");
if (parentNode != null)
{
joint.parentLink = GetAttribute(parentNode, "link");
}
XmlNode childNode = jointNode.SelectSingleNode("child");
if (childNode != null)
{
joint.childLink = GetAttribute(childNode, "link");
}
// Parse origin
XmlNode originNode = jointNode.SelectSingleNode("origin");
if (originNode != null)
{
joint.origin = ParseOrigin(originNode);
}
// Parse axis
XmlNode axisNode = jointNode.SelectSingleNode("axis");
if (axisNode != null)
{
joint.axis = ParseVector3(GetAttribute(axisNode, "xyz"), Vector3.up);
}
// Parse limit
XmlNode limitNode = jointNode.SelectSingleNode("limit");
if (limitNode != null)
{
joint.limit = new URDFJointLimit
{
lower = ParseFloat(GetAttribute(limitNode, "lower"), -Mathf.PI),
upper = ParseFloat(GetAttribute(limitNode, "upper"), Mathf.PI),
effort = ParseFloat(GetAttribute(limitNode, "effort"), 1000f),
velocity = ParseFloat(GetAttribute(limitNode, "velocity"), 1f)
};
}
return joint;
}
private static URDFOrigin ParseOrigin(XmlNode originNode)
{
URDFOrigin origin = new URDFOrigin();
origin.xyz = ParseVector3(GetAttribute(originNode, "xyz"), Vector3.zero);
origin.rpy = ParseVector3(GetAttribute(originNode, "rpy"), Vector3.zero);
return origin;
}
private static Vector3 ParseVector3(string str, Vector3 defaultValue)
{
if (string.IsNullOrEmpty(str))
return defaultValue;
string[] parts = str.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 3)
{
return new Vector3(
ParseFloat(parts[0], defaultValue.x),
ParseFloat(parts[1], defaultValue.y),
ParseFloat(parts[2], defaultValue.z)
);
}
return defaultValue;
}
private static Color ParseColor(string rgbaStr)
{
if (string.IsNullOrEmpty(rgbaStr))
return Color.white;
string[] parts = rgbaStr.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 4)
{
return new Color(
ParseFloat(parts[0], 1f),
ParseFloat(parts[1], 1f),
ParseFloat(parts[2], 1f),
ParseFloat(parts[3], 1f)
);
}
return Color.white;
}
private static float ParseFloat(string str, float defaultValue)
{
if (string.IsNullOrEmpty(str))
return defaultValue;
if (float.TryParse(str, out float result))
return result;
return defaultValue;
}
private static string GetAttribute(XmlNode node, string attributeName, string defaultValue = "")
{
if (node?.Attributes?[attributeName] != null)
return node.Attributes[attributeName].Value;
return defaultValue;
}
}
}