using System; using System.Collections.Generic; using UnityEngine; namespace UnityMoveIt2.Core { /// /// 通用机器人适配器 - Universal Robot Adapter /// 支持1-9自由度的任意机械臂配置 / Supports 1-9 DOF arbitrary manipulator configuration /// [RequireComponent(typeof(ArticulationBody))] public class UniversalRobotAdapter : MonoBehaviour { #region Inspector Fields [Header("机器人配置 / Robot Configuration")] [SerializeField, Range(1, 9)] [Tooltip("机器人自由度数量 / Degrees of Freedom")] private int degreesOfFreedom = 6; [SerializeField] [Tooltip("机器人名称 / Robot Name")] private string robotName = "universal_robot"; [SerializeField] [Tooltip("机器人类型 / Robot Type")] private string robotType = "serial_manipulator"; [Header("坐标系配置 / Frame Configuration")] [SerializeField] [Tooltip("基座坐标系 / Base Link")] private Transform baseLink; [SerializeField] [Tooltip("末端执行器 / End Effector")] private Transform endEffector; [SerializeField] [Tooltip("工具中心点偏移 / TCP Offset")] private Vector3 tcpOffset = Vector3.zero; [Header("URDF配置 / URDF Configuration")] [SerializeField] [Tooltip("URDF文件路径 / URDF Path")] private string urdfPath; [SerializeField] [Tooltip("SRDF文件路径 / SRDF Path")] private string srdfPath; [Header("运动学参数 / Kinematic Parameters")] [SerializeField] [Tooltip("工作空间半径(米) / Workspace Radius (m)")] private float workspaceRadius = 1.0f; [SerializeField] [Tooltip("规划组名称 / Planning Group Name")] private string planningGroup = "manipulator"; #endregion #region Private Fields private ArticulationBody[] joints; private JointData[] jointData; private bool isInitialized = false; private Transform[] linkTransforms; private Dictionary jointNameToIndex; #endregion #region Properties /// /// 自由度数量 / Degrees of Freedom /// public int DOF => degreesOfFreedom; /// /// 机器人名称 / Robot Name /// public string RobotName => robotName; /// /// 是否已初始化 / Is Initialized /// public bool IsInitialized => isInitialized; /// /// 基座变换 / Base Transform /// public Transform BaseLink => baseLink; /// /// 末端执行器变换 / End Effector Transform /// public Transform EndEffector => endEffector; /// /// 关节数据数组 / Joint Data Array /// public JointData[] JointData => jointData; /// /// 规划组名称 / Planning Group Name /// public string PlanningGroup => planningGroup; #endregion #region Unity Lifecycle private void Awake() { InitializeRobot(); } private void Start() { if (!isInitialized) { Debug.LogWarning($"[UniversalRobotAdapter] {robotName} 未能正确初始化 / Failed to initialize properly"); } } #endregion #region Initialization /// /// 初始化机器人 / Initialize Robot /// public bool InitializeRobot() { try { Debug.Log($"[UniversalRobotAdapter] 初始化 {degreesOfFreedom} DOF 机器人: {robotName}"); // 验证基本配置 / Validate basic configuration if (!ValidateConfiguration()) { Debug.LogError("[UniversalRobotAdapter] 配置验证失败 / Configuration validation failed"); return false; } // 查找所有关节 / Find all joints DiscoverJoints(); // 初始化关节数据 / Initialize joint data InitializeJointData(); // 构建关节索引映射 / Build joint name to index mapping BuildJointMapping(); // 设置默认关节位置 / Set default joint positions SetDefaultJointPositions(); isInitialized = true; Debug.Log($"[UniversalRobotAdapter] {robotName} 初始化成功 / Initialized successfully"); return true; } catch (Exception ex) { Debug.LogError($"[UniversalRobotAdapter] 初始化失败 / Initialization failed: {ex.Message}"); return false; } } /// /// 验证配置 / Validate Configuration /// private bool ValidateConfiguration() { if (baseLink == null) { Debug.LogError("[UniversalRobotAdapter] 基座链接未设置 / Base link not set"); return false; } if (endEffector == null) { Debug.LogWarning("[UniversalRobotAdapter] 末端执行器未设置,尝试自动检测 / End effector not set, attempting auto-detection"); AutoDetectEndEffector(); } if (degreesOfFreedom < 1 || degreesOfFreedom > 9) { Debug.LogError($"[UniversalRobotAdapter] 自由度超出范围: {degreesOfFreedom} / DOF out of range"); return false; } return true; } /// /// 发现所有关节 / Discover All Joints /// private void DiscoverJoints() { // 获取所有ArticulationBody组件 / Get all ArticulationBody components List foundJoints = new List(); GetComponentsInChildren(true, foundJoints); // 过滤出可移动关节 / Filter movable joints List movableJoints = new List(); foreach (var joint in foundJoints) { if (joint.jointType != ArticulationJointType.FixedJoint && joint != GetComponent()) { movableJoints.Add(joint); } } // 验证关节数量 / Validate joint count if (movableJoints.Count != degreesOfFreedom) { Debug.LogWarning($"[UniversalRobotAdapter] 发现关节数({movableJoints.Count})与配置DOF({degreesOfFreedom})不匹配 / " + $"Found joints count does not match configured DOF"); degreesOfFreedom = movableJoints.Count; } joints = movableJoints.ToArray(); Debug.Log($"[UniversalRobotAdapter] 发现 {joints.Length} 个可移动关节 / Discovered {joints.Length} movable joints"); } /// /// 初始化关节数据 / Initialize Joint Data /// private void InitializeJointData() { jointData = new JointData[joints.Length]; linkTransforms = new Transform[joints.Length]; for (int i = 0; i < joints.Length; i++) { jointData[i] = new JointData { index = i, name = joints[i].name, jointType = joints[i].jointType, lowerLimit = GetJointLowerLimit(joints[i]), upperLimit = GetJointUpperLimit(joints[i]), maxVelocity = GetJointMaxVelocity(joints[i]), maxAcceleration = GetJointMaxAcceleration(joints[i]), currentPosition = 0f, currentVelocity = 0f, targetPosition = 0f }; linkTransforms[i] = joints[i].transform; Debug.Log($"[UniversalRobotAdapter] 关节 {i}: {jointData[i].name}, " + $"范围: [{jointData[i].lowerLimit:F2}, {jointData[i].upperLimit:F2}]"); } } /// /// 构建关节名称到索引的映射 / Build Joint Name to Index Mapping /// private void BuildJointMapping() { jointNameToIndex = new Dictionary(); for (int i = 0; i < jointData.Length; i++) { jointNameToIndex[jointData[i].name] = i; } } /// /// 自动检测末端执行器 / Auto Detect End Effector /// private void AutoDetectEndEffector() { if (joints != null && joints.Length > 0) { // 使用最后一个关节作为末端执行器 / Use last joint as end effector ArticulationBody lastJoint = joints[joints.Length - 1]; // 查找该关节的子物体作为末端执行器 / Find child of last joint as end effector if (lastJoint.transform.childCount > 0) { endEffector = lastJoint.transform.GetChild(0); Debug.Log($"[UniversalRobotAdapter] 自动检测到末端执行器: {endEffector.name} / Auto-detected end effector"); } else { endEffector = lastJoint.transform; Debug.LogWarning("[UniversalRobotAdapter] 使用最后关节作为末端执行器 / Using last joint as end effector"); } } } #endregion #region Joint Control /// /// 设置关节位置 / Set Joint Positions /// public void SetJointPositions(float[] positions) { if (!isInitialized) { Debug.LogWarning("[UniversalRobotAdapter] 机器人未初始化 / Robot not initialized"); return; } if (positions == null || positions.Length != degreesOfFreedom) { Debug.LogError($"[UniversalRobotAdapter] 关节位置数组长度不匹配: {positions?.Length} vs {degreesOfFreedom}"); return; } for (int i = 0; i < positions.Length; i++) { SetJointPosition(i, positions[i]); } } /// /// 设置单个关节位置 / Set Single Joint Position /// public void SetJointPosition(int jointIndex, float position) { if (jointIndex < 0 || jointIndex >= joints.Length) { Debug.LogError($"[UniversalRobotAdapter] 关节索引越界: {jointIndex}"); return; } // 限制关节位置在允许范围内 / Clamp position within limits float clampedPosition = Mathf.Clamp(position, jointData[jointIndex].lowerLimit, jointData[jointIndex].upperLimit); if (Mathf.Abs(clampedPosition - position) > 0.001f) { Debug.LogWarning($"[UniversalRobotAdapter] 关节 {jointIndex} 位置被限制: {position} -> {clampedPosition}"); } // 更新关节目标位置 / Update joint target position ArticulationDrive drive = joints[jointIndex].xDrive; drive.target = clampedPosition * Mathf.Rad2Deg; // Unity uses degrees joints[jointIndex].xDrive = drive; jointData[jointIndex].targetPosition = clampedPosition; } /// /// 获取当前关节位置 / Get Current Joint Positions /// public float[] GetCurrentJointPositions() { float[] positions = new float[joints.Length]; for (int i = 0; i < joints.Length; i++) { positions[i] = joints[i].jointPosition[0] * Mathf.Deg2Rad; // Convert to radians jointData[i].currentPosition = positions[i]; } return positions; } /// /// 获取当前关节速度 / Get Current Joint Velocities /// public float[] GetCurrentJointVelocities() { float[] velocities = new float[joints.Length]; for (int i = 0; i < joints.Length; i++) { velocities[i] = joints[i].jointVelocity[0] * Mathf.Deg2Rad; // Convert to rad/s jointData[i].currentVelocity = velocities[i]; } return velocities; } /// /// 设置默认关节位置 / Set Default Joint Positions /// private void SetDefaultJointPositions() { float[] zeroPositions = new float[degreesOfFreedom]; SetJointPositions(zeroPositions); } #endregion #region Forward Kinematics /// /// 计算末端执行器位姿(正运动学) / Calculate End Effector Pose (Forward Kinematics) /// public Pose GetEndEffectorPose() { if (endEffector == null) { Debug.LogError("[UniversalRobotAdapter] 末端执行器未设置 / End effector not set"); return new Pose(Vector3.zero, Quaternion.identity); } // 应用TCP偏移 / Apply TCP offset Vector3 position = endEffector.position + endEffector.TransformDirection(tcpOffset); Quaternion rotation = endEffector.rotation; return new Pose(position, rotation); } /// /// 获取指定链接的位姿 / Get Pose of Specified Link /// public Pose GetLinkPose(int linkIndex) { if (linkIndex < 0 || linkIndex >= linkTransforms.Length) { Debug.LogError($"[UniversalRobotAdapter] 链接索引越界: {linkIndex}"); return new Pose(Vector3.zero, Quaternion.identity); } return new Pose(linkTransforms[linkIndex].position, linkTransforms[linkIndex].rotation); } #endregion #region Joint Limits private float GetJointLowerLimit(ArticulationBody joint) { if (joint.jointType == ArticulationJointType.RevoluteJoint) { return joint.xDrive.lowerLimit * Mathf.Deg2Rad; } else if (joint.jointType == ArticulationJointType.PrismaticJoint) { return joint.xDrive.lowerLimit; } return -Mathf.PI; } private float GetJointUpperLimit(ArticulationBody joint) { if (joint.jointType == ArticulationJointType.RevoluteJoint) { return joint.xDrive.upperLimit * Mathf.Deg2Rad; } else if (joint.jointType == ArticulationJointType.PrismaticJoint) { return joint.xDrive.upperLimit; } return Mathf.PI; } private float GetJointMaxVelocity(ArticulationBody joint) { // Unity ArticulationBody doesn't directly expose max velocity // Use a reasonable default based on joint type return joint.jointType == ArticulationJointType.RevoluteJoint ? 2.0f : 1.0f; } private float GetJointMaxAcceleration(ArticulationBody joint) { // Unity ArticulationBody doesn't directly expose max acceleration // Use a reasonable default return joint.jointType == ArticulationJointType.RevoluteJoint ? 4.0f : 2.0f; } #endregion #region Utility Methods /// /// 根据名称获取关节索引 / Get Joint Index by Name /// public int GetJointIndex(string jointName) { if (jointNameToIndex != null && jointNameToIndex.ContainsKey(jointName)) { return jointNameToIndex[jointName]; } return -1; } /// /// 检查关节位置是否在限制范围内 / Check if Joint Position is Within Limits /// public bool IsJointPositionValid(int jointIndex, float position) { if (jointIndex < 0 || jointIndex >= jointData.Length) return false; return position >= jointData[jointIndex].lowerLimit && position <= jointData[jointIndex].upperLimit; } /// /// 检查所有关节位置是否有效 / Check if All Joint Positions are Valid /// public bool AreJointPositionsValid(float[] positions) { if (positions == null || positions.Length != degreesOfFreedom) return false; for (int i = 0; i < positions.Length; i++) { if (!IsJointPositionValid(i, positions[i])) return false; } return true; } #endregion #region Configuration Methods /// /// 从配置文件加载机器人 / Load Robot from Configuration /// public bool LoadFromConfiguration(RobotConfiguration config) { if (config == null) { Debug.LogError("[UniversalRobotAdapter] 配置为空 / Configuration is null"); return false; } robotName = config.robotName; degreesOfFreedom = config.dof; urdfPath = config.urdfPath; srdfPath = config.srdfPath; planningGroup = config.planningGroup; return InitializeRobot(); } /// /// 导出当前配置 / Export Current Configuration /// public RobotConfiguration ExportConfiguration() { return new RobotConfiguration { robotName = this.robotName, dof = this.degreesOfFreedom, urdfPath = this.urdfPath, srdfPath = this.srdfPath, planningGroup = this.planningGroup, jointNames = GetJointNames(), jointLimitsMin = GetJointLimitsMin(), jointLimitsMax = GetJointLimitsMax() }; } private string[] GetJointNames() { string[] names = new string[jointData.Length]; for (int i = 0; i < jointData.Length; i++) { names[i] = jointData[i].name; } return names; } private float[] GetJointLimitsMin() { float[] limits = new float[jointData.Length]; for (int i = 0; i < jointData.Length; i++) { limits[i] = jointData[i].lowerLimit; } return limits; } private float[] GetJointLimitsMax() { float[] limits = new float[jointData.Length]; for (int i = 0; i < jointData.Length; i++) { limits[i] = jointData[i].upperLimit; } return limits; } #endregion } #region Data Structures /// /// 关节数据 / Joint Data /// [Serializable] public class JointData { public int index; public string name; public ArticulationJointType jointType; public float lowerLimit; public float upperLimit; public float maxVelocity; public float maxAcceleration; public float currentPosition; public float currentVelocity; public float targetPosition; } /// /// 机器人配置 / Robot Configuration /// [Serializable] public class RobotConfiguration { public string robotName; public int dof; public string urdfPath; public string srdfPath; public string planningGroup; public string[] jointNames; public float[] jointLimitsMin; public float[] jointLimitsMax; } #endregion }