| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191 |
- using Cognex.VisionPro;
- using Cognex.VisionPro.ToolBlock;
- using MaterialDesignThemes.Wpf;
- using Prism.Commands;
- using Prism.Events;
- using Prism.Ioc;
- using Prism.Mvvm;
- using Prism.Regions;
- using Prism.Services.Dialogs;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Data;
- using System.IO;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using Team.FFFeederService;
- using Team.FFFeederService.Interfaces;
- using TeamAAS_VP.Core;
- using TeamAAS_VP.Data;
- using TeamAAS_VP.Enums;
- using TeamAAS_VP.Events;
- using TeamAAS_VP.Interfaces;
- using TeamAAS_VP.Models;
- using TeamAAS_VP.Models.Calibration;
- using TeamAAS_VP.Resources.Languages;
- using TeamAAS_VP.Services;
- using TeamAAS_VP.Views.Setting;
- using static TeamAAS_VP.Core.StabilityAnalyzer;
- namespace TeamAAS_VP.ViewModels.Product
- {
- public class VisionStaticAccuracyAnalyzerViewModel : BindableBase, IDialogAware
- {
- IRegionManager _regionManager;
- IEventAggregator _eventAggregator;
- IContainerProvider _container;
- IDialogService _dialogService;
- IFeederService _feederService;
- IRobotService _robotService;
- ICameraService _cameraService;
- ISystemDatabaseService _systemDatabaseService;
- ICalibrationService _calibrationService;
- IConfigService _configService;
- //测试过程中控制取消的CTS
- CancellationTokenSource _cts;
- #region 属性
- private ICogImage _Image;
- public ICogImage Image
- {
- get { return _Image; }
- set { SetProperty(ref _Image, value); }
- }
- private Cognex.VisionPro.CogGraphicCollection _Graphic;
- public Cognex.VisionPro.CogGraphicCollection Graphic
- {
- get { return _Graphic; }
- set { SetProperty(ref _Graphic, value); }
- }
- private ProcedureModel _SelectProcedure;
- /// <summary>
- /// 选中的流程
- /// </summary>
- public ProcedureModel SelectProcedure
- {
- get { return _SelectProcedure; }
- set
- {
- SetProperty(ref _SelectProcedure, value);
- }
- }
- private ICamera _Camera;
- public ICamera Camera
- {
- get { return _Camera; }
- set { SetProperty(ref _Camera, value); }
- }
- private string _Message = "等待开始...";
- public string Message
- {
- get { return _Message; }
- set { SetProperty(ref _Message, value); }
- }
- private bool _IsRunning;
- public bool IsRunning
- {
- get { return _IsRunning; }
- set { SetProperty(ref _IsRunning, value); }
- }
- private CogToolBlock _VisionTool;
- public CogToolBlock VisionTool
- {
- get { return _VisionTool; }
- set { SetProperty(ref _VisionTool, value); }
- }
- private int _RepeatCount = 10;
- /// <summary>
- /// 重复次数
- /// </summary>
- public int RepeatCount
- {
- get { return _RepeatCount; }
- set { SetProperty(ref _RepeatCount, value); }
- }
- private int _CurrentProgress;
- /// <summary>
- /// 当前进度
- /// </summary>
- public int CurrentProgress
- {
- get { return _CurrentProgress; }
- set { SetProperty(ref _CurrentProgress, value); }
- }
- private int _SuccessCount;
- /// <summary>
- /// 成功次数
- /// </summary>
- public int SuccessCount
- {
- get { return _SuccessCount; }
- set { SetProperty(ref _SuccessCount, value); }
- }
- private int _FailCount;
- /// <summary>
- /// 失败次数
- /// </summary>
- public int FailCount
- {
- get { return _FailCount; }
- set { SetProperty(ref _FailCount, value); }
- }
- private double _SuccessRate;
- /// <summary>
- /// 成功率
- /// </summary>
- public double SuccessRate
- {
- get { return _SuccessRate; }
- set { SetProperty(ref _SuccessRate, value); }
- }
- private DataTable _TestResult;
- /// <summary>
- /// 测试结果列表DataTable
- /// </summary>
- public DataTable TestResult
- {
- get { return _TestResult; }
- set { SetProperty(ref _TestResult, value); }
- }
- private ObservableCollection<DataColumnInfo> _ResultDataColumns;
- /// <summary>
- /// DataTable的列集合
- /// </summary>
- public ObservableCollection<DataColumnInfo> ResultDataColumns
- {
- get { return _ResultDataColumns; }
- set { SetProperty(ref _ResultDataColumns, value); }
- }
- //
- private DataColumnInfo _SelectedDataColumn;
- /// <summary>
- /// 选中的列
- /// </summary>
- public DataColumnInfo SelectedDataColumn
- {
- get { return _SelectedDataColumn; }
- set { SetProperty(ref _SelectedDataColumn, value); }
- }
- private SnackbarMessageQueue _MessageQueue;
- public SnackbarMessageQueue MessageQueue
- {
- get { return _MessageQueue; }
- set { SetProperty(ref _MessageQueue, value); }
- }
- //选中列分析结果
- private StabilityMetrics _StabilityMetrics;
- public StabilityMetrics StabilityMetrics
- {
- get { return _StabilityMetrics; }
- set { SetProperty(ref _StabilityMetrics, value); }
- }
- private int _DelayTime = 200;
- /// <summary>
- /// 延时时间
- /// </summary>
- public int DelayTime
- {
- get { return _DelayTime; }
- set { SetProperty(ref _DelayTime, value); }
- }
- #endregion
- #region 命令
- private DelegateCommand _ConfirmCommand;
- public DelegateCommand ConfirmCommand =>
- _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
- private DelegateCommand _CancelCommand;
- public DelegateCommand CancelCommand =>
- _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
- private DelegateCommand _StartTestCommand;
- public DelegateCommand StartTestCommand =>
- _StartTestCommand ?? (_StartTestCommand = new DelegateCommand(ExecuteStartTestCommand, CanExecuteStartTestCommand).ObservesProperty(() => IsRunning));
- private DelegateCommand _StopTestCommand;
- public DelegateCommand StopTestCommand =>
- _StopTestCommand ?? (_StopTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
- //暂停测试命令
- private DelegateCommand _PauseTestCommand;
- public DelegateCommand PauseTestCommand =>
- _PauseTestCommand ?? (_PauseTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
- private DelegateCommand _ExportCSVCommand;
- public DelegateCommand ExportCSVCommand =>
- _ExportCSVCommand ?? (_ExportCSVCommand = new DelegateCommand(ExecuteExportCSVCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
- private DelegateCommand _ExportReportCommand;
- public DelegateCommand ExportReportCommand =>
- _ExportReportCommand ?? (_ExportReportCommand = new DelegateCommand(ExecuteExportReportCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
- private DelegateCommand _ClearDataCommand;
- public DelegateCommand ClearDataCommand =>
- _ClearDataCommand ?? (_ClearDataCommand = new DelegateCommand(ExecuteClearDataCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
- private DelegateCommand _SelectColumnCommand;
- public DelegateCommand SelectColumnCommand =>
- _SelectColumnCommand ?? (_SelectColumnCommand = new DelegateCommand(ExecuteSelectColumnCommand));
- #endregion
- #region 事件
- #endregion
- public VisionStaticAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
- IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService,
- IConfigService configService)
- {
- _regionManager = regionManager;
- _eventAggregator = ea;
- _container = container;
- _dialogService = dialogService;
- _feederService = feederService;
- _robotService = robotService;
- _cameraService = cameraService;
- _systemDatabaseService = systemDatabaseService;
- MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
- _calibrationService = calibrationService;
- _configService = configService;
- }
- #region 方法
- /// <summary>
- /// 确定
- /// </summary>
- void ExecuteConfirmCommand()
- {
- _cts?.Cancel();
- IDialogParameters parameters = new DialogParameters();
- //parameters.Add("Tool", Tool);
- //parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
- //parameters.Add("Angle", Angle);
- RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
- }
- /// <summary>
- /// 取消
- /// </summary>
- void ExecuteCancelCommand()
- {
- _cts?.Cancel();
- IDialogParameters parameters = new DialogParameters();
- //parameters.Add("Tool", Tool);
- RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
- }
- /// <summary>
- /// 开始测试
- /// </summary>
- async void ExecuteStartTestCommand()
- {
- IsRunning = true;
- _cts = new CancellationTokenSource();
- CurrentProgress = 0;
- SuccessCount = 0;
- SuccessRate = 0;
- FailCount = 0;
- Message = "测试进行中...";
- try
- {
- while (CurrentProgress < RepeatCount)
- {
- if (_cts.Token.IsCancellationRequested)
- {
- Message = "测试已取消!";
- break;
- }
- (bool isSuccess, object[] Result) = await ExecutePhotoEx(CurrentProgress);
- if (isSuccess)
- {
- SuccessCount++;
- }
- else
- {
- FailCount++;
- }
- TestResult.Rows.Add(Result);
- ExecuteSelectColumnCommand();
- await Task.Delay(DelayTime);
- CurrentProgress++;
- SuccessRate = (double)SuccessCount / CurrentProgress * 100;
- SendTaskMessage($"测试进行中... {CurrentProgress}/{RepeatCount}");
- }
- if (CurrentProgress >= RepeatCount)
- {
- Message = "测试完成!";
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogError("视觉静态重复测试时出错!", ex);
- }
- finally
- {
- IsRunning = false;
- }
- }
- bool CanExecuteStartTestCommand()
- {
- return !IsRunning;
- }
- /// <summary>
- /// 清除数据
- /// </summary>
- void ExecuteClearDataCommand()
- {
- if(TestResult!=null)
- {
- TestResult.Rows.Clear();
- }
- }
- /// <summary>
- /// 导出测试报告
- /// 使用 iTextSharp 生成 PDF,包含:标题、测试摘要、数据表格、每列稳定性分析结果
- /// 详细伪代码:
- /// 1. 校验 TestResult 是否存在数据,若无则提示并返回。
- /// 2. 弹出保存对话框,获取保存路径,若取消返回。
- /// 3. 创建 iTextSharp Document 与 PdfWriter,打开文档。
- /// 4. 创建支持中文的 BaseFont(例如 STSongStd-Light + UniGB-UCS2-H)。
- /// 5. 写入标题(居中、大号字体)。
- /// 6. 写入测试摘要信息(测试时间、重复次数、成功/失败/成功率等),每项为单独段落或表格。
- /// 7. 构建 PdfPTable:列数 = TestResult.Columns.Count,写入表头(加粗),逐行写入数据:
- /// - 对于数值使用 InvariantCulture 格式化,空值写空字符串。
- /// 8. 写入分析结果标题。
- /// 9. 对于 ResultDataColumns 中的每列:
- /// - 从 TestResult 读取该列所有数值(尝试转换为 double,忽略无法转换的项)。
- /// - 如果样本数为 0,写入“样本数为0”提示并跳过。
- /// - 调用 StabilityAnalyzer.AnalyzeStability(values) 获取指标对象。
- /// - 遍历指标对象的公开属性,将属性名与值写入一个两列的 PdfPTable(或段落)。
- /// 10. 关闭文档并释放资源。
- /// 11. 捕获异常并记录日志,提示用户失败信息。
- /// </summary>
- void ExecuteExportReportCommand()
- {
- try
- {
- if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
- {
- SendTaskMessage("无测试数据,无法导出报告。");
- return;
- }
- var dlg = new Microsoft.Win32.SaveFileDialog
- {
- DefaultExt = "pdf",
- Filter = "PDF 文件 (*.pdf)|*.pdf|所有文件 (*.*)|*.*",
- FileName = "TestReport.pdf",
- Title = "保存测试报告为 PDF"
- };
- bool? dlgResult = dlg.ShowDialog();
- if (dlgResult != true)
- return;
- string path = dlg.FileName;
- // 创建文档(A4, 边距)
- var doc = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 36, 36, 54, 54);
- using (var fs = System.IO.File.Create(path))
- {
- var writer = iTextSharp.text.pdf.PdfWriter.GetInstance(doc, fs);
- doc.Open();
- // --- 字体加载:优先载入系统中文字体并以 IDENTITY_H 编码嵌入,保证中文显示 ---
- iTextSharp.text.Font titleFont;
- iTextSharp.text.Font headerFont;
- iTextSharp.text.Font normalFont;
- iTextSharp.text.pdf.BaseFont baseFont = null;
- try
- {
- var fontsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
- var candidates = new[]
- {
- "msyh.ttf",
- "msyhbd.ttf",
- "simsun.ttc,0", // 添加 ",0" 指定第一个字体
- "simsun.ttc,1", // 第二个字体
- "Microsoft YaHei.ttf",
- "msyh.ttc,0", // 添加 ",0"
- "msyh.ttc,1", // 添加 ",1"
- "simhei.ttf"
- };
- foreach (var f in candidates)
- {
- var path1 = Path.Combine(fontsFolder, f);
- try
- {
- baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(
- path1,
- iTextSharp.text.pdf.BaseFont.IDENTITY_H,
- iTextSharp.text.pdf.BaseFont.EMBEDDED
- );
- if (baseFont != null)
- {
- Console.WriteLine($"成功加载字体: {f}");
- break;
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"字体 {f} 加载失败: {ex.Message}");
- continue;
- }
- }
- }
- catch
- {
- baseFont = null;
- }
- if (baseFont != null)
- {
- titleFont = new iTextSharp.text.Font(baseFont, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
- headerFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
- normalFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
- }
- else
- {
- // 回退:如果未找到系统中文字体,使用内置 Helvetica(注意:可能无法正确显示中文)
- titleFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
- headerFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
- normalFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
- }
- // 标题
- var title = new iTextSharp.text.Paragraph("视觉静态精度测试报告", titleFont)
- {
- Alignment = iTextSharp.text.Element.ALIGN_CENTER,
- SpacingAfter = 12f
- };
- doc.Add(title);
- // 测试摘要
- var metaTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 100f };
- metaTable.SetWidths(new float[] { 1f, 2f });
- void AddMeta(string name, string value)
- {
- var cellName = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(name, headerFont)) { Border = 0, PaddingBottom = 6f };
- var cellVal = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(value, normalFont)) { Border = 0, PaddingBottom = 6f };
- metaTable.AddCell(cellName);
- metaTable.AddCell(cellVal);
- }
- // include device info (line, station, fixture id) but exclude MES flags/urls
- var device = _configService?.GetDeviceInfo() ?? _configService?.GetDeviceInfo();
- if (device != null)
- {
- AddMeta("线别", device.Line ?? string.Empty);
- AddMeta("站别", device.Station ?? string.Empty);
- AddMeta("机台号", device.FixtureId ?? string.Empty);
- }
- AddMeta("导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
- AddMeta("测试次数(目标)", RepeatCount.ToString());
- AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
- AddMeta("成功次数", SuccessCount.ToString());
- AddMeta("失败次数", FailCount.ToString());
- AddMeta("成功率(%)", SuccessRate.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
- doc.Add(metaTable);
- doc.Add(new iTextSharp.text.Paragraph(" ")); // 空行
- // 数据表格
- int colCount = TestResult.Columns.Count;
- var table = new iTextSharp.text.pdf.PdfPTable(colCount) { WidthPercentage = 100f };
- // 表头
- foreach (System.Data.DataColumn col in TestResult.Columns)
- {
- var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(col.ColumnName, headerFont))
- {
- HorizontalAlignment = iTextSharp.text.Element.ALIGN_CENTER,
- BackgroundColor = new iTextSharp.text.BaseColor(230, 230, 230),
- Padding = 4f
- };
- table.AddCell(cell);
- }
- // 数据行
- foreach (System.Data.DataRow row in TestResult.Rows)
- {
- for (int c = 0; c < colCount; c++)
- {
- object val = row[c];
- string s;
- if (val == null || val == DBNull.Value)
- s = "";
- else if (val is double || val is float || val is decimal)
- s = Convert.ToDouble(val).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
- else
- s = val.ToString();
- var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(s, normalFont)) { Padding = 4f };
- table.AddCell(cell);
- }
- }
- doc.Add(table);
- doc.NewPage();
- // 分析结果
- var analysisTitle = new iTextSharp.text.Paragraph("分析结果", titleFont) { SpacingAfter = 8f };
- doc.Add(analysisTitle);
- if (ResultDataColumns != null && ResultDataColumns.Count > 0)
- {
- foreach (var colInfo in ResultDataColumns)
- {
- try
- {
- // 收集数值
- var values = new System.Collections.Generic.List<double>();
- foreach (System.Data.DataRow row in TestResult.Rows)
- {
- object v = row[colInfo.ColumnIndex];
- if (v == null || v == DBNull.Value) continue;
- double d;
- if (v is double) d = (double)v;
- else if (v is float) d = Convert.ToDouble((float)v);
- else if (v is decimal) d = Convert.ToDouble((decimal)v);
- else if (v is int) d = Convert.ToDouble((int)v);
- else if (v is long) d = Convert.ToDouble((long)v);
- else
- {
- if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
- continue;
- }
- values.Add(d);
- }
- var colHeader = new iTextSharp.text.Paragraph(colInfo.ColumnName, headerFont) { SpacingBefore = 6f, SpacingAfter = 4f };
- doc.Add(colHeader);
- if (values.Count == 0)
- {
- doc.Add(new iTextSharp.text.Paragraph("样本数为 0,无法计算。", normalFont));
- continue;
- }
- // 计算稳定性指标
- StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
- // 将指标写成两列表(属性名 / 值)
- var metricsTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 60f, SpacingAfter = 6f };
- metricsTable.SetWidths(new float[] { 1f, 1f });
- var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
- foreach (var p in props)
- {
- object pv = p.GetValue(metrics);
- string pvStr;
- if (pv == null) pvStr = "";
- else if (pv is double) pvStr = ((double)pv).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
- else pvStr = pv.ToString();
- var pc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(p.Name, normalFont)) { Padding = 4f };
- var vc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(pvStr, normalFont)) { Padding = 4f };
- metricsTable.AddCell(pc);
- metricsTable.AddCell(vc);
- }
- doc.Add(metricsTable);
- }
- catch (Exception exCol)
- {
- LogHelper.WriteLogError($"导出报告时处理列[{colInfo.ColumnName}]出错", exCol);
- doc.Add(new iTextSharp.text.Paragraph($"处理列 {colInfo.ColumnName} 时出错: {exCol.Message}", normalFont));
- }
- }
- }
- else
- {
- doc.Add(new iTextSharp.text.Paragraph("无用于分析的数值列。", normalFont));
- }
- doc.Close();
- writer.Close();
- }
- SendTaskMessage($"已导出测试报告到:{path}");
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogError("导出测试报告时出错", ex);
- SendTaskMessage($"导出测试报告失败:{ex.Message}");
- }
- }
- /// <summary>
- /// 导出测试数据为CSV文件
- /// </summary>
- void ExecuteExportCSVCommand()
- {
- try
- {
- if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
- {
- SendTaskMessage("无测试数据,无法导出。");
- return;
- }
- var dlg = new Microsoft.Win32.SaveFileDialog
- {
- DefaultExt = "csv",
- Filter = "CSV 文件 (*.csv)|*.csv|所有文件 (*.*)|*.*",
- FileName = "TestResult.csv",
- Title = "保存测试结果为 CSV"
- };
- bool? dlgResult = dlg.ShowDialog();
- if (dlgResult != true)
- return;
- string path = dlg.FileName;
- var sb = new System.Text.StringBuilder();
- // 辅助:CSV 字段转义
- Func<string, string> EscapeCsv = (s) =>
- {
- if (s == null) return "";
- bool mustQuote = s.Contains(",") || s.Contains("\"") || s.Contains("\r") || s.Contains("\n");
- string esc = s.Replace("\"", "\"\"");
- return mustQuote ? $"\"{esc}\"" : esc;
- };
- // include device info at top of CSV
- var device = _configService?.GetDeviceInfo();
- if (device != null)
- {
- sb.AppendLine("设备信息");
- sb.AppendLine($"线别,{EscapeCsv(device.Line)}");
- sb.AppendLine($"站别,{EscapeCsv(device.Station)}");
- sb.AppendLine($"机台号,{EscapeCsv(device.FixtureId)}");
- sb.AppendLine();
- }
- // 1. 写入表头
- for (int c = 0; c < TestResult.Columns.Count; c++)
- {
- if (c > 0) sb.Append(",");
- sb.Append(EscapeCsv(TestResult.Columns[c].ColumnName));
- }
- sb.AppendLine();
- // 2. 写入数据行
- foreach (System.Data.DataRow row in TestResult.Rows)
- {
- for (int c = 0; c < TestResult.Columns.Count; c++)
- {
- if (c > 0) sb.Append(",");
- object val = row[c];
- if (val == DBNull.Value || val == null)
- {
- sb.Append("");
- }
- else
- {
- // 保持数值格式,其他转为字符串
- string outStr;
- if (val is double || val is float || val is decimal)
- outStr = Convert.ToString(val, System.Globalization.CultureInfo.InvariantCulture);
- else if (val is int || val is long || val is short || val is byte)
- outStr = val.ToString();
- else if (val is bool)
- outStr = (bool)val ? "True" : "False";
- else
- outStr = val.ToString();
- sb.Append(EscapeCsv(outStr));
- }
- }
- sb.AppendLine();
- }
- // 3. 在末尾追加分析结果
- sb.AppendLine(); // 空行
- sb.AppendLine("分析结果");
- sb.AppendLine("列名,指标,值"); // CSV 表头:列名,指标,值
- if (ResultDataColumns != null)
- {
- foreach (var colInfo in ResultDataColumns)
- {
- try
- {
- List<double> values = new List<double>();
- foreach (System.Data.DataRow row in TestResult.Rows)
- {
- object v = row[colInfo.ColumnIndex];
- if (v != DBNull.Value && v != null)
- {
- double d;
- // 支持不同数字类型
- if (v is double) d = (double)v;
- else if (v is float) d = Convert.ToDouble((float)v);
- else if (v is decimal) d = Convert.ToDouble((decimal)v);
- else if (v is int) d = Convert.ToDouble((int)v);
- else if (v is long) d = Convert.ToDouble((long)v);
- else
- {
- if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
- continue;
- }
- values.Add(d);
- }
- }
- if (values.Count == 0)
- {
- sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("样本数")},{0}");
- continue;
- }
- // 调用稳定性分析
- StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
- // 使用反射枚举 metrics 的公开属性并写入 CSV
- var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
- foreach (var p in props)
- {
- object pv = p.GetValue(metrics);
- string pvStr = pv == null ? "" : (pv is double ? Convert.ToString((double)pv, System.Globalization.CultureInfo.InvariantCulture) : pv.ToString());
- sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv(p.Name)},{EscapeCsv(pvStr)}");
- }
- }
- catch (Exception exCol)
- {
- LogHelper.WriteLogError($"导出分析结果时处理列[{colInfo.ColumnName}]出错", exCol);
- sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("Error")},{EscapeCsv(exCol.Message)}");
- }
- }
- }
- // 写入文件(UTF8,无 BOM,若需 BOM 可使用 new UTF8Encoding(true))
- System.IO.File.WriteAllText(path, sb.ToString(), System.Text.Encoding.UTF8);
- SendTaskMessage($"已导出测试结果到:{path}");
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogError("导出CSV时出错", ex);
- SendTaskMessage($"导出CSV失败:{ex.Message}");
- }
- }
- /// <summary>
- /// 选中列时触发
- /// </summary>
- void ExecuteSelectColumnCommand()
- {
- try
- {
- //如果选中列为空,则直接返回
- if (SelectedDataColumn == null)
- return;
- //获取表的指定列的所有数据集合
- List<double> dataList = new List<double>();
- foreach (DataRow row in TestResult.Rows)
- {
- if (row[SelectedDataColumn.ColumnIndex] != DBNull.Value)
- {
- dataList.Add(Convert.ToDouble(row[SelectedDataColumn.ColumnIndex]));
- }
- }
- //如果数据量小于10,则不进行分析
- if (dataList.Count < 10)
- {
- //MessageQueue.Enqueue("选中列的数据量小于10,无法进行统计分析!");
- return;
- }
- //进行稳定性分析
- StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dataList.ToArray());
- App.Current.Dispatcher.Invoke(() =>
- {
- StabilityMetrics = metrics;
- });
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogError("选中列的数据分析数据稳定性时出错!", ex);
- }
- }
- /// <summary>
- /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
- /// </summary>
- /// <param name="index"></param>
- /// <returns></returns>
- public Task<(bool isSuccess, object[] Result)> ExecutePhotoEx(int index)
- {
- return Task.Run(() =>
- {
- CogToolBlockTerminalCollection outputCollection;
- List<object> result = new List<object>();
- result.Add(index);
- try
- {
- // 1. 采集图像
- bool succed = Camera.SetExposureTime(SelectProcedure.ExposureTime);
- if (!succed)
- {
- SendTaskMessage("相机曝光设置失败");
- }
- succed = Camera.SetGain(SelectProcedure.Gain);
- if (!succed)
- {
- SendTaskMessage("相机增益设置失败");
- }
- DateTime nowtime = DateTime.Now;
- LogHelper.WriteLogInfo("开始采集图像");
- var image = Camera.Grab();
- if (image == null)
- {
- SendTaskMessage(Lang.图像采集失败);
- outputCollection = null;
- return (false, result.ToArray());
- }
- //Image = image;
- VisionTool.Inputs["InputImage"].Value = image;
- LogHelper.WriteLogInfo($"采集图像完成用时{(DateTime.Now - nowtime).Milliseconds}ms");
- // 2. 为ToolBlock传入其他输入终端
- //if (InputTerminal != null)
- //{
- // LogHelper.WriteLogInfo(Lang.为ToolBlock传入输入终端);
- // foreach (var item in InputTerminal)
- // {
- // if (VisionTool.Inputs.Contains(item.Key))
- // {
- // LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
- // VisionTool.Inputs[item.Key].Value = item.Value;
- // }
- // else
- // {
- // LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
- // VisionTool.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
- // }
- // }
- //}
- Image = VisionTool.Inputs["InputImage"].Value as ICogImage;
- // 3. 运行视觉工具
- LogHelper.WriteLogInfo("开始运行视觉工具");
- VisionTool.Run(); //运行ToolBlock
- if (VisionTool.RunStatus.Result == CogToolResultConstants.Accept)
- {
- SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
- outputCollection = VisionTool.Outputs; //获取输出终端集合
- foreach (CogToolBlockTerminal terminal in outputCollection)
- {
- //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
- if (terminal.ValueType == typeof(string))
- {
- //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
- if (terminal.Name == "Point")
- {
- //是否需要转换为绝对坐标
- needConvertToAbsolute = false;
- //如果当前流程的校准为空,则不需要转换为绝对坐标
- if (SelectProcedure.CalibrationId != Guid.Empty)
- {
- var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
- if (calibration != null)
- {
- needConvertToAbsolute = true;
- }
- }
- //判断是否是点位格式
- string strpoints = terminal.Value.ToString();
- string[] items = strpoints.Split(';');
- bool isPointFormat = true;
- foreach (string item in items)
- {
- string[] subitems = item.Split(',');
- if (subitems.Length < 3)
- {
- isPointFormat = false;
- break;
- }
- double x, y, u;
- if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
- {
- isPointFormat = false;
- break;
- }
- }
- if (isPointFormat)
- {
- //是点位格式,则创建多列
- for (int i = 0; i < items.Length; i++)
- {
- string[] subitems = items[i].Split(',');
- //先分别创建像素X、像素Y、角度U三列
- result.Add(double.Parse(subitems[0]));
- result.Add(double.Parse(subitems[1]));
- result.Add(double.Parse(subitems[2]));
- //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
- if (needConvertToAbsolute)
- {
- double[] point = new double[3];
- var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
- var Robot = _robotService.GetRobot(SelectProcedure.RobotId);
- if (Robot != null)
- {
- //获取机器人当前坐标系
- RPoint rPoint = Robot.GetRobotPos();
- point[0] = rPoint.X;
- point[1] = rPoint.Y;
- point[2] = rPoint.U;
- if (calibration.CameraMount == CameraMount.FixedUp || calibration.CameraMount == CameraMount.FixedDown)
- {
- point = null;
- }
- }
- (bool IsSucceed, double X, double Y, double U) = _calibrationService.ConvertPixelToPosition((double.Parse(subitems[0]), double.Parse(subitems[1]), double.Parse(subitems[2])), point, calibration);
- result.Add(Math.Round(X, 5));
- result.Add(Math.Round(Y, 5));
- }
- }
- }
- else
- {
- result.Add(terminal.Value.ToString());
- }
- }
- else
- {
- result.Add(terminal.Value.ToString());
- }
- }
- else if (terminal.ValueType == typeof(int))
- result.Add((int)terminal.Value);
- else if (terminal.ValueType == typeof(double))
- result.Add((double)terminal.Value);
- else if (terminal.ValueType == typeof(bool))
- result.Add((bool)terminal.Value);
- }
- Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
- return (true, result.ToArray());
- }
- else
- {
- SendTaskMessage(Lang.视觉流程执行出错耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
- SendTaskMessage(VisionTool.RunStatus.Message);
- outputCollection = null;
- Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
- return (false, result.ToArray());
- }
- }
- catch (Exception ex)
- {
- SendTaskMessage(ex.Message);
- LogHelper.WriteLogError("执行相机取图并执行视觉工具组时出错", ex);
- outputCollection = null;
- return (false, result.ToArray());
- }
- });
- }
- public void SendTaskMessage(string msg)
- {
- App.Current.Dispatcher.Invoke(() =>
- {
- MessageQueue.Clear();
- MessageQueue.Enqueue(msg);
- });
- }
- bool needConvertToAbsolute = false;
- /// <summary>
- /// 根据VisionTool的输出终端,创建DataTable的列
- /// </summary>
- /// <param name="outputCollection"></param>
- /// <returns></returns>
- private DataTable CreateResultDataTable(CogToolBlockTerminalCollection outputCollection)
- {
- DataTable dt = new DataTable();
- ResultDataColumns = new ObservableCollection<DataColumnInfo>();
- //第一列添加序号
- dt.Columns.Add("序号", typeof(int));
- //列索引
- foreach (CogToolBlockTerminal terminal in outputCollection)
- {
- //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
- if (terminal.ValueType == typeof(string))
- {
- //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
- if (terminal.Name == "Point")
- {
- //是否需要转换为绝对坐标
- needConvertToAbsolute = false;
- //如果当前流程的校准为空,则不需要转换为绝对坐标
- if (SelectProcedure.CalibrationId != Guid.Empty)
- {
- var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
- if (calibration != null)
- {
- needConvertToAbsolute = true;
- }
- }
- //判断是否是点位格式
- string strpoints = terminal.Value.ToString();
- string[] items = strpoints.Split(';');
- bool isPointFormat = true;
- foreach (string item in items)
- {
- string[] subitems = item.Split(',');
- if (subitems.Length < 3)
- {
- isPointFormat = false;
- break;
- }
- double x, y, u;
- if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
- {
- isPointFormat = false;
- break;
- }
- }
- if (isPointFormat)
- {
- //是点位格式,则创建多列
- for (int i = 0; i < items.Length; i++)
- {
- //先分别创建像素X、像素Y、角度U三列
- dt.Columns.Add($"{terminal.Name}_Pixel_X{i + 1}", typeof(double));
- ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_X{i + 1}", dt.Columns.Count - 1));
- dt.Columns.Add($"{terminal.Name}_Pixel_Y{i + 1}", typeof(double));
- ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_Y{i + 1}", dt.Columns.Count - 1));
- dt.Columns.Add($"{terminal.Name}_Angle_U{i + 1}", typeof(double));
- ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Angle_U{i + 1}", dt.Columns.Count - 1));
- //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
- if (needConvertToAbsolute)
- {
- dt.Columns.Add($"{terminal.Name}_Absolute_X{i + 1}", typeof(double));
- ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_X{i + 1}", dt.Columns.Count - 1));
- dt.Columns.Add($"{terminal.Name}_Absolute_Y{i + 1}", typeof(double));
- ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_Y{i + 1}", dt.Columns.Count - 1));
- }
- }
- }
- else
- {
- dt.Columns.Add(terminal.Name, typeof(string));
- }
- }
- else
- {
- dt.Columns.Add(terminal.Name, typeof(string));
- }
- }
- else if (terminal.ValueType == typeof(int))
- {
- dt.Columns.Add(terminal.Name, typeof(int));
- ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
- }
- else if (terminal.ValueType == typeof(double))
- {
- dt.Columns.Add(terminal.Name, typeof(double));
- ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
- }
- else if (terminal.ValueType == typeof(bool))
- {
- dt.Columns.Add(terminal.Name, typeof(bool));
- }
- //else
- // dt.Columns.Add(terminal.Name, typeof(string));
- }
- return dt;
- }
- #endregion
- #region 继承
- public string Title { get; set; } = "视觉静态精度分析仪";
- public event Action<IDialogResult> RequestClose;
- public bool CanCloseDialog()
- {
- return true;
- }
- public void OnDialogClosed()
- {
- //关闭时,停止测试
- _cts?.Cancel();
- }
- public void OnDialogOpened(IDialogParameters parameters)
- {
- SelectProcedure = parameters.GetValue<ProcedureModel>("SelectedProcedure");
- Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
- VisionTool = parameters.GetValue<CogToolBlock>("ToolBlock");
- VisionTool.Run();
- TestResult = CreateResultDataTable(VisionTool.Outputs);
- }
- #endregion
- }
- //辅助类,用于定义DataTable的列,仅用来对于数值列进行统计分析
- public class DataColumnInfo
- {
- public string ColumnName { get; set; }
- //列索引
- public int ColumnIndex { get; set; }
- public DataColumnInfo(string columnName, int columnIndex)
- {
- ColumnName = columnName;
- ColumnIndex = columnIndex;
- }
- }
- }
|