VisionDynamicAccuracyAnalyzerViewModel.cs 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ToolBlock;
  3. using MaterialDesignThemes.Wpf;
  4. using Newtonsoft.Json;
  5. using OxyPlot;
  6. using Prism.Commands;
  7. using Prism.Events;
  8. using Prism.Ioc;
  9. using Prism.Mvvm;
  10. using Prism.Regions;
  11. using Prism.Services.Dialogs;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Collections.ObjectModel;
  15. using System.Data;
  16. using System.IO;
  17. using System.Linq;
  18. using System.Threading;
  19. using System.Threading.Tasks;
  20. using Team.FFFeederService.Interfaces;
  21. using TeamAAS_VP.Core;
  22. using TeamAAS_VP.Core.PLCs;
  23. using TeamAAS_VP.Enums;
  24. using TeamAAS_VP.Interfaces;
  25. using TeamAAS_VP.Models;
  26. using TeamAAS_VP.Resources.Languages;
  27. using TeamAAS_VP.Services;
  28. using static TeamAAS_VP.Core.StabilityAnalyzer;
  29. namespace TeamAAS_VP.ViewModels.Product
  30. {
  31. public class VisionDynamicAccuracyAnalyzerViewModel : BindableBase, IDialogAware
  32. {
  33. IRegionManager _regionManager;
  34. IEventAggregator _eventAggregator;
  35. IContainerProvider _container;
  36. IDialogService _dialogService;
  37. IFeederService _feederService;
  38. IRobotService _robotService;
  39. ICameraService _cameraService;
  40. ISystemDatabaseService _systemDatabaseService;
  41. ICalibrationService _calibrationService;
  42. IConfigService _configService;
  43. IPlcService _plcService;
  44. //测试过程中控制取消的CTS
  45. CancellationTokenSource _cts;
  46. string manualToWork = "ns=4;s=DB_DriveControl|CylCtrl[6].Cmd.ManuToWork";
  47. string manualToHome = "ns=4;s=DB_DriveControl|CylCtrl[6].Cmd.ManuToHome";
  48. string inWork = "ns=4;s=DB_DriveControl|CylCtrl[6].Status.InWork";
  49. string inHome = "ns=4;s=DB_DriveControl|CylCtrl[6].Status.InHome";
  50. #region 属性
  51. private ProductModel _SelectProduct;
  52. public ProductModel SelectProduct
  53. {
  54. get { return _SelectProduct; }
  55. set { SetProperty(ref _SelectProduct, value); }
  56. }
  57. private ICogImage _Image;
  58. public ICogImage Image
  59. {
  60. get { return _Image; }
  61. set { SetProperty(ref _Image, value); }
  62. }
  63. private Cognex.VisionPro.CogGraphicCollection _Graphic;
  64. public Cognex.VisionPro.CogGraphicCollection Graphic
  65. {
  66. get { return _Graphic; }
  67. set { SetProperty(ref _Graphic, value); }
  68. }
  69. private ProcedureModel _SelectProcedure;
  70. /// <summary>
  71. /// 选中的流程
  72. /// </summary>
  73. public ProcedureModel SelectProcedure
  74. {
  75. get { return _SelectProcedure; }
  76. set
  77. {
  78. SetProperty(ref _SelectProcedure, value);
  79. }
  80. }
  81. private ICamera _Camera;
  82. public ICamera Camera
  83. {
  84. get { return _Camera; }
  85. set { SetProperty(ref _Camera, value); }
  86. }
  87. private IRobot _Robot;
  88. public IRobot Robot
  89. {
  90. get { return _Robot; }
  91. set { SetProperty(ref _Robot, value); }
  92. }
  93. private string _Message = "等待开始...";
  94. public string Message
  95. {
  96. get { return _Message; }
  97. set { SetProperty(ref _Message, value); }
  98. }
  99. private bool _IsRunning;
  100. public bool IsRunning
  101. {
  102. get { return _IsRunning; }
  103. set { SetProperty(ref _IsRunning, value); }
  104. }
  105. private CogToolBlock _VisionTool;
  106. public CogToolBlock VisionTool
  107. {
  108. get { return _VisionTool; }
  109. set { SetProperty(ref _VisionTool, value); }
  110. }
  111. private int _RepeatCount = 10;
  112. /// <summary>
  113. /// 重复次数
  114. /// </summary>
  115. public int RepeatCount
  116. {
  117. get { return _RepeatCount; }
  118. set { SetProperty(ref _RepeatCount, value); }
  119. }
  120. private int _CurrentProgress;
  121. /// <summary>
  122. /// 当前进度
  123. /// </summary>
  124. public int CurrentProgress
  125. {
  126. get { return _CurrentProgress; }
  127. set { SetProperty(ref _CurrentProgress, value); }
  128. }
  129. private int _SuccessCount;
  130. /// <summary>
  131. /// 成功次数
  132. /// </summary>
  133. public int SuccessCount
  134. {
  135. get { return _SuccessCount; }
  136. set { SetProperty(ref _SuccessCount, value); }
  137. }
  138. private int _FailCount;
  139. /// <summary>
  140. /// 失败次数
  141. /// </summary>
  142. public int FailCount
  143. {
  144. get { return _FailCount; }
  145. set { SetProperty(ref _FailCount, value); }
  146. }
  147. private double _SuccessRate;
  148. /// <summary>
  149. /// 成功率
  150. /// </summary>
  151. public double SuccessRate
  152. {
  153. get { return _SuccessRate; }
  154. set { SetProperty(ref _SuccessRate, value); }
  155. }
  156. private ObservableCollection<DynamicTestResult> _TestResults;
  157. /// <summary>
  158. /// 测试结果列表
  159. /// </summary>
  160. public ObservableCollection<DynamicTestResult> TestResults
  161. {
  162. get { return _TestResults; }
  163. set { SetProperty(ref _TestResults, value); }
  164. }
  165. private SnackbarMessageQueue _MessageQueue;
  166. public SnackbarMessageQueue MessageQueue
  167. {
  168. get { return _MessageQueue; }
  169. set { SetProperty(ref _MessageQueue, value); }
  170. }
  171. private RPoint _PointA;
  172. public RPoint PointA
  173. {
  174. get { return _PointA; }
  175. set { SetProperty(ref _PointA, value); }
  176. }
  177. private RPoint _PointB;
  178. public RPoint PointB
  179. {
  180. get { return _PointB; }
  181. set { SetProperty(ref _PointB, value); }
  182. }
  183. private ObservableCollection<ProcedureModel> _ProcedureModels;
  184. /// <summary>
  185. /// 流程集合
  186. /// </summary>
  187. public ObservableCollection<ProcedureModel> ProcedureModels
  188. {
  189. get { return _ProcedureModels; }
  190. set { SetProperty(ref _ProcedureModels, value); }
  191. }
  192. private int _MoveSpeed = 100;
  193. /// <summary>
  194. /// 移动速度
  195. /// </summary>
  196. public int MoveSpeed
  197. {
  198. get { return _MoveSpeed; }
  199. set { SetProperty(ref _MoveSpeed, value); }
  200. }
  201. private double _CycleTime;
  202. /// <summary>
  203. /// 单次的时长
  204. /// </summary>
  205. public double CycleTime
  206. {
  207. get { return _CycleTime; }
  208. set { SetProperty(ref _CycleTime, value); }
  209. }
  210. private PrecisionAnalysisResult _precisionResult = new PrecisionAnalysisResult();
  211. /// <summary>
  212. /// 精度分析结果
  213. /// </summary>
  214. public PrecisionAnalysisResult PrecisionResult
  215. {
  216. get { return _precisionResult; }
  217. set { SetProperty(ref _precisionResult, value); }
  218. }
  219. private int _ADelay = 3000;
  220. /// <summary>
  221. /// A点延时时间
  222. /// </summary>
  223. public int ADelay
  224. {
  225. get { return _ADelay; }
  226. set { SetProperty(ref _ADelay, value); }
  227. }
  228. private int _BDelay = 3000;
  229. /// <summary>
  230. /// B点延时时间
  231. /// </summary>
  232. public int BDelay
  233. {
  234. get { return _BDelay; }
  235. set { SetProperty(ref _BDelay, value); }
  236. }
  237. private bool _IsTakePhoto = true;
  238. /// <summary>
  239. /// 是否启用拍照
  240. /// </summary>
  241. public bool IsTakePhoto
  242. {
  243. get { return _IsTakePhoto; }
  244. set { SetProperty(ref _IsTakePhoto, value); }
  245. }
  246. private bool _IsCylCtrl = true;
  247. /// <summary>
  248. /// 是否启用气缸IsVertical
  249. /// </summary>
  250. public bool IsCylCtrl
  251. {
  252. get { return _IsCylCtrl; }
  253. set { SetProperty(ref _IsCylCtrl, value); }
  254. }
  255. private bool _IsVertical = true;
  256. /// <summary>
  257. /// 是否启用垂直精度检测
  258. /// </summary>
  259. public bool IsVertical
  260. {
  261. get { return _IsVertical; }
  262. set { SetProperty(ref _IsVertical, value); }
  263. }
  264. private float _Distance;
  265. public float Distance
  266. {
  267. get { return _Distance; }
  268. set { SetProperty(ref _Distance, value); }
  269. }
  270. #endregion
  271. #region 命令
  272. private DelegateCommand _ConfirmCommand;
  273. public DelegateCommand ConfirmCommand =>
  274. _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  275. private DelegateCommand _CancelCommand;
  276. public DelegateCommand CancelCommand =>
  277. _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  278. private DelegateCommand _StartTestCommand;
  279. public DelegateCommand StartTestCommand =>
  280. _StartTestCommand ?? (_StartTestCommand = new DelegateCommand(ExecuteStartTestCommand, CanExecuteStartTestCommand).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectProcedure).ObservesProperty(() => PointA).ObservesProperty(() => PointB));
  281. private DelegateCommand _StopTestCommand;
  282. public DelegateCommand StopTestCommand =>
  283. _StopTestCommand ?? (_StopTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  284. //暂停测试命令
  285. private DelegateCommand _PauseTestCommand;
  286. public DelegateCommand PauseTestCommand =>
  287. _PauseTestCommand ?? (_PauseTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  288. private DelegateCommand _ExportCSVCommand;
  289. public DelegateCommand ExportCSVCommand =>
  290. _ExportCSVCommand ?? (_ExportCSVCommand = new DelegateCommand(ExecuteExportCSVCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  291. private DelegateCommand _ExportReportCommand;
  292. public DelegateCommand ExportReportCommand =>
  293. _ExportReportCommand ?? (_ExportReportCommand = new DelegateCommand(ExecuteExportReportCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  294. private DelegateCommand _ClearDataCommand;
  295. public DelegateCommand ClearDataCommand =>
  296. _ClearDataCommand ?? (_ClearDataCommand = new DelegateCommand(ExecuteClearDataCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  297. private DelegateCommand _TeachPointACommand;
  298. public DelegateCommand TeachPointACommand =>
  299. _TeachPointACommand ?? (_TeachPointACommand = new DelegateCommand(ExecuteTeachPointACommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  300. private DelegateCommand _TeachPointBCommand;
  301. public DelegateCommand TeachPointBCommand =>
  302. _TeachPointBCommand ?? (_TeachPointBCommand = new DelegateCommand(ExecuteTeachPointBCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  303. private DelegateCommand _ProcedureSelectionChangedCommand;
  304. public DelegateCommand ProcedureSelectionChangedCommand =>
  305. _ProcedureSelectionChangedCommand ?? (_ProcedureSelectionChangedCommand = new DelegateCommand(ExecuteProcedureSelectionChangedCommand, () => !IsRunning).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectProcedure));
  306. private DelegateCommand _SingleTestCommand;
  307. public DelegateCommand SingleTestCommand =>
  308. _SingleTestCommand ?? (_SingleTestCommand = new DelegateCommand(ExecuteSingleTestCommand, () => !IsRunning && SelectProcedure != null && PointA != null && PointB != null).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectProcedure).ObservesProperty(() => PointA).ObservesProperty(() => PointB));
  309. private DelegateCommand _ShowTrendChartCommand;
  310. public DelegateCommand ShowTrendChartCommand =>
  311. _ShowTrendChartCommand ?? (_ShowTrendChartCommand = new DelegateCommand(ExecuteShowTrendChartCommand));
  312. #endregion
  313. #region 事件
  314. #endregion
  315. public VisionDynamicAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
  316. IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService,
  317. IConfigService configService, IPlcService plcService)
  318. {
  319. _regionManager = regionManager;
  320. _eventAggregator = ea;
  321. _container = container;
  322. _dialogService = dialogService;
  323. _feederService = feederService;
  324. _robotService = robotService;
  325. _cameraService = cameraService;
  326. _systemDatabaseService = systemDatabaseService;
  327. MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
  328. _calibrationService = calibrationService;
  329. _configService = configService;
  330. _plcService = plcService;
  331. }
  332. #region 方法
  333. /// <summary>
  334. /// 确定
  335. /// </summary>
  336. void ExecuteConfirmCommand()
  337. {
  338. _cts?.Cancel();
  339. IDialogParameters parameters = new DialogParameters();
  340. //parameters.Add("Tool", Tool);
  341. //parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
  342. //parameters.Add("Angle", Angle);
  343. RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
  344. }
  345. /// <summary>
  346. /// 取消
  347. /// </summary>
  348. void ExecuteCancelCommand()
  349. {
  350. _cts?.Cancel();
  351. IDialogParameters parameters = new DialogParameters();
  352. //parameters.Add("Tool", Tool);
  353. RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
  354. }
  355. /// <summary>
  356. /// 单次测试
  357. /// </summary>
  358. async void ExecuteSingleTestCommand()
  359. {
  360. IsRunning = true;
  361. _cts = new CancellationTokenSource();
  362. CurrentProgress = 0;
  363. SuccessCount = 0;
  364. SuccessRate = 0;
  365. FailCount = 0;
  366. Message = "测试进行中...";
  367. TestResults = new ObservableCollection<DynamicTestResult>();
  368. try
  369. {
  370. //弹窗提示是否进行连续测试
  371. var result = System.Windows.MessageBox.Show("确定进行单次测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
  372. if (result != System.Windows.MessageBoxResult.Yes)
  373. {
  374. return;
  375. }
  376. var addressConfig = _configService.GetPlcAddresses();
  377. var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC;
  378. if (Robot == null || !Robot.IsConnected)
  379. {
  380. SendTaskMessage("机器人未连接,无法示教!");
  381. return;
  382. }
  383. if (IsTakePhoto)
  384. {
  385. if (Camera == null || !Camera.IsConnected)
  386. {
  387. SendTaskMessage("相机未连接,无法示教!");
  388. return;
  389. }
  390. }
  391. //为机器人设置移动速度
  392. await Robot.CalibParameAsync(new PickInfo(), MoveSpeed, 0, false, 0, 0);
  393. if (_cts.Token.IsCancellationRequested)
  394. {
  395. Message = "测试已取消!";
  396. return;
  397. }
  398. DateTime startTime = DateTime.Now;
  399. if (!IsCylCtrl)
  400. {
  401. //移动机器人至A点
  402. bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
  403. if (!isSuccess1)
  404. {
  405. SendTaskMessage("机器人移动到A点失败!");
  406. return;
  407. }
  408. await Task.Delay(ADelay);
  409. if (_cts.Token.IsCancellationRequested)
  410. {
  411. Message = "测试已取消!";
  412. return;
  413. }
  414. //移动机器人至B点
  415. isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
  416. if (!isSuccess1)
  417. {
  418. SendTaskMessage("机器人移动到B点失败!");
  419. return;
  420. }
  421. await Task.Delay(BDelay);
  422. if (_cts.Token.IsCancellationRequested)
  423. {
  424. Message = "测试已取消!";
  425. return;
  426. }
  427. }
  428. else
  429. {
  430. //气缸升上去
  431. await plc.WriteNodeAsync(manualToWork, true);
  432. await Task.Delay(BDelay);
  433. await plc.ReadNodeAsync<bool>(inWork);
  434. if (_cts.Token.IsCancellationRequested)
  435. {
  436. Message = "测试已取消!";
  437. return;
  438. }
  439. }
  440. if (IsTakePhoto)
  441. {
  442. //相机拍照并处理
  443. (bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY) = await ExecutePhotoEx(CurrentProgress);
  444. CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
  445. if (isSuccess)
  446. {
  447. SuccessCount++;
  448. }
  449. else
  450. {
  451. FailCount++;
  452. }
  453. TestResults.Add(new DynamicTestResult(CurrentProgress, isSuccess, PointB, CycleTime, pixelX, pixelY, angleU, transformX, transformY));
  454. OnTestResultAdded();
  455. CurrentProgress++;
  456. SuccessRate = (double)SuccessCount / CurrentProgress * 100;
  457. }
  458. else
  459. {
  460. CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
  461. SuccessCount++;
  462. CurrentProgress++;
  463. SuccessRate = (double)SuccessCount / CurrentProgress * 100;
  464. }
  465. if (IsCylCtrl)
  466. {
  467. await plc.WriteNodeAsync(manualToHome, true);
  468. await Task.Delay(ADelay);
  469. //inHome 气杆在底部 InWork在上面
  470. await plc.ReadNodeAsync<bool>(inHome);
  471. if (_cts.Token.IsCancellationRequested)
  472. {
  473. Message = "测试已取消!";
  474. return;
  475. }
  476. }
  477. Message = "测试完成!";
  478. }
  479. catch (Exception ex)
  480. {
  481. LogHelper.WriteLogError("视觉动态重复测试时出错!", ex);
  482. }
  483. finally
  484. {
  485. IsRunning = false;
  486. }
  487. }
  488. /// <summary>
  489. /// 连续测试
  490. /// </summary>
  491. async void ExecuteStartTestCommand()
  492. {
  493. IsRunning = true;
  494. _cts = new CancellationTokenSource();
  495. CurrentProgress = 0;
  496. SuccessCount = 0;
  497. SuccessRate = 0;
  498. FailCount = 0;
  499. Message = "测试进行中...";
  500. int up = 1;
  501. int down = 0;
  502. TestResults = new ObservableCollection<DynamicTestResult>();
  503. try
  504. {
  505. //弹窗提示是否进行连续测试
  506. var result = System.Windows.MessageBox.Show("确定进行连续测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
  507. if (result != System.Windows.MessageBoxResult.Yes)
  508. {
  509. return;
  510. }
  511. var addressConfig = _configService.GetPlcAddresses();
  512. var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC;
  513. if (Robot == null || !Robot.IsConnected)
  514. {
  515. SendTaskMessage("机器人未连接,无法示教!");
  516. return;
  517. }
  518. if (IsTakePhoto)
  519. {
  520. if (Camera == null || !Camera.IsConnected)
  521. {
  522. SendTaskMessage("相机未连接,无法示教!");
  523. return;
  524. }
  525. }
  526. //为机器人设置移动速度
  527. await Robot.CalibParameAsync(new PickInfo(), MoveSpeed, 0, false, 0, 0);
  528. while (CurrentProgress < RepeatCount)
  529. {
  530. if (_cts.Token.IsCancellationRequested)
  531. {
  532. Message = "测试已取消!";
  533. break;
  534. }
  535. DateTime startTime = DateTime.Now;
  536. if (IsVertical)
  537. {
  538. if (CurrentProgress == 0)
  539. {
  540. await Robot.JumpAsync(PointB, null);
  541. }
  542. RPoint verticalPosition = PointB.Clone();
  543. if (CurrentProgress > (RepeatCount / 2))
  544. {
  545. verticalPosition.Z += up++ * Distance;
  546. }
  547. else
  548. {
  549. verticalPosition.Z -= down++ * Distance;
  550. }
  551. //移动机器人至B点
  552. bool isSuccess1 = await Robot.GoAsync(verticalPosition);
  553. if (!isSuccess1)
  554. {
  555. SendTaskMessage("机器人移动到B点失败!");
  556. return;
  557. }
  558. await Task.Delay(BDelay);
  559. if (_cts.Token.IsCancellationRequested)
  560. {
  561. Message = "测试已取消!";
  562. break;
  563. }
  564. }
  565. else if (!IsCylCtrl)
  566. {
  567. IsVertical = false;
  568. //移动机器人至A点
  569. bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
  570. if (!isSuccess1)
  571. {
  572. SendTaskMessage("机器人移动到A点失败!");
  573. return;
  574. }
  575. await Task.Delay(ADelay);
  576. if (_cts.Token.IsCancellationRequested)
  577. {
  578. Message = "测试已取消!";
  579. break;
  580. }
  581. //移动机器人至B点
  582. isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
  583. if (!isSuccess1)
  584. {
  585. SendTaskMessage("机器人移动到B点失败!");
  586. return;
  587. }
  588. await Task.Delay(BDelay);
  589. if (_cts.Token.IsCancellationRequested)
  590. {
  591. Message = "测试已取消!";
  592. break;
  593. }
  594. }
  595. else
  596. {
  597. //气缸升上去
  598. await plc.WriteNodeAsync(manualToWork, true);
  599. await Task.Delay(BDelay);
  600. await plc.ReadNodeAsync<bool>(inWork);
  601. if (_cts.Token.IsCancellationRequested)
  602. {
  603. Message = "测试已取消!";
  604. break;
  605. }
  606. }
  607. if (IsTakePhoto)
  608. {
  609. //相机拍照并处理
  610. (bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY) = await ExecutePhotoEx(CurrentProgress);
  611. CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
  612. if (isSuccess)
  613. {
  614. SuccessCount++;
  615. }
  616. else
  617. {
  618. FailCount++;
  619. }
  620. TestResults.Add(new DynamicTestResult(CurrentProgress, isSuccess, PointB, CycleTime, pixelX, pixelY, angleU, transformX, transformY));
  621. OnTestResultAdded();
  622. CurrentProgress++;
  623. SuccessRate = (double)SuccessCount / CurrentProgress * 100;
  624. }
  625. else
  626. {
  627. CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
  628. SuccessCount++;
  629. CurrentProgress++;
  630. SuccessRate = (double)SuccessCount / CurrentProgress * 100;
  631. }
  632. if (IsCylCtrl)
  633. {
  634. await plc.WriteNodeAsync(manualToHome, true);
  635. await Task.Delay(ADelay);
  636. //inHome 气杆在底部 InWork在上面
  637. await plc.ReadNodeAsync<bool>(inHome);
  638. if (_cts.Token.IsCancellationRequested)
  639. {
  640. Message = "测试已取消!";
  641. break;
  642. }
  643. }
  644. SendTaskMessage($"测试进行中... {CurrentProgress}/{RepeatCount}");
  645. }
  646. if (CurrentProgress >= RepeatCount)
  647. {
  648. Message = "测试完成!";
  649. }
  650. }
  651. catch (Exception ex)
  652. {
  653. LogHelper.WriteLogError("视觉动态重复测试时出错!", ex);
  654. }
  655. finally
  656. {
  657. IsRunning = false;
  658. }
  659. }
  660. bool CanExecuteStartTestCommand()
  661. {
  662. return !IsRunning && SelectProcedure != null && PointA != null && PointB != null;
  663. }
  664. /// <summary>
  665. /// 清除数据
  666. /// </summary>
  667. void ExecuteClearDataCommand()
  668. {
  669. if (TestResults != null)
  670. {
  671. TestResults.Clear();
  672. }
  673. }
  674. /// <summary>
  675. /// 导出测试报告
  676. /// 使用 iTextSharp 生成 PDF,包含:标题、测试摘要、数据表格、每列稳定性分析结果
  677. /// 详细伪代码:
  678. /// 1. 校验 TestResults 是否存在且有数据,若无则提示并返回。
  679. /// 2. 弹出保存对话框,获取 PDF 保存路径,若取消则返回。
  680. /// 3. 使用 iTextSharp 创建 Document 和 PdfWriter,打开文档流。
  681. /// 4. 尝试从系统字体目录加载中文字体(多候选),若加载失败使用回退字体。
  682. /// 5. 写入标题(居中大号字体)和测试摘要信息(表格格式:导出时间、目标次数、当前进度、成功/失败次数、成功率)。
  683. /// 6. 创建一个 PdfPTable,列为固定的测试结果字段(Index, Timestamp, IsPhotoSuccess, PointB.X/Y/Z, CycleTime, PixelX, PixelY, AngleU, TransformX, TransformY)。
  684. /// - 写入表头(加粗)
  685. /// - 遍历 TestResults,每行按列顺序写入值(数值按 InvariantCulture 格式化,空值写空字符串)
  686. /// 7. 新页,写入“分析结果”标题。
  687. /// 8. 对于需要分析的维度(TransformX, TransformY, AngleU):
  688. /// - 从 TestResults 过滤出 IsPhotoSuccess 为 true 的样本并收集对应维度值(double)
  689. /// - 若样本数为0,写明无法计算;否则调用 StabilityAnalyzer.AnalyzeStability(values) 获取 StabilityMetrics
  690. /// - 将 StabilityMetrics 的公开属性写成两列表(属性名 / 值)写入 PDF
  691. /// 9. 关闭并释放文档、writer、流,提示导出成功;捕获异常记录日志并提示失败。
  692. /// 10. 使用 SendTaskMessage 在 UI 上通知结果。
  693. /// </summary>
  694. void ExecuteExportReportCommand()
  695. {
  696. try
  697. {
  698. if (TestResults == null || TestResults.Count == 0)
  699. {
  700. SendTaskMessage("无测试数据,无法导出报告。");
  701. return;
  702. }
  703. var dlg = new Microsoft.Win32.SaveFileDialog
  704. {
  705. DefaultExt = "pdf",
  706. Filter = "PDF 文件 (*.pdf)|*.pdf|所有文件 (*.*)|*.*",
  707. FileName = "TestReport.pdf",
  708. Title = "保存测试报告为 PDF"
  709. };
  710. bool? dlgResult = dlg.ShowDialog();
  711. if (dlgResult != true)
  712. return;
  713. string path = dlg.FileName;
  714. // 创建文档(A4, 边距)
  715. var doc = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 36, 36, 54, 54);
  716. using (var fs = System.IO.File.Create(path))
  717. {
  718. var writer = iTextSharp.text.pdf.PdfWriter.GetInstance(doc, fs);
  719. doc.Open();
  720. // 字体加载,优先中文系统字体
  721. iTextSharp.text.Font titleFont;
  722. iTextSharp.text.Font headerFont;
  723. iTextSharp.text.Font normalFont;
  724. iTextSharp.text.pdf.BaseFont baseFont = null;
  725. try
  726. {
  727. var fontsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
  728. var candidates = new[]
  729. {
  730. "msyh.ttf",
  731. "msyhbd.ttf",
  732. "simsun.ttc,0",
  733. "simsun.ttc,1",
  734. "Microsoft YaHei.ttf",
  735. "msyh.ttc,0",
  736. "msyh.ttc,1",
  737. "simhei.ttf"
  738. };
  739. foreach (var f in candidates)
  740. {
  741. var path1 = Path.Combine(fontsFolder, f);
  742. try
  743. {
  744. baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(
  745. path1,
  746. iTextSharp.text.pdf.BaseFont.IDENTITY_H,
  747. iTextSharp.text.pdf.BaseFont.EMBEDDED
  748. );
  749. if (baseFont != null)
  750. break;
  751. }
  752. catch
  753. {
  754. continue;
  755. }
  756. }
  757. }
  758. catch
  759. {
  760. baseFont = null;
  761. }
  762. if (baseFont != null)
  763. {
  764. titleFont = new iTextSharp.text.Font(baseFont, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  765. headerFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  766. normalFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  767. }
  768. else
  769. {
  770. titleFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  771. headerFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  772. normalFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  773. }
  774. // 标题
  775. var title = new iTextSharp.text.Paragraph("视觉动态精度测试报告", titleFont)
  776. {
  777. Alignment = iTextSharp.text.Element.ALIGN_CENTER,
  778. SpacingAfter = 12f
  779. };
  780. doc.Add(title);
  781. // 测试摘要
  782. var metaTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 100f };
  783. metaTable.SetWidths(new float[] { 1f, 2f });
  784. void AddMeta(string name, string value)
  785. {
  786. var cellName = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(name, headerFont)) { Border = 0, PaddingBottom = 6f };
  787. var cellVal = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(value, normalFont)) { Border = 0, PaddingBottom = 6f };
  788. metaTable.AddCell(cellName);
  789. metaTable.AddCell(cellVal);
  790. }
  791. // include device info (line, station, fixture id) but exclude MES flags/urls
  792. var device = _configService?.GetDeviceInfo();
  793. if (device != null)
  794. {
  795. AddMeta("线别", device.Line ?? string.Empty);
  796. AddMeta("站别", device.Station ?? string.Empty);
  797. AddMeta("机台号", device.FixtureId ?? string.Empty);
  798. }
  799. AddMeta("导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  800. AddMeta("测试次数(目标)", RepeatCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
  801. AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
  802. AddMeta("成功次数", SuccessCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
  803. AddMeta("失败次数", FailCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
  804. AddMeta("成功率(%)", SuccessRate.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
  805. doc.Add(metaTable);
  806. doc.Add(new iTextSharp.text.Paragraph(" ")); // 空行
  807. // 数据表格列定义
  808. var headers = new[]
  809. {
  810. "Index",
  811. "Timestamp",
  812. "IsPhotoSuccess",
  813. "PointB.X",
  814. "PointB.Y",
  815. "PointB.Z",
  816. "CycleTime(s)",
  817. "PixelX",
  818. "PixelY",
  819. "AngleU",
  820. "TransformX",
  821. "TransformY"
  822. };
  823. var table = new iTextSharp.text.pdf.PdfPTable(headers.Length) { WidthPercentage = 100f };
  824. foreach (var h in headers)
  825. {
  826. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(h, headerFont))
  827. {
  828. HorizontalAlignment = iTextSharp.text.Element.ALIGN_CENTER,
  829. BackgroundColor = new iTextSharp.text.BaseColor(230, 230, 230),
  830. Padding = 4f
  831. };
  832. table.AddCell(cell);
  833. }
  834. // 写入每一行数据
  835. foreach (var r in TestResults)
  836. {
  837. string sIndex = r.Index.ToString(System.Globalization.CultureInfo.InvariantCulture);
  838. string sTime = r.Timestamp.ToString("o", System.Globalization.CultureInfo.InvariantCulture);
  839. string sSucc = r.IsPhotoSuccess ? "True" : "False";
  840. string sPx = "";
  841. string sPy = "";
  842. string sPz = "";
  843. if (r.PointB != null)
  844. {
  845. sPx = Convert.ToString(r.PointB.X, System.Globalization.CultureInfo.InvariantCulture);
  846. sPy = Convert.ToString(r.PointB.Y, System.Globalization.CultureInfo.InvariantCulture);
  847. sPz = Convert.ToString(r.PointB.Z, System.Globalization.CultureInfo.InvariantCulture);
  848. }
  849. string sCycle = Convert.ToString(r.CycleTime, System.Globalization.CultureInfo.InvariantCulture);
  850. string sPixelX = Convert.ToString(r.PixelX, System.Globalization.CultureInfo.InvariantCulture);
  851. string sPixelY = Convert.ToString(r.PixelY, System.Globalization.CultureInfo.InvariantCulture);
  852. string sAngle = Convert.ToString(r.AngleU, System.Globalization.CultureInfo.InvariantCulture);
  853. string sTx = Convert.ToString(r.TransformX, System.Globalization.CultureInfo.InvariantCulture);
  854. string sTy = Convert.ToString(r.TransformY, System.Globalization.CultureInfo.InvariantCulture);
  855. var rowVals = new[] { sIndex, sTime, sSucc, sPx, sPy, sPz, sCycle, sPixelX, sPixelY, sAngle, sTx, sTy };
  856. foreach (var v in rowVals)
  857. {
  858. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(v ?? "", normalFont)) { Padding = 4f };
  859. table.AddCell(cell);
  860. }
  861. }
  862. doc.Add(table);
  863. // 新页,写入分析结果
  864. doc.NewPage();
  865. var analysisTitle = new iTextSharp.text.Paragraph("分析结果", titleFont) { SpacingAfter = 8f };
  866. doc.Add(analysisTitle);
  867. var dims = new[]
  868. {
  869. new { Name = "TransformX", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformX).ToArray() },
  870. new { Name = "TransformY", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformY).ToArray() },
  871. new { Name = "AngleU", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.AngleU).ToArray() }
  872. };
  873. foreach (var dim in dims)
  874. {
  875. try
  876. {
  877. var header = new iTextSharp.text.Paragraph(dim.Name, headerFont) { SpacingBefore = 6f, SpacingAfter = 4f };
  878. doc.Add(header);
  879. if (dim.Values == null || dim.Values.Length == 0)
  880. {
  881. doc.Add(new iTextSharp.text.Paragraph("样本数为 0,无法计算。", normalFont));
  882. continue;
  883. }
  884. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dim.Values);
  885. var metricsTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 60f, SpacingAfter = 6f };
  886. metricsTable.SetWidths(new float[] { 1f, 1f });
  887. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  888. foreach (var p in props)
  889. {
  890. object pv = p.GetValue(metrics);
  891. string pvStr;
  892. if (pv == null) pvStr = "";
  893. else if (pv is double) pvStr = ((double)pv).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
  894. else pvStr = pv.ToString();
  895. var pc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(p.Name, normalFont)) { Padding = 4f };
  896. var vc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(pvStr, normalFont)) { Padding = 4f };
  897. metricsTable.AddCell(pc);
  898. metricsTable.AddCell(vc);
  899. }
  900. doc.Add(metricsTable);
  901. }
  902. catch (Exception exCol)
  903. {
  904. LogHelper.WriteLogError($"导出报告时处理维度[{dim.Name}]出错", exCol);
  905. doc.Add(new iTextSharp.text.Paragraph($"处理维度 {dim.Name} 时出错: {exCol.Message}", normalFont));
  906. }
  907. }
  908. doc.Close();
  909. writer.Close();
  910. }
  911. SendTaskMessage($"已导出测试报告到:{path}");
  912. }
  913. catch (Exception ex)
  914. {
  915. LogHelper.WriteLogError("导出测试报告时出错", ex);
  916. SendTaskMessage($"导出测试报告失败:{ex.Message}");
  917. }
  918. }
  919. /// <summary>
  920. /// 导出测试数据为CSV文件
  921. /// </summary>
  922. void ExecuteExportCSVCommand()
  923. {
  924. try
  925. {
  926. if (TestResults == null || TestResults.Count == 0)
  927. {
  928. SendTaskMessage("无测试数据,无法导出。");
  929. return;
  930. }
  931. var dlg = new Microsoft.Win32.SaveFileDialog
  932. {
  933. DefaultExt = "csv",
  934. Filter = "CSV 文件 (*.csv)|*.csv|所有文件 (*.*)|*.*",
  935. FileName = "TestResults.csv",
  936. Title = "保存测试结果为 CSV"
  937. };
  938. bool? dlgResult = dlg.ShowDialog();
  939. if (dlgResult != true)
  940. return;
  941. string path = dlg.FileName;
  942. var sb = new System.Text.StringBuilder();
  943. // 辅助:CSV 字段转义
  944. Func<string, string> EscapeCsv = (s) =>
  945. {
  946. if (s == null) return "";
  947. bool mustQuote = s.Contains(",") || s.Contains("\"") || s.Contains("\r") || s.Contains("\n");
  948. string esc = s.Replace("\"", "\"\"");
  949. return mustQuote ? $"\"{esc}\"" : esc;
  950. };
  951. // include device info at top of CSV
  952. var device = _configService?.GetDeviceInfo() ?? _configService?.GetDeviceInfo();
  953. if (device != null)
  954. {
  955. sb.AppendLine("设备信息");
  956. sb.AppendLine($"线别,{EscapeCsv(device.Line)}");
  957. sb.AppendLine($"站别,{EscapeCsv(device.Station)}");
  958. sb.AppendLine($"机台号,{EscapeCsv(device.FixtureId)}");
  959. sb.AppendLine();
  960. }
  961. // 写入表头
  962. var headers = new[]
  963. {
  964. "Index",
  965. "Timestamp",
  966. "IsPhotoSuccess",
  967. "PointB.X",
  968. "PointB.Y",
  969. "PointB.Z",
  970. "CycleTime",
  971. "PixelX",
  972. "PixelY",
  973. "AngleU",
  974. "TransformX",
  975. "TransformY"
  976. };
  977. sb.AppendLine(string.Join(",", headers.Select(h => EscapeCsv(h))));
  978. // 写入数据行
  979. foreach (var r in TestResults)
  980. {
  981. var cols = new List<string>();
  982. cols.Add(EscapeCsv(r.Index.ToString()));
  983. cols.Add(EscapeCsv(r.Timestamp.ToString("o"))); // ISO 8601 格式
  984. cols.Add(EscapeCsv(r.IsPhotoSuccess ? "True" : "False"));
  985. if (r.PointB != null)
  986. {
  987. // 假设 RPoint 有 X,Y,Z 属性(若命名不同需调整)
  988. cols.Add(EscapeCsv(Convert.ToString(r.PointB.X, System.Globalization.CultureInfo.InvariantCulture)));
  989. cols.Add(EscapeCsv(Convert.ToString(r.PointB.Y, System.Globalization.CultureInfo.InvariantCulture)));
  990. cols.Add(EscapeCsv(Convert.ToString(r.PointB.Z, System.Globalization.CultureInfo.InvariantCulture)));
  991. }
  992. else
  993. {
  994. cols.Add("");
  995. cols.Add("");
  996. cols.Add("");
  997. }
  998. cols.Add(EscapeCsv(Convert.ToString(r.CycleTime, System.Globalization.CultureInfo.InvariantCulture)));
  999. cols.Add(EscapeCsv(Convert.ToString(r.PixelX, System.Globalization.CultureInfo.InvariantCulture)));
  1000. cols.Add(EscapeCsv(Convert.ToString(r.PixelY, System.Globalization.CultureInfo.InvariantCulture)));
  1001. cols.Add(EscapeCsv(Convert.ToString(r.AngleU, System.Globalization.CultureInfo.InvariantCulture)));
  1002. cols.Add(EscapeCsv(Convert.ToString(r.TransformX, System.Globalization.CultureInfo.InvariantCulture)));
  1003. cols.Add(EscapeCsv(Convert.ToString(r.TransformY, System.Globalization.CultureInfo.InvariantCulture)));
  1004. sb.AppendLine(string.Join(",", cols));
  1005. }
  1006. // 在末尾追加分析结果
  1007. sb.AppendLine();
  1008. sb.AppendLine("分析结果");
  1009. sb.AppendLine("维度,指标,值");
  1010. // 要分析的维度:TransformX, TransformY, AngleU
  1011. var dims = new[]
  1012. {
  1013. new { Name = "TransformX", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformX).ToArray() },
  1014. new { Name = "TransformY", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformY).ToArray() },
  1015. new { Name = "AngleU", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.AngleU).ToArray() }
  1016. };
  1017. foreach (var dim in dims)
  1018. {
  1019. try
  1020. {
  1021. if (dim.Values == null || dim.Values.Length == 0)
  1022. {
  1023. sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv("样本数")},{EscapeCsv("0")}");
  1024. continue;
  1025. }
  1026. // 调用稳定性分析
  1027. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dim.Values);
  1028. // 反射写入公开属性
  1029. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  1030. foreach (var p in props)
  1031. {
  1032. object pv = p.GetValue(metrics);
  1033. string pvStr;
  1034. if (pv == null) pvStr = "";
  1035. else if (pv is double) pvStr = Convert.ToString((double)pv, System.Globalization.CultureInfo.InvariantCulture);
  1036. else pvStr = pv.ToString();
  1037. sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv(p.Name)},{EscapeCsv(pvStr)}");
  1038. }
  1039. }
  1040. catch (Exception exCol)
  1041. {
  1042. LogHelper.WriteLogError($"导出分析结果时处理维度[{dim.Name}]出错", exCol);
  1043. sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv("Error")},{EscapeCsv(exCol.Message)}");
  1044. }
  1045. }
  1046. // 写入文件(UTF8,无 BOM)
  1047. System.IO.File.WriteAllText(path, sb.ToString(), System.Text.Encoding.UTF8);
  1048. SendTaskMessage($"已导出测试结果到:{path}");
  1049. }
  1050. catch (Exception ex)
  1051. {
  1052. LogHelper.WriteLogError("导出CSV时出错", ex);
  1053. SendTaskMessage($"导出CSV失败:{ex.Message}");
  1054. }
  1055. }
  1056. /// <summary>
  1057. /// 选中视觉流程改变时
  1058. /// </summary>
  1059. void ExecuteProcedureSelectionChangedCommand()
  1060. {
  1061. if (SelectProcedure == null)
  1062. {
  1063. return;
  1064. }
  1065. //获取相机
  1066. Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
  1067. VisionTool = SelectProcedure.ToolBlock;
  1068. }
  1069. /// <summary>
  1070. /// 示教点B
  1071. /// </summary>
  1072. void ExecuteTeachPointBCommand()
  1073. {
  1074. if (Robot == null || !Robot.IsConnected)
  1075. {
  1076. SendTaskMessage("机器人未连接,无法示教!");
  1077. return;
  1078. }
  1079. try
  1080. {
  1081. var position = Robot.GetRobotPos();
  1082. if (position != null)
  1083. {
  1084. PointB = position.Clone();
  1085. SendTaskMessage($"已示教点B:X={PointB.X:F2} Y={PointB.Y:F2} Z={PointB.Z:F2}");
  1086. }
  1087. else
  1088. {
  1089. SendTaskMessage("获取机器人当前位置失败,无法示教点B!");
  1090. }
  1091. }
  1092. catch (Exception ex)
  1093. {
  1094. LogHelper.WriteLogError("示教点B时出错!", ex);
  1095. SendTaskMessage($"示教点B时出错:{ex.Message}");
  1096. }
  1097. }
  1098. /// <summary>
  1099. /// 示教点A
  1100. /// </summary>
  1101. void ExecuteTeachPointACommand()
  1102. {
  1103. if (Robot == null || !Robot.IsConnected)
  1104. {
  1105. SendTaskMessage("机器人未连接,无法示教!");
  1106. return;
  1107. }
  1108. try
  1109. {
  1110. var position = Robot.GetRobotPos();
  1111. if (position != null)
  1112. {
  1113. PointA = position.Clone();
  1114. SendTaskMessage($"已示教点A:X={PointA.X:F2} Y={PointA.Y:F2} Z={PointA.Z:F2}");
  1115. }
  1116. else
  1117. {
  1118. SendTaskMessage("获取机器人当前位置失败,无法示教点A!");
  1119. }
  1120. }
  1121. catch (Exception ex)
  1122. {
  1123. LogHelper.WriteLogError("示教点A时出错!", ex);
  1124. SendTaskMessage($"示教点A时出错:{ex.Message}");
  1125. }
  1126. }
  1127. /// <summary>
  1128. /// 显示趋势图
  1129. /// </summary>
  1130. void ExecuteShowTrendChartCommand()
  1131. {
  1132. }
  1133. /// <summary>
  1134. /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
  1135. /// </summary>
  1136. /// <param name="index"></param>
  1137. /// <returns></returns>
  1138. public Task<(bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY)> ExecutePhotoEx(int index)
  1139. {
  1140. return Task.Run(() =>
  1141. {
  1142. CogToolBlockTerminalCollection outputCollection;
  1143. List<object> result = new List<object>();
  1144. result.Add(index);
  1145. try
  1146. {
  1147. // 1. 采集图像
  1148. bool succed = Camera.SetExposureTime(SelectProcedure.ExposureTime);
  1149. if (!succed)
  1150. {
  1151. SendTaskMessage("相机曝光设置失败");
  1152. }
  1153. succed = Camera.SetGain(SelectProcedure.Gain);
  1154. if (!succed)
  1155. {
  1156. SendTaskMessage("相机增益设置失败");
  1157. }
  1158. DateTime nowtime = DateTime.Now;
  1159. LogHelper.WriteLogInfo("开始采集图像");
  1160. var image = Camera.Grab();
  1161. if (image == null)
  1162. {
  1163. SendTaskMessage(Lang.图像采集失败);
  1164. outputCollection = null;
  1165. return (false, 0d, 0d, 0d, 0d, 0d);
  1166. }
  1167. //Image = image;
  1168. VisionTool.Inputs["InputImage"].Value = image;
  1169. //LogHelper.WriteLogInfo(string.Format(Lang.采集图像完成用时0ms, (DateTime.Now - nowtime).Milliseconds));
  1170. // 2. 为ToolBlock传入其他输入终端
  1171. //if (InputTerminal != null)
  1172. //{
  1173. // LogHelper.WriteLogInfo(Lang.为ToolBlock传入输入终端);
  1174. // foreach (var item in InputTerminal)
  1175. // {
  1176. // if (VisionTool.Inputs.Contains(item.Key))
  1177. // {
  1178. // LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
  1179. // VisionTool.Inputs[item.Key].Value = item.Value;
  1180. // }
  1181. // else
  1182. // {
  1183. // LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
  1184. // VisionTool.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
  1185. // }
  1186. // }
  1187. //}
  1188. Image = VisionTool.Inputs["InputImage"].Value as ICogImage;
  1189. // 3. 运行视觉工具
  1190. LogHelper.WriteLogInfo("开始运行视觉工具");
  1191. VisionTool.Run(); //运行ToolBlock
  1192. if (VisionTool.RunStatus.Result == CogToolResultConstants.Accept)
  1193. {
  1194. SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  1195. outputCollection = VisionTool.Outputs; //获取输出终端集合
  1196. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  1197. if (!outputCollection.Contains("Found"))
  1198. {
  1199. SendTaskMessage(Lang.未找到视觉输出结果);
  1200. return (false, 0d, 0d, 0d, 0d, 0d);
  1201. }
  1202. // 2. 获取视觉输出结果
  1203. bool Found = (bool)(outputCollection["Found"].Value);
  1204. if (!Found)
  1205. {
  1206. SendTaskMessage(Lang.拍照NG);
  1207. return (false, 0d, 0d, 0d, 0d, 0d);
  1208. }
  1209. //SendTaskMessage(Lang.拍照OK, MessageLevel.Debug);
  1210. // 3. 获取视觉输出结果
  1211. string points = (string)(outputCollection["Point"].Value);
  1212. string[] strpoints = points.Split(';');
  1213. //先将pos按照逗号进行分隔,拿到数组
  1214. var posParts = strpoints[0].Split(',');
  1215. double _pixel_x = double.Parse(posParts[0]);
  1216. double _pixel_y = double.Parse(posParts[1]);
  1217. double _pixel_u = double.Parse(posParts[2]);
  1218. //获取相机校准
  1219. var calib = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  1220. if (calib == null)
  1221. {
  1222. return (true, _pixel_x, _pixel_y, _pixel_u, 0, 0); ;
  1223. }
  1224. //获取机器人当前坐标系
  1225. RPoint rPoint = Robot.GetRobotPos();
  1226. double[] point = new double[3];
  1227. point[0] = rPoint.X;
  1228. point[1] = rPoint.Y;
  1229. point[2] = rPoint.U;
  1230. if (calib.CameraMount == CameraMount.FixedUp || calib.CameraMount == CameraMount.FixedDown)
  1231. {
  1232. point = null;
  1233. }
  1234. //转换点位-像素转换成机器人绝对坐标
  1235. var calibResult = _calibrationService.ConvertPixelToPosition((_pixel_x, _pixel_y, _pixel_u), point, calib, RobotBrand.XYZ_Platform);
  1236. if (!calibResult.IsSucceed)
  1237. {
  1238. return (false, _pixel_x, _pixel_y, _pixel_u, 0d, 0d);
  1239. }
  1240. return (true, _pixel_x, _pixel_y, _pixel_u, calibResult.X, calibResult.Y); ;
  1241. }
  1242. else
  1243. {
  1244. SendTaskMessage(Lang.视觉流程执行出错耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  1245. SendTaskMessage(VisionTool.RunStatus.Message);
  1246. outputCollection = null;
  1247. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  1248. return (false, 0d, 0d, 0d, 0d, 0d);
  1249. }
  1250. }
  1251. catch (Exception ex)
  1252. {
  1253. SendTaskMessage(ex.Message);
  1254. LogHelper.WriteLogError("执行相机取图并执行视觉工具组时出错", ex);
  1255. outputCollection = null;
  1256. return (false, 0d, 0d, 0d, 0d, 0d);
  1257. }
  1258. });
  1259. }
  1260. public void SendTaskMessage(string msg)
  1261. {
  1262. App.Current.Dispatcher.Invoke(() =>
  1263. {
  1264. MessageQueue.Clear();
  1265. MessageQueue.Enqueue(msg);
  1266. });
  1267. }
  1268. // 规格限制(根据实际情况设置)
  1269. private double _xLSL = -0.02; // X下规格限
  1270. private double _xUSL = 0.02; // X上规格限
  1271. private double _yLSL = -0.02; // Y下规格限
  1272. private double _yUSL = 0.02; // Y上规格限
  1273. private double _angleLSL = -1.0; // 角度下规格限
  1274. private double _angleUSL = 1.0; // 角度上规格限
  1275. /// <summary>
  1276. /// 计算所有精度指标
  1277. /// </summary>
  1278. public void CalculatePrecisionMetrics()
  1279. {
  1280. if (TestResults == null || TestResults.Count < 2)
  1281. return;
  1282. // 过滤成功的测试结果
  1283. var successResults = TestResults.Where(r => r.IsPhotoSuccess).ToList();
  1284. if (successResults.Count < 2)
  1285. return;
  1286. // 提取数据
  1287. var transformXs = successResults.Select(r => r.TransformX).ToList();
  1288. var transformYs = successResults.Select(r => r.TransformY).ToList();
  1289. var angleUs = successResults.Select(r => r.AngleU).ToList();
  1290. // 计算X方向指标
  1291. var xMetrics = CalculateMetrics(transformXs, _xLSL, _xUSL);
  1292. PrecisionResult.XMean = xMetrics.Mean;
  1293. PrecisionResult.XPrecision = xMetrics.StdDeviation;
  1294. PrecisionResult.XThreeSigmaRange = xMetrics.ThreeSigmaRange;
  1295. PrecisionResult.XRange = xMetrics.Range;
  1296. PrecisionResult.XKurtosis = xMetrics.Kurtosis;
  1297. PrecisionResult.XCpk = xMetrics.Cpk;
  1298. // 计算Y方向指标
  1299. var yMetrics = CalculateMetrics(transformYs, _yLSL, _yUSL);
  1300. PrecisionResult.YMean = yMetrics.Mean;
  1301. PrecisionResult.YPrecision = yMetrics.StdDeviation;
  1302. PrecisionResult.YThreeSigmaRange = yMetrics.ThreeSigmaRange;
  1303. PrecisionResult.YRange = yMetrics.Range;
  1304. PrecisionResult.YKurtosis = yMetrics.Kurtosis;
  1305. PrecisionResult.YCpk = yMetrics.Cpk;
  1306. // 计算角度指标
  1307. var angleMetrics = CalculateMetrics(angleUs, _angleLSL, _angleUSL);
  1308. PrecisionResult.AngleMean = angleMetrics.Mean;
  1309. PrecisionResult.AnglePrecision = angleMetrics.StdDeviation;
  1310. PrecisionResult.AngleThreeSigmaRange = angleMetrics.ThreeSigmaRange;
  1311. PrecisionResult.AngleRange = angleMetrics.Range;
  1312. PrecisionResult.AngleCpk = angleMetrics.Cpk;
  1313. // 计算综合精度
  1314. PrecisionResult.OverallPrecision = Math.Sqrt(
  1315. Math.Pow(PrecisionResult.XPrecision, 2) +
  1316. Math.Pow(PrecisionResult.YPrecision, 2));
  1317. PrecisionResult.OverallThreeSigmaRange = PrecisionResult.OverallPrecision * 3;
  1318. // 使用最差的CPK作为整体评级
  1319. var minCpk = Math.Min(Math.Min(PrecisionResult.XCpk, PrecisionResult.YCpk), PrecisionResult.AngleCpk);
  1320. PrecisionResult.ProcessRating = PrecisionResult.GetCpkRating(minCpk);
  1321. }
  1322. /// <summary>
  1323. /// 计算单个维度的所有指标
  1324. /// </summary>
  1325. private DimensionMetrics CalculateMetrics(List<double> values, double lsl, double usl)
  1326. {
  1327. if (values == null || values.Count < 2)
  1328. return new DimensionMetrics();
  1329. var metrics = new DimensionMetrics
  1330. {
  1331. Mean = values.Average(),
  1332. StdDeviation = CalculateStandardDeviation(values),
  1333. Range = values.Max() - values.Min(),
  1334. Kurtosis = CalculateKurtosis(values)
  1335. };
  1336. metrics.ThreeSigmaRange = metrics.StdDeviation * 3;
  1337. metrics.Cpk = CalculateCpk(values, lsl, usl);
  1338. return metrics;
  1339. }
  1340. /// <summary>
  1341. /// 计算标准差
  1342. /// </summary>
  1343. private double CalculateStandardDeviation(List<double> values)
  1344. {
  1345. if (values.Count < 2)
  1346. return 0;
  1347. var mean = values.Average();
  1348. var sumSq = values.Sum(v => Math.Pow(v - mean, 2));
  1349. return Math.Sqrt(sumSq / (values.Count - 1));
  1350. }
  1351. /// <summary>
  1352. /// 计算峰度
  1353. /// </summary>
  1354. private double CalculateKurtosis(List<double> values)
  1355. {
  1356. if (values.Count < 4)
  1357. return 0;
  1358. var mean = values.Average();
  1359. var n = values.Count;
  1360. var sum4 = values.Sum(v => Math.Pow(v - mean, 4));
  1361. var sum2 = values.Sum(v => Math.Pow(v - mean, 2));
  1362. if (sum2 == 0)
  1363. return 0;
  1364. return (n * sum4) / Math.Pow(sum2, 2) - 3;
  1365. }
  1366. /// <summary>
  1367. /// 计算CPK
  1368. /// </summary>
  1369. private double CalculateCpk(List<double> values, double lsl, double usl)
  1370. {
  1371. if (values.Count < 2 || usl <= lsl)
  1372. return 0;
  1373. var stdDev = CalculateStandardDeviation(values);
  1374. if (stdDev == 0)
  1375. return double.MaxValue;
  1376. var mean = values.Average();
  1377. var cpu = (usl - mean) / (3 * stdDev);
  1378. var cpl = (mean - lsl) / (3 * stdDev);
  1379. return Math.Min(cpu, cpl);
  1380. }
  1381. /// <summary>
  1382. /// 单个维度的指标
  1383. /// </summary>
  1384. private class DimensionMetrics
  1385. {
  1386. public double Mean { get; set; }
  1387. public double StdDeviation { get; set; }
  1388. public double ThreeSigmaRange { get; set; }
  1389. public double Range { get; set; }
  1390. public double Kurtosis { get; set; }
  1391. public double Cpk { get; set; }
  1392. }
  1393. /// <summary>
  1394. /// 在添加测试结果后自动更新精度分析
  1395. /// </summary>
  1396. private void OnTestResultAdded()
  1397. {
  1398. CalculatePrecisionMetrics();
  1399. RaisePropertyChanged(nameof(PrecisionResult));
  1400. }
  1401. /// <summary>
  1402. /// 设置规格限制
  1403. /// </summary>
  1404. public void SetSpecificationLimits(double xLSL, double xUSL, double yLSL, double yUSL, double angleLSL, double angleUSL)
  1405. {
  1406. _xLSL = xLSL;
  1407. _xUSL = xUSL;
  1408. _yLSL = yLSL;
  1409. _yUSL = yUSL;
  1410. _angleLSL = angleLSL;
  1411. _angleUSL = angleUSL;
  1412. // 重新计算精度指标
  1413. CalculatePrecisionMetrics();
  1414. }
  1415. /// <summary>
  1416. /// 基于3σ自动设置规格限制
  1417. /// </summary>
  1418. public void AutoSetSpecificationLimits()
  1419. {
  1420. if (TestResults == null || TestResults.Count < 2)
  1421. return;
  1422. var successResults = TestResults.Where(r => r.IsPhotoSuccess).ToList();
  1423. if (successResults.Count < 2)
  1424. return;
  1425. var transformXs = successResults.Select(r => r.TransformX).ToList();
  1426. var transformYs = successResults.Select(r => r.TransformY).ToList();
  1427. var angleUs = successResults.Select(r => r.AngleU).ToList();
  1428. var xStd = CalculateStandardDeviation(transformXs);
  1429. var yStd = CalculateStandardDeviation(transformYs);
  1430. var angleStd = CalculateStandardDeviation(angleUs);
  1431. var xMean = transformXs.Average();
  1432. var yMean = transformYs.Average();
  1433. var angleMean = angleUs.Average();
  1434. // 使用平均值±3σ作为自动规格限
  1435. SetSpecificationLimits(
  1436. xLSL: xMean - 3 * xStd,
  1437. xUSL: xMean + 3 * xStd,
  1438. yLSL: yMean - 3 * yStd,
  1439. yUSL: yMean + 3 * yStd,
  1440. angleLSL: angleMean - 3 * angleStd,
  1441. angleUSL: angleMean + 3 * angleStd
  1442. );
  1443. }
  1444. #endregion
  1445. #region 继承
  1446. public string Title { get; set; } = "视觉静态精度分析仪";
  1447. public event Action<IDialogResult> RequestClose;
  1448. public bool CanCloseDialog()
  1449. {
  1450. return true;
  1451. }
  1452. public void OnDialogClosed()
  1453. {
  1454. _cts?.Cancel();
  1455. SaveConfig();
  1456. }
  1457. public void OnDialogOpened(IDialogParameters parameters)
  1458. {
  1459. SelectProduct = parameters.GetValue<ProductModel>("SelectProduct");
  1460. Robot = parameters.GetValue<IRobot>("Robot");
  1461. ProcedureModels = new ObservableCollection<ProcedureModel>();
  1462. foreach (var item in SelectProduct.CameraProcedures)
  1463. {
  1464. foreach (var item1 in item.ProcedureModels)
  1465. {
  1466. ProcedureModels.Add(item1);
  1467. }
  1468. }
  1469. LoadConfig();
  1470. }
  1471. #endregion
  1472. /// <summary>
  1473. /// 加载配置
  1474. /// </summary>
  1475. private void LoadConfig()
  1476. {
  1477. //配置文件路径:FilePath.ProductsPath//productName//VisionDynamicAccuracyAnalyzerConfig.json
  1478. string configPath = Path.Combine(FilePath.ProductsPath, SelectProduct.Name, "VisionDynamicAccuracyAnalyzerConfig.json");
  1479. if (File.Exists(configPath))
  1480. {
  1481. try
  1482. {
  1483. string json = File.ReadAllText(configPath);
  1484. var ConfigModel = JsonConvert.DeserializeObject<VisionDynamicAccuracyAnalyzerConfigModel>(json);
  1485. //加载点位界面参数
  1486. this.PointA = ConfigModel.PointA;
  1487. this.PointB = ConfigModel.PointB;
  1488. this.MoveSpeed = ConfigModel.RobotMoveSpeed;
  1489. this.RepeatCount = ConfigModel.TestCount;
  1490. this.ADelay = ConfigModel.ADelay;
  1491. this.BDelay = ConfigModel.BDelay;
  1492. this.IsCylCtrl = ConfigModel.IsCylCtrl;
  1493. this.IsTakePhoto = ConfigModel.IsTakePhoto;
  1494. this.IsVertical = ConfigModel.IsVertical;
  1495. this.Distance = ConfigModel.Distance;
  1496. //加载选中视觉流程
  1497. var procedure = ProcedureModels.FirstOrDefault(p => p.Id == ConfigModel.SelectProcedureId);
  1498. if (procedure != null)
  1499. {
  1500. this.SelectProcedure = procedure;
  1501. this.Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
  1502. }
  1503. }
  1504. catch (Exception ex)
  1505. {
  1506. LogHelper.WriteLogError("加载视觉动态精度分析仪配置时出错", ex);
  1507. }
  1508. }
  1509. }
  1510. /// <summary>
  1511. /// 保存配置
  1512. /// </summary>
  1513. private void SaveConfig()
  1514. {
  1515. try
  1516. {
  1517. //配置文件路径:FilePath.ProductsPath//productName//VisionDynamicAccuracyAnalyzerConfig.json
  1518. string configPath = Path.Combine(FilePath.ProductsPath, SelectProduct.Name, "VisionDynamicAccuracyAnalyzerConfig.json");
  1519. var ConfigModel = new VisionDynamicAccuracyAnalyzerConfigModel
  1520. {
  1521. PointA = this.PointA,
  1522. PointB = this.PointB,
  1523. SelectProcedureId = this.SelectProcedure?.Id ?? Guid.Empty,
  1524. SelectProcedureName = this.SelectProcedure?.Name ?? "",
  1525. RobotMoveSpeed = this.MoveSpeed,
  1526. TestCount = this.RepeatCount,
  1527. ADelay = this.ADelay,
  1528. BDelay = this.BDelay,
  1529. IsCylCtrl = this.IsCylCtrl,
  1530. IsTakePhoto = this.IsTakePhoto,
  1531. IsVertical = this.IsVertical,
  1532. Distance = this.Distance,
  1533. };
  1534. //如果配置文件夹不存在则创建
  1535. var configDir = Path.GetDirectoryName(configPath);
  1536. if (!Directory.Exists(configDir))
  1537. {
  1538. Directory.CreateDirectory(configDir);
  1539. }
  1540. string json = JsonConvert.SerializeObject(ConfigModel, Formatting.Indented);
  1541. File.WriteAllText(configPath, json);
  1542. }
  1543. catch (Exception ex)
  1544. {
  1545. LogHelper.WriteLogError("保存视觉动态精度分析仪配置时出错", ex);
  1546. }
  1547. }
  1548. //配置参数模型
  1549. public class VisionDynamicAccuracyAnalyzerConfigModel : BindableBase
  1550. {
  1551. private int _RobotMoveSpeed = 100;
  1552. /// <summary>
  1553. /// 机器人移动速度
  1554. /// </summary>
  1555. public int RobotMoveSpeed
  1556. {
  1557. get { return _RobotMoveSpeed; }
  1558. set { SetProperty(ref _RobotMoveSpeed, value); }
  1559. }
  1560. private int _TestCount = 10;
  1561. /// <summary>
  1562. /// 测试次数
  1563. /// </summary>
  1564. public int TestCount
  1565. {
  1566. get { return _TestCount; }
  1567. set { SetProperty(ref _TestCount, value); }
  1568. }
  1569. //点位A
  1570. private RPoint _PointA;
  1571. /// <summary>
  1572. /// 点位A
  1573. /// </summary>
  1574. public RPoint PointA
  1575. {
  1576. get { return _PointA; }
  1577. set { SetProperty(ref _PointA, value); }
  1578. }
  1579. //点位B
  1580. private RPoint _PointB;
  1581. /// <summary>
  1582. /// 点位B
  1583. /// </summary>
  1584. public RPoint PointB
  1585. {
  1586. get { return _PointB; }
  1587. set { SetProperty(ref _PointB, value); }
  1588. }
  1589. private Guid _SelectProcedureId;
  1590. /// <summary>
  1591. /// 选中的视觉流程ID
  1592. /// </summary>
  1593. public Guid SelectProcedureId
  1594. {
  1595. get { return _SelectProcedureId; }
  1596. set { SetProperty(ref _SelectProcedureId, value); }
  1597. }
  1598. private string _SelectProcedureName;
  1599. /// <summary>
  1600. /// 选中的视觉流程Name
  1601. /// </summary>
  1602. public string SelectProcedureName
  1603. {
  1604. get { return _SelectProcedureName; }
  1605. set { SetProperty(ref _SelectProcedureName, value); }
  1606. }
  1607. private int _ADelay;
  1608. public int ADelay
  1609. {
  1610. get { return _ADelay; }
  1611. set { _ADelay = value; }
  1612. }
  1613. private int _BDelay;
  1614. public int BDelay
  1615. {
  1616. get { return _BDelay; }
  1617. set { _BDelay = value; }
  1618. }
  1619. private bool _IsCylCtrl;
  1620. public bool IsCylCtrl
  1621. {
  1622. get { return _IsCylCtrl; }
  1623. set { _IsCylCtrl = value; }
  1624. }
  1625. private bool _IsTakePhoto;
  1626. public bool IsTakePhoto
  1627. {
  1628. get { return _IsTakePhoto; }
  1629. set { _IsTakePhoto = value; }
  1630. }
  1631. private bool _IsVertical = true;
  1632. /// <summary>
  1633. /// 是否启用垂直精度检测
  1634. /// </summary>
  1635. public bool IsVertical
  1636. {
  1637. get { return _IsVertical; }
  1638. set { SetProperty(ref _IsVertical, value); }
  1639. }
  1640. private float _Distance;
  1641. public float Distance
  1642. {
  1643. get { return _Distance; }
  1644. set { SetProperty(ref _Distance, value); }
  1645. }
  1646. }
  1647. }
  1648. public class DynamicTestResult : BindableBase
  1649. {
  1650. private int _Index;
  1651. /// <summary>
  1652. /// 序号
  1653. /// </summary>
  1654. public int Index
  1655. {
  1656. get { return _Index; }
  1657. set { SetProperty(ref _Index, value); }
  1658. }
  1659. private DateTime _Timestamp;
  1660. /// <summary>
  1661. /// 时间戳
  1662. /// </summary>
  1663. public DateTime Timestamp
  1664. {
  1665. get { return _Timestamp; }
  1666. set { SetProperty(ref _Timestamp, value); }
  1667. }
  1668. private bool _isPhotoSuccess;
  1669. /// <summary>
  1670. /// 拍照结果
  1671. /// </summary>
  1672. public bool IsPhotoSuccess
  1673. {
  1674. get { return _isPhotoSuccess; }
  1675. set { SetProperty(ref _isPhotoSuccess, value); }
  1676. }
  1677. private RPoint _PointB;
  1678. /// <summary>
  1679. /// 机器人拍照的坐标
  1680. /// </summary>
  1681. public RPoint PointB
  1682. {
  1683. get { return _PointB; }
  1684. set { SetProperty(ref _PointB, value); }
  1685. }
  1686. private double _CycleTime;
  1687. /// <summary>
  1688. /// 单次节拍
  1689. /// </summary>
  1690. public double CycleTime
  1691. {
  1692. get { return _CycleTime; }
  1693. set { SetProperty(ref _CycleTime, value); }
  1694. }
  1695. private double _PixelX;
  1696. /// <summary>
  1697. /// 像素X
  1698. /// </summary>
  1699. public double PixelX
  1700. {
  1701. get { return _PixelX; }
  1702. set { SetProperty(ref _PixelX, value); }
  1703. }
  1704. private double _PixelY;
  1705. /// <summary>
  1706. /// 像素Y
  1707. /// </summary>
  1708. public double PixelY
  1709. {
  1710. get { return _PixelY; }
  1711. set { SetProperty(ref _PixelY, value); }
  1712. }
  1713. private double _AngleU;
  1714. /// <summary>
  1715. /// 像素U
  1716. /// </summary>
  1717. public double AngleU
  1718. {
  1719. get { return _AngleU; }
  1720. set { SetProperty(ref _AngleU, value); }
  1721. }
  1722. private double _TransformX;
  1723. /// <summary>
  1724. /// 转换后的机器人坐标X
  1725. /// </summary>
  1726. public double TransformX
  1727. {
  1728. get { return _TransformX; }
  1729. set { SetProperty(ref _TransformX, value); }
  1730. }
  1731. private double _TransformY;
  1732. /// <summary>
  1733. /// 转换后的机器人坐标Y
  1734. /// </summary>
  1735. public double TransformY
  1736. {
  1737. get { return _TransformY; }
  1738. set { SetProperty(ref _TransformY, value); }
  1739. }
  1740. public DynamicTestResult()
  1741. { }
  1742. public DynamicTestResult(int index, bool isPhotoSuccess, RPoint pointB, double cycleTime, double pixelX, double pixelY, double angleU, double transformX, double transformY)
  1743. {
  1744. Index = index;
  1745. Timestamp = DateTime.Now;
  1746. IsPhotoSuccess = isPhotoSuccess;
  1747. PointB = pointB;
  1748. CycleTime = cycleTime;
  1749. PixelX = pixelX;
  1750. PixelY = pixelY;
  1751. AngleU = angleU;
  1752. TransformX = transformX;
  1753. TransformY = transformY;
  1754. }
  1755. }
  1756. /// <summary>
  1757. /// 精度分析结果
  1758. /// </summary>
  1759. public class PrecisionAnalysisResult : BindableBase
  1760. {
  1761. private double _xPrecision;
  1762. /// <summary>
  1763. /// X方向精度(标准差)
  1764. /// </summary>
  1765. public double XPrecision
  1766. {
  1767. get { return _xPrecision; }
  1768. set { SetProperty(ref _xPrecision, value); }
  1769. }
  1770. private double _yPrecision;
  1771. /// <summary>
  1772. /// Y方向精度(标准差)
  1773. /// </summary>
  1774. public double YPrecision
  1775. {
  1776. get { return _yPrecision; }
  1777. set { SetProperty(ref _yPrecision, value); }
  1778. }
  1779. private double _anglePrecision;
  1780. /// <summary>
  1781. /// 角度精度(标准差)
  1782. /// </summary>
  1783. public double AnglePrecision
  1784. {
  1785. get { return _anglePrecision; }
  1786. set { SetProperty(ref _anglePrecision, value); }
  1787. }
  1788. private double _xThreeSigmaRange;
  1789. /// <summary>
  1790. /// X方向3σ范围
  1791. /// </summary>
  1792. public double XThreeSigmaRange
  1793. {
  1794. get { return _xThreeSigmaRange; }
  1795. set { SetProperty(ref _xThreeSigmaRange, value); }
  1796. }
  1797. private double _yThreeSigmaRange;
  1798. /// <summary>
  1799. /// Y方向3σ范围
  1800. /// </summary>
  1801. public double YThreeSigmaRange
  1802. {
  1803. get { return _yThreeSigmaRange; }
  1804. set { SetProperty(ref _yThreeSigmaRange, value); }
  1805. }
  1806. private double _angleThreeSigmaRange;
  1807. /// <summary>
  1808. /// 角度3σ范围
  1809. /// </summary>
  1810. public double AngleThreeSigmaRange
  1811. {
  1812. get { return _angleThreeSigmaRange; }
  1813. set { SetProperty(ref _angleThreeSigmaRange, value); }
  1814. }
  1815. private double _xMean;
  1816. /// <summary>
  1817. /// X方向平均值
  1818. /// </summary>
  1819. public double XMean
  1820. {
  1821. get { return _xMean; }
  1822. set { SetProperty(ref _xMean, value); }
  1823. }
  1824. private double _yMean;
  1825. /// <summary>
  1826. /// Y方向平均值
  1827. /// </summary>
  1828. public double YMean
  1829. {
  1830. get { return _yMean; }
  1831. set { SetProperty(ref _yMean, value); }
  1832. }
  1833. private double _angleMean;
  1834. /// <summary>
  1835. /// 角度平均值
  1836. /// </summary>
  1837. public double AngleMean
  1838. {
  1839. get { return _angleMean; }
  1840. set { SetProperty(ref _angleMean, value); }
  1841. }
  1842. private double _xRange;
  1843. /// <summary>
  1844. /// X方向极差
  1845. /// </summary>
  1846. public double XRange
  1847. {
  1848. get { return _xRange; }
  1849. set { SetProperty(ref _xRange, value); }
  1850. }
  1851. private double _yRange;
  1852. /// <summary>
  1853. /// Y方向极差
  1854. /// </summary>
  1855. public double YRange
  1856. {
  1857. get { return _yRange; }
  1858. set { SetProperty(ref _yRange, value); }
  1859. }
  1860. private double _angleRange;
  1861. /// <summary>
  1862. /// 角度极差
  1863. /// </summary>
  1864. public double AngleRange
  1865. {
  1866. get { return _angleRange; }
  1867. set { SetProperty(ref _angleRange, value); }
  1868. }
  1869. private double _xKurtosis;
  1870. /// <summary>
  1871. /// X方向峰度
  1872. /// </summary>
  1873. public double XKurtosis
  1874. {
  1875. get { return _xKurtosis; }
  1876. set { SetProperty(ref _xKurtosis, value); }
  1877. }
  1878. private double _yKurtosis;
  1879. /// <summary>
  1880. /// Y方向峰度
  1881. /// </summary>
  1882. public double YKurtosis
  1883. {
  1884. get { return _yKurtosis; }
  1885. set { SetProperty(ref _yKurtosis, value); }
  1886. }
  1887. private double _xCpk;
  1888. /// <summary>
  1889. /// X方向CPK
  1890. /// </summary>
  1891. public double XCpk
  1892. {
  1893. get { return _xCpk; }
  1894. set { SetProperty(ref _xCpk, value); }
  1895. }
  1896. private double _yCpk;
  1897. /// <summary>
  1898. /// Y方向CPK
  1899. /// </summary>
  1900. public double YCpk
  1901. {
  1902. get { return _yCpk; }
  1903. set { SetProperty(ref _yCpk, value); }
  1904. }
  1905. private double _angleCpk;
  1906. /// <summary>
  1907. /// 角度CPK
  1908. /// </summary>
  1909. public double AngleCpk
  1910. {
  1911. get { return _angleCpk; }
  1912. set { SetProperty(ref _angleCpk, value); }
  1913. }
  1914. private double _overallPrecision;
  1915. /// <summary>
  1916. /// 综合精度(XY合成标准差)
  1917. /// </summary>
  1918. public double OverallPrecision
  1919. {
  1920. get { return _overallPrecision; }
  1921. set { SetProperty(ref _overallPrecision, value); }
  1922. }
  1923. private double _overallThreeSigmaRange;
  1924. /// <summary>
  1925. /// 综合3σ范围
  1926. /// </summary>
  1927. public double OverallThreeSigmaRange
  1928. {
  1929. get { return _overallThreeSigmaRange; }
  1930. set { SetProperty(ref _overallThreeSigmaRange, value); }
  1931. }
  1932. private string _processRating;
  1933. /// <summary>
  1934. /// 过程能力评级
  1935. /// </summary>
  1936. public string ProcessRating
  1937. {
  1938. get { return _processRating; }
  1939. set { SetProperty(ref _processRating, value); }
  1940. }
  1941. /// <summary>
  1942. /// 获取CPK评级描述
  1943. /// </summary>
  1944. public string GetCpkRating(double cpk)
  1945. {
  1946. if (cpk >= 1.67)
  1947. return "卓越";
  1948. else if (cpk >= 1.33)
  1949. return "良好";
  1950. else if (cpk >= 1.00)
  1951. return "可接受";
  1952. else if (cpk >= 0.67)
  1953. return "不足";
  1954. else
  1955. return "严重不足";
  1956. }
  1957. }
  1958. }