VisionDynamicAccuracyAnalyzerViewModel.cs 51 KB

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