04-Unity机床|机器人加工防碰撞仿真、代码说明

04-Unity机床|机器人加工防碰撞仿真、代码说明

写在前面

每隔一段时间打开这个数字孪生系统就忘记以前是怎么设置的了,所以决定写点什么记录一下,纯纯说明书。本文详细记录了数字孪生系统中机器人仿真功能的实现过程。主要内容包括:1)仿真文件加载路径和碰撞器设置;2)铣磨机、抛光机及机器人变量绑定方法;3)关节J5/J6遍历运动集功能,包含碰撞检测(标红显示)、轨迹绘制和数据导出;4)完整的C#代码实现,涵盖运动控制、碰撞检测逻辑和CSV数据记录功能。

一、操作步骤

1.仿真源代码

机床仿真代码来源于Vericut,ABB机器人仿真代码来源于Robotstudio,均可保存为.txt文件,图1-5分别为Unity仿真加载文件列表,铣磨机、抛光机和机器人仿真文件,机器人关节遍历代码,图2-4代码保存至unity项目固定文件夹【E:\unity\Unity Project\My project\Assets\StreamingAssets】下。

机器人仿真文件机器人关节遍历代码

2.Unity碰撞器

Unity碰撞器类型:盒碰撞器/2D ,胶囊碰撞器/2D,2D 圆形碰撞器,2D 复合碰撞器,2D 自定义碰撞器,2D 边缘碰撞器,网格碰撞器,2D 多边形碰撞器,球形碰撞器,地形碰撞器,2D 瓦片地图碰撞器,车轮碰撞器。

对于六轴机器人而言,常用碰撞器类型为Box Collider和Mesh Collider,记得打开右上角地球🌏图标开关,否则不显示添加的碰撞器

3.加工仿真C#代码变量绑定

铣磨机,抛光机,机器人物体与变量绑定,旋转轴方向设置等见图片

铣磨机变量绑定   抛光机变量绑定
机器人变量绑定   关节5/6遍历代码变量绑定

4.关节J5/J6 遍历运动

集遍历+碰撞检测 + 轨迹绘制 + 数据导出一体化功能。碰撞检测过程中,若发生碰撞,物体会标红,末端执行器是 “碰撞检测的发起者”(挂载 Trigger Collider),而其他部件是 “被碰撞的目标”,末端自己的 Collider 只需要勾Is Trigger。其碰撞机制分为两类:

一是末端执行器与机器人本体的碰撞(主要检测关节4与末端是否干涉);

二是机器人和末端整体与工件的碰撞。

遍历完成后会导出.csv文件至 C:/Users/ThinkPad/AppData/LocalLow/BJUT/ABB test project\J5J6_Collision_Result.csv,路径与文件名可以自己设置。Unity效果与RS遍历结果一样。

代码

using UnityEngine; using System.Collections; using System.IO; using System.Text; using System.Collections.Generic; using System.Linq; using System; using TMPro; // 引入TextMeshPro命名空间 public class RapidJointTraversal_J5J6 : MonoBehaviour { // === 机器人关节配置 === public Transform Joint5; // ABB IRB6700 J5关节 public Transform Joint6; // ABB IRB6700 J6关节 [Header("轨迹绘制与碰撞检测分离配置")] public Transform TrajectoryDrawPoint; // 专门用于绘制轨迹的末端点 public Transform CollisionCheckEffector; // 专门用于碰撞检测的末端执行器 [Header("碰撞检测核心配置")] public List<Transform> RobotBodyParts; // 拖入J4/J5/J6本体部件 public Collider targetWorkpieceCollider; // 目标工件碰撞体 public List<Collider> workpieceColliders = new List<Collider>(); // 所有工件碰撞体列表 [Header("UI状态显示(TextMeshPro)")] public TextMeshProUGUI collisionInfoText; // 统一显示所有碰撞信息的TextMeshPro文本 public static int mode; // 运行模式(1-自由 2-自动归位 3-碰撞预警 4-抛光) // === 轨迹绘制(固定红色) === [Header("轨迹绘制")] public LineRenderer TrajectoryLine; public float lineWidth = 0.01f; // 线宽,调大一点更容易看到 // === 碰撞标红配置 === [Header("碰撞标红配置")] public Color collisionHighlightColor = Color.red; public bool drawCollisionSphere = true; public float collisionSphereRadiusScale = 1.1f; public Shader highlightShader; // === 内部核心变量 === private bool _isSelfColliding, _isExternalColliding, _isColliding; private Transform _collidedBodyPart; private Collider _collidedWorkpiece; private Dictionary<Renderer, Material> _originMaterials = new Dictionary<Renderer, Material>(); private List<Vector3> _trajectoryPoints = new List<Vector3>(); private List<string> _csvDataList = new List<string>(); private Vector3 _debugSphereCenter; private float _debugSphereRadius; private bool collisionOccurred = false; // 碰撞发生标记 void Start() { // 校验关键组件赋值 if (TrajectoryDrawPoint == null) Debug.LogError("请给【TrajectoryDrawPoint(轨迹绘制点)】赋值!"); if (CollisionCheckEffector == null) Debug.LogError("请给【CollisionCheckEffector(碰撞检测执行器)】赋值!"); if (collisionInfoText == null) Debug.LogWarning("未赋值collisionInfoText,碰撞状态将无法显示在UI上!"); // 初始化轨迹(强制设置为红色,无任何切换) InitializeTrajectoryLine(); // CSV表头 + 预存材质 _csvDataList.Add("J5,J6,是否干涉,碰撞类型,碰撞物体,X坐标,Y坐标,Z坐标"); PreSaveOriginMaterials(); if (highlightShader == null) { highlightShader = Shader.Find("Standard"); } // 初始化UI状态 UpdateCollisionUI(); // 启动遍历协程 StartCoroutine(StartJ5J6Traversal()); } private void Update() { // 实时更新UI显示 UpdateCollisionUI(); } #region 核心运动逻辑(保留无抽搐版本) private IEnumerator StartJ5J6Traversal() { float j5Step = 1f; float j6Step = 10f; for (float j5 = -120f; j5 <= 120f; j5 += j5Step) { SetJointAngle(Joint5, j5, Axis.Y); yield return null; for (float j6 = 0f; j6 < 360f; j6 += j6Step) { ResetCollisionState(); ResetAllMaterials(); collisionOccurred = false; // 重置碰撞标记 SetJointAngle(Joint6, j6, Axis.X); yield return null; CheckCollision(); // 修复碰撞检测逻辑 HighlightCollidedObjects(); RecordJ5J6Data(j5, j6); // 碰撞发生时处理 if (_isColliding) { collisionOccurred = true; HandleCollisionBasedOnMode(); } // 实时更新轨迹(基于TrajectoryDrawPoint) UpdateTrajectoryLine(); Debug.Log($"遍历 → J5={j5:F1}°, J6={j6:F1}° | 干涉={_isColliding} | 碰撞类型={(_isSelfColliding ? "自碰撞" : _isExternalColliding ? "工件碰撞" : "无")}"); yield return new WaitForSeconds(0.02f); } } ResetAllMaterials(); ExportCSV(); Debug.Log("全范围遍历完成!"); } // 无抽搐运动方法 private enum Axis { X, Y, Z } private void SetJointAngle(Transform joint, float angle, Axis axis) { if (joint == null) return; Vector3 targetEulers = Vector3.zero; switch (axis) { case Axis.X: targetEulers.x = angle; break; case Axis.Y: targetEulers.y = angle; break; case Axis.Z: targetEulers.z = angle; break; } joint.localRotation = Quaternion.Euler(targetEulers); } #endregion #region 轨迹初始化+更新 private void InitializeTrajectoryLine() { if (TrajectoryLine == null) { Debug.LogError("请给TrajectoryLine赋值!"); return; } // 轨迹线基础设置 TrajectoryLine.widthMultiplier = lineWidth; TrajectoryLine.positionCount = 0; TrajectoryLine.useWorldSpace = true; TrajectoryLine.enabled = true; // 强制创建红色材质,彻底避免黑色问题 Material redMat = new Material(Shader.Find("Unlit/Color")); redMat.color = Color.red; // 固定为红色 TrajectoryLine.material = redMat; // 线调大一点,置顶显示,确保能看到 TrajectoryLine.sortingOrder = 100; } private void UpdateTrajectoryLine() { if (TrajectoryLine == null || _trajectoryPoints.Count == 0) return; TrajectoryLine.positionCount = _trajectoryPoints.Count; TrajectoryLine.SetPositions(_trajectoryPoints.ToArray()); } #endregion #region 碰撞检测核心修复 private void CheckCollision() { _isSelfColliding = false; _isExternalColliding = false; _collidedBodyPart = null; _collidedWorkpiece = null; _debugSphereCenter = Vector3.zero; _debugSphereRadius = 0; if (CollisionCheckEffector == null) return; Collider toolCol = CollisionCheckEffector.GetComponent<Collider>(); if (toolCol == null) { Debug.LogError("CollisionCheckEffector上没有挂载Collider组件,无法检测碰撞!"); return; } Bounds toolBounds = toolCol.bounds; _debugSphereCenter = toolBounds.center; _debugSphereRadius = toolBounds.extents.magnitude * collisionSphereRadiusScale; // 1. 检测自碰撞(工具头-本体) if (RobotBodyParts != null && RobotBodyParts.Count > 0) { foreach (var part in RobotBodyParts) { if (part == null) continue; Collider bodyCol = part.GetComponent<Collider>(); if (bodyCol == null) continue; float checkRadius = (toolBounds.extents.magnitude + bodyCol.bounds.extents.magnitude) * collisionSphereRadiusScale; // 增加包围盒相交检测,提高准确性 if (Physics.CheckSphere(toolBounds.center, checkRadius, 1 << part.gameObject.layer) && toolCol.bounds.Intersects(bodyCol.bounds)) { _isSelfColliding = true; _collidedBodyPart = part; break; } } } // 2. 检测外部碰撞(机器人-工件)- 移除层检测,仅用指定碰撞体 if (!_isSelfColliding) { // 方式1:检测指定的目标工件碰撞体 if (targetWorkpieceCollider != null && toolCol.bounds.Intersects(targetWorkpieceCollider.bounds)) { _isExternalColliding = true; _collidedWorkpiece = targetWorkpieceCollider; } // 方式2:检测自定义的工件碰撞体列表 else if (workpieceColliders != null && workpieceColliders.Count > 0) { foreach (var wpCol in workpieceColliders) { if (wpCol != null && toolCol.bounds.Intersects(wpCol.bounds)) { _isExternalColliding = true; _collidedWorkpiece = wpCol; break; } } } } _isColliding = _isSelfColliding || _isExternalColliding; } // 按模式处理碰撞,所有信息输出到同一个TextMeshPro文本 private void HandleCollisionBasedOnMode() { string modeName = mode switch { 1 => "自由模式", 2 => "调试模式(自动归位)", 3 => "调试模式(碰撞预警)", 4 => "抛光模式", _ => "未知模式" }; string collisionTypeName = _isSelfColliding ? "工具头-本体碰撞" : "机器人-工件碰撞"; string collidedObjectName = _isSelfColliding ? (_collidedBodyPart?.name ?? "未知关节") : (_collidedWorkpiece?.name ?? "未知工件"); // 构建统一的碰撞信息文本 string collisionMsg = $"【{modeName}】\n" + $"碰撞状态:发生碰撞\n" + $"碰撞类型:{collisionTypeName}\n" + $"碰撞物体:{collidedObjectName}\n" + $"J5角度:{Joint5.localEulerAngles.y:F1}° | J6角度:{Joint6.localEulerAngles.x:F1}°"; // 更新TextMeshPro文本 if (collisionInfoText != null) { collisionInfoText.text = collisionMsg; collisionInfoText.color = Color.red; } Debug.Log($"碰撞处理 → {collisionMsg}"); } // 更新碰撞UI显示(无碰撞时显示正常状态) private void UpdateCollisionUI() { if (collisionInfoText == null) return; if (!collisionOccurred) { string modeName = mode switch { 1 => "自由模式", 2 => "调试模式(自动归位)", 3 => "调试模式(碰撞预警)", 4 => "抛光模式", _ => "未知模式" }; // 无碰撞时的统一正常信息 string normalMsg = $"【{modeName}】\n" + $"碰撞状态:运行正常\n" + $"告警信息:无\n" + $"J5角度:{Joint5.localEulerAngles.y:F1}° | J6角度:{Joint6.localEulerAngles.x:F1}°"; collisionInfoText.text = normalMsg; collisionInfoText.color = Color.green; } } private void HighlightCollidedObjects() { if (_isSelfColliding && _collidedBodyPart != null && CollisionCheckEffector != null) { SetObjectHighlight(CollisionCheckEffector); SetObjectHighlight(_collidedBodyPart); } if (_isExternalColliding && _collidedWorkpiece != null && CollisionCheckEffector != null) { SetObjectHighlight(CollisionCheckEffector); SetObjectHighlight(_collidedWorkpiece.transform); } } private void SetObjectHighlight(Transform target) { if (target == null) return; Renderer renderer = target.GetComponent<Renderer>(); if (renderer == null) return; Material highlightMat = new Material(highlightShader); highlightMat.color = collisionHighlightColor; highlightMat.mainTexture = null; renderer.material = highlightMat; } #endregion #region 数据记录与导出 private void PreSaveOriginMaterials() { // 保存碰撞检测执行器的材质 SaveRendererMaterial(CollisionCheckEffector?.GetComponent<Renderer>()); if (RobotBodyParts != null) { foreach (var part in RobotBodyParts) { SaveRendererMaterial(part?.GetComponent<Renderer>()); } } // 保存工件碰撞体的材质 if (targetWorkpieceCollider != null) { SaveRendererMaterial(targetWorkpieceCollider.GetComponent<Renderer>()); } foreach (var wpCol in workpieceColliders) { SaveRendererMaterial(wpCol?.GetComponent<Renderer>()); } } private void SaveRendererMaterial(Renderer renderer) { if (renderer == null || _originMaterials.ContainsKey(renderer)) return; _originMaterials[renderer] = renderer.material; } private void RecordJ5J6Data(float j5, float j6) { string collisionState = _isColliding ? "是" : "否"; string collisionType = "无"; string collidedObj = "无"; if (_isExternalColliding) { collisionType = "机器人-工件碰撞"; collidedObj = _collidedWorkpiece?.name ?? "未知工件"; } else if (_isSelfColliding) { collisionType = "工具头-本体碰撞"; collidedObj = _collidedBodyPart?.name ?? "未知关节"; } // CSV记录的坐标:轨迹绘制点(TrajectoryDrawPoint)的坐标 string xyz = TrajectoryDrawPoint != null ? $"{TrajectoryDrawPoint.position.x:F3},{TrajectoryDrawPoint.position.y:F3},{TrajectoryDrawPoint.position.z:F3}" : "0,0,0"; _csvDataList.Add($"{j5:F1},{j6:F1},{collisionState},{collisionType},{collidedObj},{xyz}"); // 轨迹点添加:只基于TrajectoryDrawPoint if (TrajectoryDrawPoint != null && TrajectoryLine != null) { _trajectoryPoints.Add(TrajectoryDrawPoint.position); } } private void ExportCSV() { string csvPath = Path.Combine(Application.persistentDataPath, "J5J6_Collision_Result.csv"); try { byte[] bom = Encoding.UTF8.GetPreamble(); byte[] content = Encoding.UTF8.GetBytes(string.Join("\n", _csvDataList)); byte[] allBytes = new byte[bom.Length + content.Length]; Buffer.BlockCopy(bom, 0, allBytes, 0, bom.Length); Buffer.BlockCopy(content, 0, allBytes, bom.Length, content.Length); File.WriteAllBytes(csvPath, allBytes); Debug.Log($"CSV导出成功 → {csvPath}"); } catch (System.Exception e) { Debug.LogError($"CSV导出失败:{e.Message}"); } } #endregion #region 重置与辅助方法 private void ResetAllMaterials() { foreach (var kvp in _originMaterials) { if (kvp.Key != null) { kvp.Key.material = kvp.Value; } } } private void ResetCollisionState() { _isSelfColliding = false; _isExternalColliding = false; _isColliding = false; _collidedBodyPart = null; _collidedWorkpiece = null; } void OnDrawGizmos() { if (!drawCollisionSphere || _debugSphereRadius <= 0) return; Gizmos.color = _isColliding ? Color.red : Color.green; Gizmos.DrawWireSphere(_debugSphereCenter, _debugSphereRadius); if (_collidedBodyPart != null) { Gizmos.color = Color.yellow; Gizmos.DrawSphere(_collidedBodyPart.position, 0.05f); } if (_collidedWorkpiece != null) { Gizmos.color = Color.magenta; Gizmos.DrawSphere(_collidedWorkpiece.transform.position, 0.05f); } } public void ClearTrajectory() { _trajectoryPoints.Clear(); if (TrajectoryLine != null) { TrajectoryLine.positionCount = 0; } } //// 设置运行模式 //public void SetMode(int newMode) //{ // mode = newMode; // collisionOccurred = false; // 切换模式时重置碰撞状态 // UpdateCollisionUI(); // Debug.Log($"切换到模式:{mode}(1-自由 2-自动归位 3-碰撞预警 4-抛光)"); //} // 设置工件碰撞体列表 public void SetWorkpieceColliders(Collider[] colliders) { workpieceColliders.Clear(); foreach (var collider in colliders) { workpieceColliders.Add(collider); Debug.Log("添加工件碰撞体: " + collider.name); } } #endregion }

弹窗显示

using UnityEngine; using UnityEngine.UI; public class AlertPopup : MonoBehaviour { public GameObject alertPanel; // 引用UI弹窗的Panel private void Start() { alertPanel.SetActive(false); // 初始时隐藏弹窗 } // 调用此方法来显示/隐藏弹窗 public void ShowAlert(string message) { alertPanel.SetActive(true); alertPanel.GetComponentInChildren<Text>().text = message; // 设置预警信息 } public void HideAlert() { alertPanel.SetActive(false); } }

二、代码说明

脚本挂在物体说明
1

A_ExchangeMilling、

MillingMachine_A

铣磨机铣磨机驱动
2

B_ExchangePolishing、

PolishingMachine_B

抛光机抛光机驱动
3Manufacturing_RobotCSV机器人机器人.csv解析驱动
4Manufacturing_RobotMod机器人机器人.mod解析驱动
5IKSolver_6Axis机器人正逆运动学求解器
6RapidJointTraversal_J5J6机器人机器人关节5/6遍历
7CollisionDetector碰撞检测

 三、数字孪生工厂参考资料

机械臂可靠性数字孪生

工业机器人全球安装量(2014-2024)+ 2028 年预测

https://ifr.org/ifr-press-releases/news/global-robot-demand-in-factories-doubles-over-10-years

PowerPoint-Präsentation

四、参考文章

1.solidworks模型导出urdf(超详细)配合视频观看_solidworks转urdf-ZEEKLOG博客

2.Solidworks到URDF的转化教程-ZEEKLOG博客

Read more

Xinference-v1.17.1快速部署:GitHub Codespaces云端环境3分钟启动WebUI

Xinference-v1.17.1快速部署:GitHub Codespaces云端环境3分钟启动WebUI 1. 为什么这次更新值得你立刻试试? Xinference-v1.17.1不是一次普通的小版本迭代。它把“开箱即用”这件事做到了新高度——你不需要本地装Python、不用配CUDA、甚至不用下载模型文件,只要一个浏览器,三分钟内就能看到完整的WebUI界面跑起来,还能直接和Qwen2、Phi-3、Gemma2这些热门模型对话。 更关键的是,它彻底打破了“换模型=重装环境”的老套路。以前想试试Llama3还是DeepSeek-V2,得反复改配置、删缓存、调参数;现在只需要改一行代码,GPT的调用逻辑就自动切换成任意开源LLM。这不是概念演示,是实打实能在云上跑、在笔记本跑、在边缘设备跑的生产级推理平台。 如果你试过用Ollama拉模型卡在99%、被vLLM的编译折磨到放弃、或者被FastChat的端口冲突搞崩溃……那这次,真的可以松一口气了。 2. 什么是Xinference?一句话说清它能帮你省多少事 Xinference(全称Xorbits Inference

SAP ABAP Web Dynpro (保准教会)

SAP ABAP Web Dynpro (保准教会)

文章目录 * 前言 * 01、案例介绍/笔者需求 * 02、Web Dynpro 是什么? *             `a.`Web Dynpro的用途及优点 *             `b.`什么是MVC架构? *             `c.`Web dynpro 开发方式技术架构 *             `d.`Web dynpro 组件架构逻辑 * 03、创建运行一个简单Web Dynpro *             `a.`创建 *             `b.`Layout界面异常 *             `c.`绘制简单的控件 并运行Web Dynpro * 04、Web Dynpro 各界面作用 *             `a.`VIEW(视图) 各分页签的作用 *             `b.`Window(窗体) 各分页签的作用 * 05、Web Dynpro 对应的3大控制器 *             `a.

【前端】前端面试题

【前端】前端面试题

前端面试题 闭包 1. 定义: 闭包(Closure) 是指一个函数能够访问并记住其外部作用域中的变量,即使外部函数已经执行完毕。闭包由两部分组成: * 一个函数(通常是内部函数)。 * 该函数被创建时所在的作用域(即外部函数的变量环境) functionouter(){let count =0;// 外部函数的变量functioninner(){ count++;// 内部函数访问外部变量 console.log(count);}return inner;}const counter =outer();counter();// 输出 1counter();// 输出 2 2. 闭包的核心原理 * 作用域链:函数在定义时,会记住自己的词法环境(即外部作用域)。当内部函数访问变量时,会沿着作用域链向上查找。 * 变量持久化:闭包使得外部函数的变量不会被垃圾回收,因为内部函数仍持有对它们的引用 3. 闭包的常见用途 3.1 私有变量封装 通过闭包隐藏内部变量,

【技术干货】用 Claude 4.6 直接“写”出可上线的前端 UI:从画布工具到代码工作流的升级思路

【技术干货】用 Claude 4.6 直接“写”出可上线的前端 UI:从画布工具到代码工作流的升级思路

摘要 本文从 Google Stitch 热度切入,对比“AI 画布式 UI 生成”与“代码内 UI 生成”两种路径,系统拆解如何用 Claude 4.6 + 前端设计规则,在真实代码库中迭代出可上线的 UI。附完整 Python API 调用示例与提示词模板,并结合多模型平台薛定猫 AI 的接入方式,帮助前端/全栈开发者把 AI UI 生成直接融入开发流水线。 一、背景:从“好看截图”到“可上线 UI” 当前 AI UI 方向大致两类路径: 1. 画布式设计工具 代表:Google Stitch