# iFactory.Godot 基于虚实交互和数模驱动,`iFactory`能满足仿真(以虚映实)、控制(以虚控实)、预测(以虚预实)、优化(以虚优实)等应用服务需求,其中连接与交互是实现数字孪生动态运行和虚实空间高效融合的核心关键 ![](https://img.shields.io/badge/Dev-0.32-green) 基于Godot的iFactory客户端(网络服务和服务端已定义接口,预计使用[KCP](https://github.com/skywind3000/kcp)实现) 正在从[Unity版(内网链接)](http://192.168.1.50:3000/cn-intelli/iFactory.git)一步一步移植到Godot中 该项目主要使用[ECS](https://zhuanlan.zhihu.com/p/30538626)架构,[BITKit](http://server.bitfall.icu:3000/root/BITKit.git)作为基本的dotnet框架 该项目为`基准演示`,主要通过一些场景展现该软件的所有功能 由于Godot未提供相关的编辑器功能(例如`暴露可编辑的class`和`自定义接口实例`),该项目的进度并没有`Unity`的实现速度快 *** ## Installation 安装过程 1.首先你需要安装 **Godot4.0.3 Net** 👉[GodotEngine.Net](https://godotengine.org/download/windows/) 2.通过Git Clone一些仓库到与该仓库相同路径的文件夹,文件结构看起来像这样: > GitHub (你的项目文件夹的上一级) > >iFactory.Godot [外网仓库链接](http://server.bitfall.icu:3000/root/iFactory.Godot.git) > > >BITKit [外网仓库链接](http://server.bitfall.icu:3000/root/BITKit.git) > > >BITKit.Godot [BITKit.Godot](http://server.bitfall.icu:3000/root/BITKit.Godot) > > >AGV_System [内网仓库链接](http://192.168.1.50:3000/cn-intelli/AGV_System.git) 3.最后在**Godot**中导入**iFactory.Godot** 4.安装完成⭐ *** ## Features 功能与模块 ### 场景介绍 * #### 基于AGV小车提供的三维环境感知场景 * 基于AGV小车提供的三维环境感知,可视化当前小车的环境,任务和运行路线,可演示并手动控制小车 * 支持演示模式,实时为小车编辑任务并模拟运行和实际运行演示 * #### CPS数字孪生验证场景 * 由于塞昇提供的一体机没有独立显卡,该项目首先为塞昇提供低配可流畅运行的领克和嘉际模型小车装配过程验证 * 当前塞昇客户端的运行情况为运行时占用2G内存和20FPS,长时间运行后占用6G内存和1FPS * #### 虚拟客机装配 * 该场景主要演示在该软件的快速规划下,自定义小车,装配路线和过程,验证及演示装配客机的过程 * #### 虚拟泊车演示 * 该场景主要用于验证和展示泊车AGV的调度方案,通过添加车辆(停车)和调取车辆(取车)的方式,验证泊车方案和并展示泊车过程 ### 功能介绍 #### 机位同步 * 超炫酷`零延迟`机位同步(可能需要一些硬件支持) * 基于角度的`Loopback`算法,解决了同步角度和位置时0>1>0的值被线性插值解析为0>0.7414>0的问题 * 基于物理`PBR`的三维模型,确保和现实中的设备看起来几乎一致 * 基于`Lancer.SCADA`返回数据的物料同步(例如`SR7C1L`夹着物料`多功能笔记本`放进`交付气缸夹`) #### 实时数据 * 通过`Lancer.SCADA`返回的数据,试试展示重要数据 #### 生产回放 * 录制生产时的数据,可离线播放生产过程 * 支持数据追溯,收集和录制重要数据,可对数据进行加工、提炼和分析,找到可能存在的重要数据 * WebGL版本支持云端回放 #### 环境扫描 * 基于具有环境感知的设备对现场环境进行扫描,快速还原现场外观和环境 * 支持多个具有环境感知的设备同时对现场环境进行并行扫描 #### 监控和摄像头接入 * 接入`网络摄像头`和`串口摄像头` * 支持`RTSP`协议,支持网络流媒体直播(例如`海康RSTP`) #### 布局规划 * 拖放`模型库`/`预制件`到场景中,快速完成场景的基础规划 * 选中`实体`并编辑数值或节点,可快速定制`模型`,例如 * 自定义围墙,围栏,输送带的尺寸,如 长`4096mm`,宽`256mm` * 编辑贝塞尔曲线,标记`AGV`或其他可运动设备的`路线` * 快速替换预览设备的型号,例如将`KUKA.AGV`替换为`Intelli.AGV`\ #### 沙盒仿真(南岸信的可能性需求) * 基于`行为树`的顺序执行模式带来相对直观的设计 * 基于`行为树`你可以直接设计生产流程,并支持通过http接入MES系统 * 行为树支持`Godot Editor`和`Runtime`模式,可以在编辑器或运行时编辑行为 * 基于行为树,你可以让`Entity`和`Signal`执行各种各样的操作,包括且不限于: * AGV移动到指定位置(可等待到达,或指定到达时间以及移动速度) * 等待条件达成(e.g.等待某个信号`Signal`)后执行操作 * 手动触发信号,以及设置信号断点 * 支持信号日志 * 根据需求接入`ABB Studio`,接入后可直接在`ABB Studio`或`西门子 PLC`等平台中直接设计程序,并在`iFactory`中进行仿真演算和验证 #### 教学平台与考试模式 * 支持课程编辑器,可根据提供的文件快速生成课程讲解与教学实训 * 基于与`ABB Studio`之间的通讯,可设置相关实训和考试目标,在`ABB Studio`中设计程序,并在`iFactory`中验证题目是否完成并给出评分 * 支持考试模式,主要的监管客户端可监控所有实训客户端的所有状态,支持对实训端给出信号或干预("某极域客户端的黑屏提示") #### 标识解析与注册 * 支持手动为相关元素(例如`物料`和`工序`等)进行标识码注册与解析 * 支持标识解析可视化,根据当前进度和数据实时显示重要信息到看板中 * 实训模式和考试模式下支持模拟标识注册与解析 #### 方案展示 基于`布局规划`和`沙盒仿真`的组合展示场景,向用户展示方案的设计,过程与验证结果 #### 虚实联动 * 手动控制`AGV`或`机械臂`等设备 * 已支持`Lancer.AGV`可手动控制小车移动 * 手动编辑机械臂角度信息,在没有示教器的情况下进行手动演示 * 可手动编辑并发起相关信号,例如亮灯,单片机信号以及自定义指令等 #### 多终端实时同步场景 * `PC客户端`进行布局规划,`Mixed Reality`客户端(例如`AR`)可直接在现场看到设备规划的位置和生产路线 * 虚拟化生产过程,用户可通过`手机AR`或者`AR眼睛`在现场预览生产过程并对过程进行评估 * `AGV`夹起`物料`放入`装配单元` * `AGV`向`装配单元`提供`物料` * `装配单元`进行虚拟化装配 * `AGV`从`装配单元`取走产品,并放入`交付单元` * `所有客户端`可对现实坐标进行标记(`文字消息` `图像消息`) * 标记需要改进的工序或移动的位置 * 标记用户希望的生产路线 #### 数据查询 * 可快速查询数据库和记录,例如工单数据和异常记录 * 开发者模式下,可通过自定义数据库查询语句 * 具有完整的权限体系,用户模式下仅可查询部分记录,开发者模式下通过`数字签名`或`电子令牌`查询所有记录 #### 智能诊断 * 基于`Lancer.SCADA`提供的数据,自动分析重要数据的值并返回异常和诊断数据 * 可通过`WebApi`接口下发异常 * 接入`微信`和`E-Mail`,`异常` `诊断报告`等日常维护数据将自动推送给相关人员 * `微信`将通过公众号推送工单的方式推送数据 * `E-Mail`通过自建邮箱服务器推送数据 * 智能诊断建议 * 在无法自动解决异常时,分析并提供相关的操作建议和解决方式建议 * 异常解决后,可快速输入异常解决方式,当异常再次发生时,可快速自动解决或快速提供建议 ### 功能清单 以下的大多数功能都正在从`Unity`移植到`Godot`中 - [x] 基于`Lancer.SCADA`的基本数据请求服务`WebApi/GetInfos`——请求json,处理json嵌套,向内部提交数据 - [x] 基于`Lancer.SCADA`的基本的角度和位置同步 - [ ] 基于`AI`算法的零延迟同步 - [ ] 基于`硬件本身的api`提供的零延迟同步 - [ ] 基于`Lancer.SCADA`的装配过程同步 - [ ] 基于`ObjectMatcher`的装配过程同步——`Lancer.SCADA`没有提供的数据,通过匹配关键词的方式对数据进行分析和匹配,计算出当前的物料情况 - [ ] `ObjectMatcher`——关键词匹配引擎,通过分析数据,得到对应的最接近的结果 - [ ] 全平台通用网络通讯接口(基于KCP的基本网络服务)`NetProvider` `NetClient` `NetServer` - [ ] 基于`Camera3D`的虚拟相机服务`观察场景` 例如`移动视角` `拖动视角` `缩放视角` `保存与加载预设视角` - [ ] `Android`支持 - [ ] `WebGL` 支持 - [x] `依赖注入`——已接入`Microsoft.Extension.DependencyInjection` - [ ] `数据库支持`——通过`EntityFrameworkCore`接入数据库 - [ ] `离线回放`——录制`Lancer.SCADA`返回的数据, - [ ] `在线回放`——基于`WebDAB`同步云端的录制数据 - [ ] `场景编辑器`——拖动预制件到场景中,完成组合场景,可用于对现场规划进行三维浏览和布局评估 - [ ] `网络场景`——多个平台和客户端同步场景,例如在PC上更改场景,在`Mixed Reality`中可实时看到场景的更改 - [ ] `自诊断服务`——通过解析数据,找到`空值`或`null`的数据并向指定接口(例如`微信推送` `数据库日志`)提交异常 - [x] `微信推送服务`——完成申请微信公众号后接入 - [ ] `E-Mail推送服务`——通过`宝塔邮局`推送 - [ ] `文件打包服务`——保存生产回放和其他数据,都是通过该服务进行读取和写入的 - [ ] `环境扫描`——通过对现场环境进行三维扫描和重建,得到最接近和还原的三维场景 - [ ] 基于HTML或类HTML的`用户界面框架`——统一技术栈,使用html编写用户界面,例如`Html`和`Unity.UI Toolkit`的关系 - [ ] `标识解析`——支持手动注册与解析标识 - [ ] `实训平台`——提供线上教学线下考试的服务 *** ## Getting Started 使用指南 所有注释和指南都包括在代码中,用例正在编写中 ### 依赖注入 基于`Microsoft.Extensions.DependencyInjection`的依赖注入服务 用例: #### 注册服务 ```csharp public interface IService{} public class MyService:Node,IService { //注入服务 BITApp.ServiceCollection.AddSingleton(this); } ``` #### 注入服务 ##### 初始化加载(可能遇到服务还没注入的情况) ```csharp public class MyService:Node { public override void _Ready() { var myService = BITApp.ServiceProvider.GetService(); } } ``` ##### 基于封装的懒加载 ```csharp public class MyService:Node { //声明懒加载服务 private readonly ServiceLoader myService=new(); public override void _Process(double delta) { //如果服务未加载,则跳过执行 if(myService.Loaded is false)return; //获取服务 var _myService = myService.Value; //执行 _myService.DoSomething(); } } ``` *** ### 实体,数据与系统的结构 > Root/场景 > > `Service` \ `System` 处理逻辑和数据的服务或系统 > > > `RotationService` 获取数据并应用角度的服务 > > > `Entity` 存放数据的基本实体 > > > `RotationComponent`存放数据的组件,例如角度偏移`Offset`和角度权重`Weight`等数据 *** ### 创建基本组件 组件需要继承自`EntityComponent`或者`IEntityComponent`,这样`Entity`才能识别并注册组件 ```csharp //基本的角度组件 /// /// ECS中iFactory.Rotation的角度组件 /// public partial class RotationComponent : EntityComponent { /// /// 获取角度的路径 /// [Export] public string Path { get; private set; } /// /// 角度的绝对权重,例如90* (0,0,1) = 0,0,90 /// [Export] public Vector3 Weight { get; private set; } /// /// 角度的相对偏移 /// [Export] public Vector3 Offset { get; private set; } /// /// 默认角度的缓存 /// public Vector3 OriginalEuler { get; private set; } public override void _Ready() { //保存默认角度 OriginalEuler = Rotation; } } ``` *** ### 创建服务 基于场景的服务,可直接放入场景中的节点即可 基于全局的服务,可在Godot.ProjectSettings.AutoLoad中添加自动加载 *** #### 非ECS服务 ```csharp public partial class RotationService:Node { //在构造函数中注入依赖 public ApplyRotationService() { BITApp.ServiceCollection.AddSingleton(this); } //非基于ECS的注册方式,例如Node在_Ready回调中调用该方法注册 public static void Register(IObj obj); //基于非ECS的注册方式,例如Node在Dispose方法中调用该方法注销 public static void UnRegister(IObj obj) //通常情况下,Register和UnRegister最好使用Queue作为队列的方式注册和注销元素,而非List.Add和List.Remove //使用消息队列可以解决异步中动态注册和注销导致队列被更改的问题 public override _Process(double delta) { //在这里遍历已注册的元素,并 foreach(var element in registriedElement) { //你的Code,不,是你的Code element.Rotation = new(0,1,0); } } } ``` *** #### 基于ECS的服务(System) 目前`IEntitiesService`的实例为`GodotEntitiesService` ```csharp public partial class RotationService:Node { //在构造函数中注入依赖 public ApplyRotationService() { BITApp.ServiceCollection.AddSingleton(this); } public override _Process(double delta) { //遍历所有包含RotationComponent的Entity foreach(var entity in EntitiesManager.Query()) { //或者RotationComponent组件 if(entity.TryGetComponent(out var rotationComponent) { //获取该组件的角度 var angle = //你的Code //应用组件计算后的角度 rotationComponent.Rotation = angle * rotationComponent.Weight; } } } } ``` #### 配置UI 首先你需要确保基于`Control`节点的`UXService`存在与`项目设置`中的`自动加载`中 ##### 创建UI面板 接下来,创建一个新的类,继承自`IUXPanel`或者直接为UI面板的节点添加`UXPanel`就完成了UI的基本配置 `UXService`是基于消息队列的,你可以从多线程中添加队列 `UXService`支持`Return`,例如: ```csharp //进入提示面板 UXService.Entry(); //返回到当前面板 UXService.Return(); ``` ##### 如何进入某个UI面板? 在脚本中调用 ```csharp UXService.Entry(this as IUXPanel); ``` 或者在信号中连接面板 ```csharp UXPanel.Open(); ``` #### 自动加载配置文件 你需要手动在场景中添加节点`Exec`并配置好`json`文件的相对路径,例如:`Mods/EIPC/appsettings.json` 默认情况下,都会加载项目根目录下的`appsettings.json` 或者可以手动加载: ```csharp DataParser.Set(json); ``` 所有基本的json类型都会加载到全局变量`Data`中 #### 获取和监听配置文件 ```csharp //获取配置(全局变量) Data.Get(string:key); //获取单个配置 Data.Get(); //监听变量 Data.AddListener("MyEvent",MyCallback); void MyCallaback(T value); //设置变量 Data.Invoke("MyEvent",value); ``` #### 基于UniTask的异步与多线程使用方式 原理就是在处理数据时切换到线程池,并在处理完成后返回主线程 这里涉及到 ```csharp //处理任务之前的准备,例如 //创建计时器,计算任务耗时 Stopwatch stopwatch = new(); stopwatch.Start(); //切换到任务池(默认为线程池) await UniTask.SwitchToTaskPool(); //在这里处理你的复杂任务,例如: DiggingBitcoin(); CrackWifiPassword(); RmRf(); //在完成任务后,切换回主线程 try { //切换回主线程的同时,应该添加CancellationToken await UniTask.SwitchToSynchronizationContext(BITApp.SynchronizationContext, cancellationTokenSource.Token); } catch (OperationCanceledException) { //任务取消后应该怎么处理,比如回滚数据 return; } //完成任务后,打印出任务耗时时间 Console.WriteLine($"该任务耗时:{stopwatch.ElapsedMilliseconds}ms"); ``` ##### 怎么取消任务 在创建await任务时,添加CancellationToken 通常CancellationToken是这么构成的 ```csharp //声明一个CancellationTokenSource private CancellationTokenSource cancellationTokenSource; public override void _Ready() { //创建新的CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); } public override void _ExitTree() { //取消任务 cancellationTokenSource.Cancel(); } public override void _Process() { //如果任务已取消,抛出任务取消的异常 cancellationTokenSource.Token.ThrowIfCancellationRequested(); //用try catch捕获取消异常 try { //等待某个任务,并添加CancellationToken await SomeAsync(cancellationTokenSource.Token); }catch(OperationCanceledException) { //任务已取消,你需要做些什么,比如回滚数据 } } ```