VisionStaticAccuracyAnalyzerViewModel.cs 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ToolBlock;
  3. using MaterialDesignThemes.Wpf;
  4. using Prism.Commands;
  5. using Prism.Events;
  6. using Prism.Ioc;
  7. using Prism.Mvvm;
  8. using Prism.Regions;
  9. using Prism.Services.Dialogs;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Collections.ObjectModel;
  13. using System.Data;
  14. using System.IO;
  15. using System.Linq;
  16. using System.Text;
  17. using System.Threading;
  18. using System.Threading.Tasks;
  19. using Team.FFFeederService.Interfaces;
  20. using TeamAAS_VP.Core;
  21. using TeamAAS_VP.Interfaces;
  22. using TeamAAS_VP.Models;
  23. using TeamAAS_VP.Resources.Languages;
  24. using static TeamAAS_VP.Core.StabilityAnalyzer;
  25. namespace TeamAAS_VP.ViewModels.Product
  26. {
  27. public class VisionStaticAccuracyAnalyzerViewModel : BindableBase, IDialogAware
  28. {
  29. IRegionManager _regionManager;
  30. IEventAggregator _eventAggregator;
  31. IContainerProvider _container;
  32. IDialogService _dialogService;
  33. IFeederService _feederService;
  34. IRobotService _robotService;
  35. ICameraService _cameraService;
  36. ISystemDatabaseService _systemDatabaseService;
  37. ICalibrationService _calibrationService;
  38. //测试过程中控制取消的CTS
  39. CancellationTokenSource _cts;
  40. #region 属性
  41. private ICogImage _Image;
  42. public ICogImage Image
  43. {
  44. get { return _Image; }
  45. set { SetProperty(ref _Image, value); }
  46. }
  47. private Cognex.VisionPro.CogGraphicCollection _Graphic;
  48. public Cognex.VisionPro.CogGraphicCollection Graphic
  49. {
  50. get { return _Graphic; }
  51. set { SetProperty(ref _Graphic, value); }
  52. }
  53. private ProcedureModel _SelectProcedure;
  54. /// <summary>
  55. /// 选中的流程
  56. /// </summary>
  57. public ProcedureModel SelectProcedure
  58. {
  59. get { return _SelectProcedure; }
  60. set
  61. {
  62. SetProperty(ref _SelectProcedure, value);
  63. }
  64. }
  65. private ICamera _Camera;
  66. public ICamera Camera
  67. {
  68. get { return _Camera; }
  69. set { SetProperty(ref _Camera, value); }
  70. }
  71. private string _Message = "等待开始...";
  72. public string Message
  73. {
  74. get { return _Message; }
  75. set { SetProperty(ref _Message, value); }
  76. }
  77. private bool _IsRunning;
  78. public bool IsRunning
  79. {
  80. get { return _IsRunning; }
  81. set { SetProperty(ref _IsRunning, value); }
  82. }
  83. private CogToolBlock _VisionTool;
  84. public CogToolBlock VisionTool
  85. {
  86. get { return _VisionTool; }
  87. set { SetProperty(ref _VisionTool, value); }
  88. }
  89. private int _RepeatCount = 10;
  90. /// <summary>
  91. /// 重复次数
  92. /// </summary>
  93. public int RepeatCount
  94. {
  95. get { return _RepeatCount; }
  96. set { SetProperty(ref _RepeatCount, value); }
  97. }
  98. private int _CurrentProgress;
  99. /// <summary>
  100. /// 当前进度
  101. /// </summary>
  102. public int CurrentProgress
  103. {
  104. get { return _CurrentProgress; }
  105. set { SetProperty(ref _CurrentProgress, value); }
  106. }
  107. private int _SuccessCount;
  108. /// <summary>
  109. /// 成功次数
  110. /// </summary>
  111. public int SuccessCount
  112. {
  113. get { return _SuccessCount; }
  114. set { SetProperty(ref _SuccessCount, value); }
  115. }
  116. private int _FailCount;
  117. /// <summary>
  118. /// 失败次数
  119. /// </summary>
  120. public int FailCount
  121. {
  122. get { return _FailCount; }
  123. set { SetProperty(ref _FailCount, value); }
  124. }
  125. private double _SuccessRate;
  126. /// <summary>
  127. /// 成功率
  128. /// </summary>
  129. public double SuccessRate
  130. {
  131. get { return _SuccessRate; }
  132. set { SetProperty(ref _SuccessRate, value); }
  133. }
  134. private DataTable _TestResult;
  135. /// <summary>
  136. /// 测试结果列表DataTable
  137. /// </summary>
  138. public DataTable TestResult
  139. {
  140. get { return _TestResult; }
  141. set { SetProperty(ref _TestResult, value); }
  142. }
  143. private ObservableCollection<DataColumnInfo> _ResultDataColumns;
  144. /// <summary>
  145. /// DataTable的列集合
  146. /// </summary>
  147. public ObservableCollection<DataColumnInfo> ResultDataColumns
  148. {
  149. get { return _ResultDataColumns; }
  150. set { SetProperty(ref _ResultDataColumns, value); }
  151. }
  152. //
  153. private DataColumnInfo _SelectedDataColumn;
  154. /// <summary>
  155. /// 选中的列
  156. /// </summary>
  157. public DataColumnInfo SelectedDataColumn
  158. {
  159. get { return _SelectedDataColumn; }
  160. set { SetProperty(ref _SelectedDataColumn, value); }
  161. }
  162. private SnackbarMessageQueue _MessageQueue;
  163. public SnackbarMessageQueue MessageQueue
  164. {
  165. get { return _MessageQueue; }
  166. set { SetProperty(ref _MessageQueue, value); }
  167. }
  168. //选中列分析结果
  169. private StabilityMetrics _StabilityMetrics;
  170. public StabilityMetrics StabilityMetrics
  171. {
  172. get { return _StabilityMetrics; }
  173. set { SetProperty(ref _StabilityMetrics, value); }
  174. }
  175. #endregion
  176. #region 命令
  177. private DelegateCommand _ConfirmCommand;
  178. public DelegateCommand ConfirmCommand =>
  179. _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  180. private DelegateCommand _CancelCommand;
  181. public DelegateCommand CancelCommand =>
  182. _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  183. private DelegateCommand _StartTestCommand;
  184. public DelegateCommand StartTestCommand =>
  185. _StartTestCommand ?? (_StartTestCommand = new DelegateCommand(ExecuteStartTestCommand, CanExecuteStartTestCommand).ObservesProperty(() => IsRunning));
  186. private DelegateCommand _StopTestCommand;
  187. public DelegateCommand StopTestCommand =>
  188. _StopTestCommand ?? (_StopTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  189. //暂停测试命令
  190. private DelegateCommand _PauseTestCommand;
  191. public DelegateCommand PauseTestCommand =>
  192. _PauseTestCommand ?? (_PauseTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  193. private DelegateCommand _ExportCSVCommand;
  194. public DelegateCommand ExportCSVCommand =>
  195. _ExportCSVCommand ?? (_ExportCSVCommand = new DelegateCommand(ExecuteExportCSVCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  196. private DelegateCommand _ExportReportCommand;
  197. public DelegateCommand ExportReportCommand =>
  198. _ExportReportCommand ?? (_ExportReportCommand = new DelegateCommand(ExecuteExportReportCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  199. private DelegateCommand _ClearDataCommand;
  200. public DelegateCommand ClearDataCommand =>
  201. _ClearDataCommand ?? (_ClearDataCommand = new DelegateCommand(ExecuteClearDataCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  202. private DelegateCommand _SelectColumnCommand;
  203. public DelegateCommand SelectColumnCommand =>
  204. _SelectColumnCommand ?? (_SelectColumnCommand = new DelegateCommand(ExecuteSelectColumnCommand));
  205. #endregion
  206. #region 事件
  207. #endregion
  208. public VisionStaticAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
  209. IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService)
  210. {
  211. _regionManager = regionManager;
  212. _eventAggregator = ea;
  213. _container = container;
  214. _dialogService = dialogService;
  215. _feederService = feederService;
  216. _robotService = robotService;
  217. _cameraService = cameraService;
  218. _systemDatabaseService = systemDatabaseService;
  219. MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
  220. _calibrationService = calibrationService;
  221. }
  222. #region 方法
  223. /// <summary>
  224. /// 确定
  225. /// </summary>
  226. void ExecuteConfirmCommand()
  227. {
  228. _cts?.Cancel();
  229. IDialogParameters parameters = new DialogParameters();
  230. //parameters.Add("Tool", Tool);
  231. //parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
  232. //parameters.Add("Angle", Angle);
  233. RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
  234. }
  235. /// <summary>
  236. /// 取消
  237. /// </summary>
  238. void ExecuteCancelCommand()
  239. {
  240. _cts?.Cancel();
  241. IDialogParameters parameters = new DialogParameters();
  242. //parameters.Add("Tool", Tool);
  243. RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
  244. }
  245. /// <summary>
  246. /// 开始测试
  247. /// </summary>
  248. async void ExecuteStartTestCommand()
  249. {
  250. IsRunning = true;
  251. _cts = new CancellationTokenSource();
  252. CurrentProgress = 0;
  253. SuccessCount = 0;
  254. SuccessRate = 0;
  255. FailCount = 0;
  256. Message = "测试进行中...";
  257. try
  258. {
  259. while (CurrentProgress < RepeatCount)
  260. {
  261. if (_cts.Token.IsCancellationRequested)
  262. {
  263. Message = "测试已取消!";
  264. break;
  265. }
  266. (bool isSuccess, object[] Result) = await ExecutePhotoEx(CurrentProgress);
  267. if (isSuccess)
  268. {
  269. SuccessCount++;
  270. }
  271. else
  272. {
  273. FailCount++;
  274. }
  275. TestResult.Rows.Add(Result);
  276. ExecuteSelectColumnCommand();
  277. CurrentProgress++;
  278. SuccessRate = (double)SuccessCount / CurrentProgress * 100;
  279. SendTaskMessage($"测试进行中... {CurrentProgress}/{RepeatCount}");
  280. }
  281. if (CurrentProgress >= RepeatCount)
  282. {
  283. Message = "测试完成!";
  284. }
  285. }
  286. catch (Exception ex)
  287. {
  288. LogHelper.WriteLogError("视觉静态重复测试时出错!", ex);
  289. }
  290. finally
  291. {
  292. IsRunning = false;
  293. }
  294. }
  295. bool CanExecuteStartTestCommand()
  296. {
  297. return !IsRunning;
  298. }
  299. /// <summary>
  300. /// 清除数据
  301. /// </summary>
  302. void ExecuteClearDataCommand()
  303. {
  304. if (TestResult != null)
  305. {
  306. TestResult.Rows.Clear();
  307. }
  308. }
  309. /// <summary>
  310. /// 导出测试报告
  311. /// 使用 iTextSharp 生成 PDF,包含:标题、测试摘要、数据表格、每列稳定性分析结果
  312. /// 详细伪代码:
  313. /// 1. 校验 TestResult 是否存在数据,若无则提示并返回。
  314. /// 2. 弹出保存对话框,获取保存路径,若取消返回。
  315. /// 3. 创建 iTextSharp Document 与 PdfWriter,打开文档。
  316. /// 4. 创建支持中文的 BaseFont(例如 STSongStd-Light + UniGB-UCS2-H)。
  317. /// 5. 写入标题(居中、大号字体)。
  318. /// 6. 写入测试摘要信息(测试时间、重复次数、成功/失败/成功率等),每项为单独段落或表格。
  319. /// 7. 构建 PdfPTable:列数 = TestResult.Columns.Count,写入表头(加粗),逐行写入数据:
  320. /// - 对于数值使用 InvariantCulture 格式化,空值写空字符串。
  321. /// 8. 写入分析结果标题。
  322. /// 9. 对于 ResultDataColumns 中的每列:
  323. /// - 从 TestResult 读取该列所有数值(尝试转换为 double,忽略无法转换的项)。
  324. /// - 如果样本数为 0,写入“样本数为0”提示并跳过。
  325. /// - 调用 StabilityAnalyzer.AnalyzeStability(values) 获取指标对象。
  326. /// - 遍历指标对象的公开属性,将属性名与值写入一个两列的 PdfPTable(或段落)。
  327. /// 10. 关闭文档并释放资源。
  328. /// 11. 捕获异常并记录日志,提示用户失败信息。
  329. /// </summary>
  330. void ExecuteExportReportCommand()
  331. {
  332. try
  333. {
  334. if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
  335. {
  336. SendTaskMessage("无测试数据,无法导出报告。");
  337. return;
  338. }
  339. var dlg = new Microsoft.Win32.SaveFileDialog
  340. {
  341. DefaultExt = "pdf",
  342. Filter = "PDF 文件 (*.pdf)|*.pdf|所有文件 (*.*)|*.*",
  343. FileName = "TestReport.pdf",
  344. Title = "保存测试报告为 PDF"
  345. };
  346. bool? dlgResult = dlg.ShowDialog();
  347. if (dlgResult != true)
  348. return;
  349. string path = dlg.FileName;
  350. // 创建文档(A4, 边距)
  351. var doc = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 36, 36, 54, 54);
  352. using (var fs = System.IO.File.Create(path))
  353. {
  354. var writer = iTextSharp.text.pdf.PdfWriter.GetInstance(doc, fs);
  355. doc.Open();
  356. // --- 字体加载:优先载入系统中文字体并以 IDENTITY_H 编码嵌入,保证中文显示 ---
  357. iTextSharp.text.Font titleFont;
  358. iTextSharp.text.Font headerFont;
  359. iTextSharp.text.Font normalFont;
  360. iTextSharp.text.pdf.BaseFont baseFont = null;
  361. try
  362. {
  363. var fontsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
  364. var candidates = new[]
  365. {
  366. "msyh.ttf",
  367. "msyhbd.ttf",
  368. "simsun.ttc,0", // 添加 ",0" 指定第一个字体
  369. "simsun.ttc,1", // 第二个字体
  370. "Microsoft YaHei.ttf",
  371. "msyh.ttc,0", // 添加 ",0"
  372. "msyh.ttc,1", // 添加 ",1"
  373. "simhei.ttf"
  374. };
  375. foreach (var f in candidates)
  376. {
  377. var path1 = Path.Combine(fontsFolder, f);
  378. try
  379. {
  380. baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(
  381. path1,
  382. iTextSharp.text.pdf.BaseFont.IDENTITY_H,
  383. iTextSharp.text.pdf.BaseFont.EMBEDDED
  384. );
  385. if (baseFont != null)
  386. {
  387. Console.WriteLine($"成功加载字体: {f}");
  388. break;
  389. }
  390. }
  391. catch (Exception ex)
  392. {
  393. Console.WriteLine($"字体 {f} 加载失败: {ex.Message}");
  394. continue;
  395. }
  396. }
  397. }
  398. catch
  399. {
  400. baseFont = null;
  401. }
  402. if (baseFont != null)
  403. {
  404. titleFont = new iTextSharp.text.Font(baseFont, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  405. headerFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  406. normalFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  407. }
  408. else
  409. {
  410. // 回退:如果未找到系统中文字体,使用内置 Helvetica(注意:可能无法正确显示中文)
  411. titleFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  412. headerFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  413. normalFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  414. }
  415. // 标题
  416. var title = new iTextSharp.text.Paragraph("视觉静态精度测试报告", titleFont)
  417. {
  418. Alignment = iTextSharp.text.Element.ALIGN_CENTER,
  419. SpacingAfter = 12f
  420. };
  421. doc.Add(title);
  422. // 测试摘要
  423. var metaTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 100f };
  424. metaTable.SetWidths(new float[] { 1f, 2f });
  425. void AddMeta(string name, string value)
  426. {
  427. var cellName = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(name, headerFont)) { Border = 0, PaddingBottom = 6f };
  428. var cellVal = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(value, normalFont)) { Border = 0, PaddingBottom = 6f };
  429. metaTable.AddCell(cellName);
  430. metaTable.AddCell(cellVal);
  431. }
  432. AddMeta("导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  433. AddMeta("测试次数(目标)", RepeatCount.ToString());
  434. AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
  435. AddMeta("成功次数", SuccessCount.ToString());
  436. AddMeta("失败次数", FailCount.ToString());
  437. AddMeta("成功率(%)", SuccessRate.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
  438. doc.Add(metaTable);
  439. doc.Add(new iTextSharp.text.Paragraph(" ")); // 空行
  440. // 数据表格
  441. int colCount = TestResult.Columns.Count;
  442. var table = new iTextSharp.text.pdf.PdfPTable(colCount) { WidthPercentage = 100f };
  443. // 表头
  444. foreach (System.Data.DataColumn col in TestResult.Columns)
  445. {
  446. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(col.ColumnName, headerFont))
  447. {
  448. HorizontalAlignment = iTextSharp.text.Element.ALIGN_CENTER,
  449. BackgroundColor = new iTextSharp.text.BaseColor(230, 230, 230),
  450. Padding = 4f
  451. };
  452. table.AddCell(cell);
  453. }
  454. // 数据行
  455. foreach (System.Data.DataRow row in TestResult.Rows)
  456. {
  457. for (int c = 0; c < colCount; c++)
  458. {
  459. object val = row[c];
  460. string s;
  461. if (val == null || val == DBNull.Value)
  462. s = "";
  463. else if (val is double || val is float || val is decimal)
  464. s = Convert.ToDouble(val).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
  465. else
  466. s = val.ToString();
  467. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(s, normalFont)) { Padding = 4f };
  468. table.AddCell(cell);
  469. }
  470. }
  471. doc.Add(table);
  472. doc.NewPage();
  473. // 分析结果
  474. var analysisTitle = new iTextSharp.text.Paragraph("分析结果", titleFont) { SpacingAfter = 8f };
  475. doc.Add(analysisTitle);
  476. if (ResultDataColumns != null && ResultDataColumns.Count > 0)
  477. {
  478. foreach (var colInfo in ResultDataColumns)
  479. {
  480. try
  481. {
  482. // 收集数值
  483. var values = new System.Collections.Generic.List<double>();
  484. foreach (System.Data.DataRow row in TestResult.Rows)
  485. {
  486. object v = row[colInfo.ColumnIndex];
  487. if (v == null || v == DBNull.Value) continue;
  488. double d;
  489. if (v is double) d = (double)v;
  490. else if (v is float) d = Convert.ToDouble((float)v);
  491. else if (v is decimal) d = Convert.ToDouble((decimal)v);
  492. else if (v is int) d = Convert.ToDouble((int)v);
  493. else if (v is long) d = Convert.ToDouble((long)v);
  494. else
  495. {
  496. if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
  497. continue;
  498. }
  499. values.Add(d);
  500. }
  501. var colHeader = new iTextSharp.text.Paragraph(colInfo.ColumnName, headerFont) { SpacingBefore = 6f, SpacingAfter = 4f };
  502. doc.Add(colHeader);
  503. if (values.Count == 0)
  504. {
  505. doc.Add(new iTextSharp.text.Paragraph("样本数为 0,无法计算。", normalFont));
  506. continue;
  507. }
  508. // 计算稳定性指标
  509. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
  510. // 将指标写成两列表(属性名 / 值)
  511. var metricsTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 60f, SpacingAfter = 6f };
  512. metricsTable.SetWidths(new float[] { 1f, 1f });
  513. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  514. foreach (var p in props)
  515. {
  516. object pv = p.GetValue(metrics);
  517. string pvStr;
  518. if (pv == null) pvStr = "";
  519. else if (pv is double) pvStr = ((double)pv).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
  520. else pvStr = pv.ToString();
  521. var pc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(p.Name, normalFont)) { Padding = 4f };
  522. var vc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(pvStr, normalFont)) { Padding = 4f };
  523. metricsTable.AddCell(pc);
  524. metricsTable.AddCell(vc);
  525. }
  526. doc.Add(metricsTable);
  527. }
  528. catch (Exception exCol)
  529. {
  530. LogHelper.WriteLogError($"导出报告时处理列[{colInfo.ColumnName}]出错", exCol);
  531. doc.Add(new iTextSharp.text.Paragraph($"处理列 {colInfo.ColumnName} 时出错: {exCol.Message}", normalFont));
  532. }
  533. }
  534. }
  535. else
  536. {
  537. doc.Add(new iTextSharp.text.Paragraph("无用于分析的数值列。", normalFont));
  538. }
  539. doc.Close();
  540. writer.Close();
  541. }
  542. SendTaskMessage($"已导出测试报告到:{path}");
  543. }
  544. catch (Exception ex)
  545. {
  546. LogHelper.WriteLogError("导出测试报告时出错", ex);
  547. SendTaskMessage($"导出测试报告失败:{ex.Message}");
  548. }
  549. }
  550. /// <summary>
  551. /// 导出测试数据为CSV文件
  552. /// </summary>
  553. void ExecuteExportCSVCommand()
  554. {
  555. try
  556. {
  557. if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
  558. {
  559. SendTaskMessage("无测试数据,无法导出。");
  560. return;
  561. }
  562. var dlg = new Microsoft.Win32.SaveFileDialog
  563. {
  564. DefaultExt = "csv",
  565. Filter = "CSV 文件 (*.csv)|*.csv|所有文件 (*.*)|*.*",
  566. FileName = "TestResult.csv",
  567. Title = "保存测试结果为 CSV"
  568. };
  569. bool? dlgResult = dlg.ShowDialog();
  570. if (dlgResult != true)
  571. return;
  572. string path = dlg.FileName;
  573. var sb = new System.Text.StringBuilder();
  574. // 辅助:CSV 字段转义
  575. Func<string, string> EscapeCsv = (s) =>
  576. {
  577. if (s == null) return "";
  578. bool mustQuote = s.Contains(",") || s.Contains("\"") || s.Contains("\r") || s.Contains("\n");
  579. string esc = s.Replace("\"", "\"\"");
  580. return mustQuote ? $"\"{esc}\"" : esc;
  581. };
  582. // 1. 写入表头
  583. for (int c = 0; c < TestResult.Columns.Count; c++)
  584. {
  585. if (c > 0) sb.Append(",");
  586. sb.Append(EscapeCsv(TestResult.Columns[c].ColumnName));
  587. }
  588. sb.AppendLine();
  589. // 2. 写入数据行
  590. foreach (System.Data.DataRow row in TestResult.Rows)
  591. {
  592. for (int c = 0; c < TestResult.Columns.Count; c++)
  593. {
  594. if (c > 0) sb.Append(",");
  595. object val = row[c];
  596. if (val == DBNull.Value || val == null)
  597. {
  598. sb.Append("");
  599. }
  600. else
  601. {
  602. // 保持数值格式,其他转为字符串
  603. string outStr;
  604. if (val is double || val is float || val is decimal)
  605. outStr = Convert.ToString(val, System.Globalization.CultureInfo.InvariantCulture);
  606. else if (val is int || val is long || val is short || val is byte)
  607. outStr = val.ToString();
  608. else if (val is bool)
  609. outStr = (bool)val ? "True" : "False";
  610. else
  611. outStr = val.ToString();
  612. sb.Append(EscapeCsv(outStr));
  613. }
  614. }
  615. sb.AppendLine();
  616. }
  617. // 3. 在末尾追加分析结果
  618. sb.AppendLine(); // 空行
  619. sb.AppendLine("分析结果");
  620. sb.AppendLine("列名,指标,值"); // CSV 表头:列名,指标,值
  621. if (ResultDataColumns != null)
  622. {
  623. foreach (var colInfo in ResultDataColumns)
  624. {
  625. try
  626. {
  627. List<double> values = new List<double>();
  628. foreach (System.Data.DataRow row in TestResult.Rows)
  629. {
  630. object v = row[colInfo.ColumnIndex];
  631. if (v != DBNull.Value && v != null)
  632. {
  633. double d;
  634. // 支持不同数字类型
  635. if (v is double) d = (double)v;
  636. else if (v is float) d = Convert.ToDouble((float)v);
  637. else if (v is decimal) d = Convert.ToDouble((decimal)v);
  638. else if (v is int) d = Convert.ToDouble((int)v);
  639. else if (v is long) d = Convert.ToDouble((long)v);
  640. else
  641. {
  642. if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
  643. continue;
  644. }
  645. values.Add(d);
  646. }
  647. }
  648. if (values.Count == 0)
  649. {
  650. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("样本数")},{0}");
  651. continue;
  652. }
  653. // 调用稳定性分析
  654. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
  655. // 使用反射枚举 metrics 的公开属性并写入 CSV
  656. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  657. foreach (var p in props)
  658. {
  659. object pv = p.GetValue(metrics);
  660. string pvStr = pv == null ? "" : (pv is double ? Convert.ToString((double)pv, System.Globalization.CultureInfo.InvariantCulture) : pv.ToString());
  661. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv(p.Name)},{EscapeCsv(pvStr)}");
  662. }
  663. }
  664. catch (Exception exCol)
  665. {
  666. LogHelper.WriteLogError($"导出分析结果时处理列[{colInfo.ColumnName}]出错", exCol);
  667. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("Error")},{EscapeCsv(exCol.Message)}");
  668. }
  669. }
  670. }
  671. // 写入文件(UTF8,无 BOM,若需 BOM 可使用 new UTF8Encoding(true))
  672. System.IO.File.WriteAllText(path, sb.ToString(), System.Text.Encoding.UTF8);
  673. SendTaskMessage($"已导出测试结果到:{path}");
  674. }
  675. catch (Exception ex)
  676. {
  677. LogHelper.WriteLogError("导出CSV时出错", ex);
  678. SendTaskMessage($"导出CSV失败:{ex.Message}");
  679. }
  680. }
  681. /// <summary>
  682. /// 选中列时触发
  683. /// </summary>
  684. void ExecuteSelectColumnCommand()
  685. {
  686. try
  687. {
  688. //如果选中列为空,则直接返回
  689. if (SelectedDataColumn == null)
  690. return;
  691. //获取表的指定列的所有数据集合
  692. List<double> dataList = new List<double>();
  693. foreach (DataRow row in TestResult.Rows)
  694. {
  695. if (row[SelectedDataColumn.ColumnIndex] != DBNull.Value)
  696. {
  697. dataList.Add(Convert.ToDouble(row[SelectedDataColumn.ColumnIndex]));
  698. }
  699. }
  700. //如果数据量小于10,则不进行分析
  701. if (dataList.Count < 10)
  702. {
  703. //MessageQueue.Enqueue("选中列的数据量小于10,无法进行统计分析!");
  704. return;
  705. }
  706. //进行稳定性分析
  707. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dataList.ToArray());
  708. App.Current.Dispatcher.Invoke(() =>
  709. {
  710. StabilityMetrics = metrics;
  711. });
  712. }
  713. catch (Exception ex)
  714. {
  715. LogHelper.WriteLogError("选中列的数据分析数据稳定性时出错!", ex);
  716. }
  717. }
  718. /// <summary>
  719. /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
  720. /// </summary>
  721. /// <param name="index"></param>
  722. /// <returns></returns>
  723. public Task<(bool isSuccess, object[] Result)> ExecutePhotoEx(int index)
  724. {
  725. return Task.Run(() =>
  726. {
  727. CogToolBlockTerminalCollection outputCollection;
  728. List<object> result = new List<object>();
  729. result.Add(index);
  730. try
  731. {
  732. // 1. 采集图像
  733. bool succed = Camera.SetExposureTime(SelectProcedure.ExposureTime);
  734. if (!succed)
  735. {
  736. SendTaskMessage(Lang.相机曝光设置失败);
  737. }
  738. succed = Camera.SetGain(SelectProcedure.Gain);
  739. if (!succed)
  740. {
  741. SendTaskMessage(Lang.相机增益设置失败);
  742. }
  743. DateTime nowtime = DateTime.Now;
  744. LogHelper.WriteLogInfo(Lang.开始采集图像);
  745. var image = Camera.Grab();
  746. if (image == null)
  747. {
  748. SendTaskMessage(Lang.图像采集失败);
  749. outputCollection = null;
  750. return (false, result.ToArray());
  751. }
  752. //Image = image;
  753. VisionTool.Inputs["InputImage"].Value = image;
  754. LogHelper.WriteLogInfo(string.Format(Lang.采集图像完成用时0ms, (DateTime.Now - nowtime).Milliseconds));
  755. // 2. 为ToolBlock传入其他输入终端
  756. //if (InputTerminal != null)
  757. //{
  758. // LogHelper.WriteLogInfo(Lang.为ToolBlock传入输入终端);
  759. // foreach (var item in InputTerminal)
  760. // {
  761. // if (VisionTool.Inputs.Contains(item.Key))
  762. // {
  763. // LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
  764. // VisionTool.Inputs[item.Key].Value = item.Value;
  765. // }
  766. // else
  767. // {
  768. // LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
  769. // VisionTool.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
  770. // }
  771. // }
  772. //}
  773. Image = VisionTool.Inputs["InputImage"].Value as ICogImage;
  774. // 3. 运行视觉工具
  775. LogHelper.WriteLogInfo(Lang.开始运行视觉工具);
  776. VisionTool.Run(); //运行ToolBlock
  777. if (VisionTool.RunStatus.Result == CogToolResultConstants.Accept)
  778. {
  779. SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  780. outputCollection = VisionTool.Outputs; //获取输出终端集合
  781. foreach (CogToolBlockTerminal terminal in outputCollection)
  782. {
  783. //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
  784. if (terminal.ValueType == typeof(string))
  785. {
  786. //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
  787. if (terminal.Name == "Point")
  788. {
  789. //是否需要转换为绝对坐标
  790. needConvertToAbsolute = false;
  791. //如果当前流程的校准为空,则不需要转换为绝对坐标
  792. if (SelectProcedure.CalibrationId != Guid.Empty)
  793. {
  794. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  795. if (calibration != null)
  796. {
  797. needConvertToAbsolute = true;
  798. }
  799. }
  800. //判断是否是点位格式
  801. string strpoints = terminal.Value.ToString();
  802. string[] items = strpoints.Split(';');
  803. bool isPointFormat = true;
  804. foreach (string item in items)
  805. {
  806. string[] subitems = item.Split(',');
  807. if (subitems.Length < 3)
  808. {
  809. isPointFormat = false;
  810. break;
  811. }
  812. double x, y, u;
  813. if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
  814. {
  815. isPointFormat = false;
  816. break;
  817. }
  818. }
  819. if (isPointFormat)
  820. {
  821. //是点位格式,则创建多列
  822. for (int i = 0; i < items.Length; i++)
  823. {
  824. string[] subitems = items[i].Split(',');
  825. //先分别创建像素X、像素Y、角度U三列
  826. result.Add(double.Parse(subitems[0]));
  827. result.Add(double.Parse(subitems[1]));
  828. result.Add(double.Parse(subitems[2]));
  829. //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
  830. if (needConvertToAbsolute)
  831. {
  832. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  833. var robot= _robotService.GetAllRobots().FirstOrDefault();
  834. var pos= robot.GetRobotPos();
  835. double[] robotpos = new double[] { pos.X,pos.Y,pos.Z };
  836. (bool IsSucceed, double X, double Y, double U) = _calibrationService.ConvertPixelToPosition((double.Parse(subitems[0]), double.Parse(subitems[1]), double.Parse(subitems[2])), robotpos, calibration);
  837. result.Add(Math.Round(X, 5));
  838. result.Add(Math.Round(Y, 5));
  839. }
  840. }
  841. }
  842. else
  843. {
  844. result.Add(terminal.Value.ToString());
  845. }
  846. }
  847. else
  848. {
  849. result.Add(terminal.Value.ToString());
  850. }
  851. }
  852. else if (terminal.ValueType == typeof(int))
  853. result.Add((int)terminal.Value);
  854. else if (terminal.ValueType == typeof(double))
  855. result.Add((double)terminal.Value);
  856. else if (terminal.ValueType == typeof(bool))
  857. result.Add((bool)terminal.Value);
  858. }
  859. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  860. return (true, result.ToArray());
  861. }
  862. else
  863. {
  864. SendTaskMessage(Lang.视觉流程执行出错耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  865. SendTaskMessage(VisionTool.RunStatus.Message);
  866. outputCollection = null;
  867. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  868. return (false, result.ToArray());
  869. }
  870. }
  871. catch (Exception ex)
  872. {
  873. SendTaskMessage(ex.Message);
  874. LogHelper.WriteLogError(Lang.执行相机取图并执行视觉工具组时出错, ex);
  875. outputCollection = null;
  876. return (false, result.ToArray());
  877. }
  878. });
  879. }
  880. public void SendTaskMessage(string msg)
  881. {
  882. App.Current.Dispatcher.Invoke(() =>
  883. {
  884. MessageQueue.Clear();
  885. MessageQueue.Enqueue(msg);
  886. });
  887. }
  888. bool needConvertToAbsolute = false;
  889. /// <summary>
  890. /// 根据VisionTool的输出终端,创建DataTable的列
  891. /// </summary>
  892. /// <param name="outputCollection"></param>
  893. /// <returns></returns>
  894. private DataTable CreateResultDataTable(CogToolBlockTerminalCollection outputCollection)
  895. {
  896. DataTable dt = new DataTable();
  897. ResultDataColumns = new ObservableCollection<DataColumnInfo>();
  898. //第一列添加序号
  899. dt.Columns.Add("序号", typeof(int));
  900. //列索引
  901. foreach (CogToolBlockTerminal terminal in outputCollection)
  902. {
  903. //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
  904. if (terminal.ValueType == typeof(string))
  905. {
  906. //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
  907. if (terminal.Name == "Point")
  908. {
  909. //是否需要转换为绝对坐标
  910. needConvertToAbsolute = false;
  911. //如果当前流程的校准为空,则不需要转换为绝对坐标
  912. if (SelectProcedure.CalibrationId != Guid.Empty)
  913. {
  914. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  915. if (calibration != null)
  916. {
  917. needConvertToAbsolute = true;
  918. }
  919. }
  920. //判断是否是点位格式
  921. string strpoints = terminal.Value.ToString();
  922. string[] items = strpoints.Split(';');
  923. bool isPointFormat = true;
  924. foreach (string item in items)
  925. {
  926. string[] subitems = item.Split(',');
  927. if (subitems.Length < 3)
  928. {
  929. isPointFormat = false;
  930. break;
  931. }
  932. double x, y, u;
  933. if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
  934. {
  935. isPointFormat = false;
  936. break;
  937. }
  938. }
  939. if (isPointFormat)
  940. {
  941. //是点位格式,则创建多列
  942. for (int i = 0; i < items.Length; i++)
  943. {
  944. //先分别创建像素X、像素Y、角度U三列
  945. dt.Columns.Add($"{terminal.Name}_Pixel_X{i + 1}", typeof(double));
  946. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_X{i + 1}", dt.Columns.Count - 1));
  947. dt.Columns.Add($"{terminal.Name}_Pixel_Y{i + 1}", typeof(double));
  948. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_Y{i + 1}", dt.Columns.Count - 1));
  949. dt.Columns.Add($"{terminal.Name}_Angle_U{i + 1}", typeof(double));
  950. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Angle_U{i + 1}", dt.Columns.Count - 1));
  951. //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
  952. if (needConvertToAbsolute)
  953. {
  954. dt.Columns.Add($"{terminal.Name}_Absolute_X{i + 1}", typeof(double));
  955. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_X{i + 1}", dt.Columns.Count - 1));
  956. dt.Columns.Add($"{terminal.Name}_Absolute_Y{i + 1}", typeof(double));
  957. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_Y{i + 1}", dt.Columns.Count - 1));
  958. }
  959. }
  960. }
  961. else
  962. {
  963. dt.Columns.Add(terminal.Name, typeof(string));
  964. }
  965. }
  966. else
  967. {
  968. dt.Columns.Add(terminal.Name, typeof(string));
  969. }
  970. }
  971. else if (terminal.ValueType == typeof(int))
  972. {
  973. dt.Columns.Add(terminal.Name, typeof(int));
  974. ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
  975. }
  976. else if (terminal.ValueType == typeof(double))
  977. {
  978. dt.Columns.Add(terminal.Name, typeof(double));
  979. ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
  980. }
  981. else if (terminal.ValueType == typeof(bool))
  982. {
  983. dt.Columns.Add(terminal.Name, typeof(bool));
  984. }
  985. //else
  986. // dt.Columns.Add(terminal.Name, typeof(string));
  987. }
  988. return dt;
  989. }
  990. #endregion
  991. #region 继承
  992. public string Title { get; set; } = "视觉静态精度分析仪";
  993. public event Action<IDialogResult> RequestClose;
  994. public bool CanCloseDialog()
  995. {
  996. return true;
  997. }
  998. public void OnDialogClosed()
  999. {
  1000. //关闭时,停止测试
  1001. _cts?.Cancel();
  1002. }
  1003. public void OnDialogOpened(IDialogParameters parameters)
  1004. {
  1005. SelectProcedure = parameters.GetValue<ProcedureModel>("SelectedProcedure");
  1006. Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
  1007. VisionTool = parameters.GetValue<CogToolBlock>("ToolBlock");
  1008. VisionTool.Run();
  1009. TestResult = CreateResultDataTable(VisionTool.Outputs);
  1010. }
  1011. #endregion
  1012. }
  1013. //辅助类,用于定义DataTable的列,仅用来对于数值列进行统计分析
  1014. public class DataColumnInfo
  1015. {
  1016. public string ColumnName { get; set; }
  1017. //列索引
  1018. public int ColumnIndex { get; set; }
  1019. public DataColumnInfo(string columnName, int columnIndex)
  1020. {
  1021. ColumnName = columnName;
  1022. ColumnIndex = columnIndex;
  1023. }
  1024. }
  1025. }