using Cognex.VisionPro;
using Cognex.VisionPro.ToolBlock;
using MaterialDesignThemes.Wpf;
using Newtonsoft.Json;
using OxyPlot;
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.Interfaces;
using TeamAAS_VP.Core;
using TeamAAS_VP.Core.PLCs;
using TeamAAS_VP.Enums;
using TeamAAS_VP.Interfaces;
using TeamAAS_VP.Models;
using TeamAAS_VP.Resources.Languages;
using TeamAAS_VP.Services;
using static TeamAAS_VP.Core.StabilityAnalyzer;
namespace TeamAAS_VP.ViewModels.Product
{
public class VisionDynamicAccuracyAnalyzerViewModel : BindableBase, IDialogAware
{
IRegionManager _regionManager;
IEventAggregator _eventAggregator;
IContainerProvider _container;
IDialogService _dialogService;
IFeederService _feederService;
IRobotService _robotService;
ICameraService _cameraService;
ISystemDatabaseService _systemDatabaseService;
ICalibrationService _calibrationService;
IConfigService _configService;
IPlcService _plcService;
//测试过程中控制取消的CTS
CancellationTokenSource _cts;
string manualToWork = "ns=4;s=DB_DriveControl|CylCtrl[6].Cmd.ManuToWork";
string manualToHome = "ns=4;s=DB_DriveControl|CylCtrl[6].Cmd.ManuToHome";
string inWork = "ns=4;s=DB_DriveControl|CylCtrl[6].Status.InWork";
string inHome = "ns=4;s=DB_DriveControl|CylCtrl[6].Status.InHome";
#region 属性
private ProductModel _SelectProduct;
public ProductModel SelectProduct
{
get { return _SelectProduct; }
set { SetProperty(ref _SelectProduct, value); }
}
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;
///
/// 选中的流程
///
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 IRobot _Robot;
public IRobot Robot
{
get { return _Robot; }
set { SetProperty(ref _Robot, 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;
///
/// 重复次数
///
public int RepeatCount
{
get { return _RepeatCount; }
set { SetProperty(ref _RepeatCount, value); }
}
private int _CurrentProgress;
///
/// 当前进度
///
public int CurrentProgress
{
get { return _CurrentProgress; }
set { SetProperty(ref _CurrentProgress, value); }
}
private int _SuccessCount;
///
/// 成功次数
///
public int SuccessCount
{
get { return _SuccessCount; }
set { SetProperty(ref _SuccessCount, value); }
}
private int _FailCount;
///
/// 失败次数
///
public int FailCount
{
get { return _FailCount; }
set { SetProperty(ref _FailCount, value); }
}
private double _SuccessRate;
///
/// 成功率
///
public double SuccessRate
{
get { return _SuccessRate; }
set { SetProperty(ref _SuccessRate, value); }
}
private ObservableCollection _TestResults;
///
/// 测试结果列表
///
public ObservableCollection TestResults
{
get { return _TestResults; }
set { SetProperty(ref _TestResults, value); }
}
private SnackbarMessageQueue _MessageQueue;
public SnackbarMessageQueue MessageQueue
{
get { return _MessageQueue; }
set { SetProperty(ref _MessageQueue, value); }
}
private RPoint _PointA;
public RPoint PointA
{
get { return _PointA; }
set { SetProperty(ref _PointA, value); }
}
private RPoint _PointB;
public RPoint PointB
{
get { return _PointB; }
set { SetProperty(ref _PointB, value); }
}
private ObservableCollection _ProcedureModels;
///
/// 流程集合
///
public ObservableCollection ProcedureModels
{
get { return _ProcedureModels; }
set { SetProperty(ref _ProcedureModels, value); }
}
private int _MoveSpeed = 100;
///
/// 移动速度
///
public int MoveSpeed
{
get { return _MoveSpeed; }
set { SetProperty(ref _MoveSpeed, value); }
}
private double _CycleTime;
///
/// 单次的时长
///
public double CycleTime
{
get { return _CycleTime; }
set { SetProperty(ref _CycleTime, value); }
}
private PrecisionAnalysisResult _precisionResult = new PrecisionAnalysisResult();
///
/// 精度分析结果
///
public PrecisionAnalysisResult PrecisionResult
{
get { return _precisionResult; }
set { SetProperty(ref _precisionResult, value); }
}
private int _ADelay = 3000;
///
/// A点延时时间
///
public int ADelay
{
get { return _ADelay; }
set { SetProperty(ref _ADelay, value); }
}
private int _BDelay = 3000;
///
/// B点延时时间
///
public int BDelay
{
get { return _BDelay; }
set { SetProperty(ref _BDelay, value); }
}
private bool _IsTakePhoto = true;
///
/// 是否启用拍照
///
public bool IsTakePhoto
{
get { return _IsTakePhoto; }
set { SetProperty(ref _IsTakePhoto, value); }
}
private bool _IsCylCtrl = true;
///
/// 是否启用气缸IsVertical
///
public bool IsCylCtrl
{
get { return _IsCylCtrl; }
set { SetProperty(ref _IsCylCtrl, value); }
}
private bool _IsVertical = true;
///
/// 是否启用垂直精度检测
///
public bool IsVertical
{
get { return _IsVertical; }
set { SetProperty(ref _IsVertical, value); }
}
private float _Distance;
public float Distance
{
get { return _Distance; }
set { SetProperty(ref _Distance, 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).ObservesProperty(() => SelectProcedure).ObservesProperty(() => PointA).ObservesProperty(() => PointB));
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 _TeachPointACommand;
public DelegateCommand TeachPointACommand =>
_TeachPointACommand ?? (_TeachPointACommand = new DelegateCommand(ExecuteTeachPointACommand, () => !IsRunning).ObservesProperty(() => IsRunning));
private DelegateCommand _TeachPointBCommand;
public DelegateCommand TeachPointBCommand =>
_TeachPointBCommand ?? (_TeachPointBCommand = new DelegateCommand(ExecuteTeachPointBCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
private DelegateCommand _ProcedureSelectionChangedCommand;
public DelegateCommand ProcedureSelectionChangedCommand =>
_ProcedureSelectionChangedCommand ?? (_ProcedureSelectionChangedCommand = new DelegateCommand(ExecuteProcedureSelectionChangedCommand, () => !IsRunning).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectProcedure));
private DelegateCommand _SingleTestCommand;
public DelegateCommand SingleTestCommand =>
_SingleTestCommand ?? (_SingleTestCommand = new DelegateCommand(ExecuteSingleTestCommand, () => !IsRunning && SelectProcedure != null && PointA != null && PointB != null).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectProcedure).ObservesProperty(() => PointA).ObservesProperty(() => PointB));
private DelegateCommand _ShowTrendChartCommand;
public DelegateCommand ShowTrendChartCommand =>
_ShowTrendChartCommand ?? (_ShowTrendChartCommand = new DelegateCommand(ExecuteShowTrendChartCommand));
#endregion
#region 事件
#endregion
public VisionDynamicAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService,
IConfigService configService, IPlcService plcService)
{
_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;
_plcService = plcService;
}
#region 方法
///
/// 确定
///
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));
}
///
/// 取消
///
void ExecuteCancelCommand()
{
_cts?.Cancel();
IDialogParameters parameters = new DialogParameters();
//parameters.Add("Tool", Tool);
RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
}
///
/// 单次测试
///
async void ExecuteSingleTestCommand()
{
IsRunning = true;
_cts = new CancellationTokenSource();
CurrentProgress = 0;
SuccessCount = 0;
SuccessRate = 0;
FailCount = 0;
Message = "测试进行中...";
TestResults = new ObservableCollection();
try
{
//弹窗提示是否进行连续测试
var result = System.Windows.MessageBox.Show("确定进行单次测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
if (result != System.Windows.MessageBoxResult.Yes)
{
return;
}
var addressConfig = _configService.GetPlcAddresses();
var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC;
if (Robot == null || !Robot.IsConnected)
{
SendTaskMessage("机器人未连接,无法示教!");
return;
}
if (IsTakePhoto)
{
if (Camera == null || !Camera.IsConnected)
{
SendTaskMessage("相机未连接,无法示教!");
return;
}
}
//为机器人设置移动速度
await Robot.CalibParameAsync(new PickInfo(), MoveSpeed, 0, false, 0, 0);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
return;
}
DateTime startTime = DateTime.Now;
if (!IsCylCtrl)
{
//移动机器人至A点
bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
if (!isSuccess1)
{
SendTaskMessage("机器人移动到A点失败!");
return;
}
await Task.Delay(ADelay);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
return;
}
//移动机器人至B点
isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
if (!isSuccess1)
{
SendTaskMessage("机器人移动到B点失败!");
return;
}
await Task.Delay(BDelay);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
return;
}
}
else
{
//气缸升上去
await plc.WriteNodeAsync(manualToWork, true);
await Task.Delay(BDelay);
await plc.ReadNodeAsync(inWork);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
return;
}
}
if (IsTakePhoto)
{
//相机拍照并处理
(bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY) = await ExecutePhotoEx(CurrentProgress);
CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
if (isSuccess)
{
SuccessCount++;
}
else
{
FailCount++;
}
TestResults.Add(new DynamicTestResult(CurrentProgress, isSuccess, PointB, CycleTime, pixelX, pixelY, angleU, transformX, transformY));
OnTestResultAdded();
CurrentProgress++;
SuccessRate = (double)SuccessCount / CurrentProgress * 100;
}
else
{
CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
SuccessCount++;
CurrentProgress++;
SuccessRate = (double)SuccessCount / CurrentProgress * 100;
}
if (IsCylCtrl)
{
await plc.WriteNodeAsync(manualToHome, true);
await Task.Delay(ADelay);
//inHome 气杆在底部 InWork在上面
await plc.ReadNodeAsync(inHome);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
return;
}
}
Message = "测试完成!";
}
catch (Exception ex)
{
LogHelper.WriteLogError("视觉动态重复测试时出错!", ex);
}
finally
{
IsRunning = false;
}
}
///
/// 连续测试
///
async void ExecuteStartTestCommand()
{
IsRunning = true;
_cts = new CancellationTokenSource();
CurrentProgress = 0;
SuccessCount = 0;
SuccessRate = 0;
FailCount = 0;
Message = "测试进行中...";
int up = 1;
int down = 0;
TestResults = new ObservableCollection();
try
{
//弹窗提示是否进行连续测试
var result = System.Windows.MessageBox.Show("确定进行连续测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
if (result != System.Windows.MessageBoxResult.Yes)
{
return;
}
var addressConfig = _configService.GetPlcAddresses();
var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC;
if (Robot == null || !Robot.IsConnected)
{
SendTaskMessage("机器人未连接,无法示教!");
return;
}
if (IsTakePhoto)
{
if (Camera == null || !Camera.IsConnected)
{
SendTaskMessage("相机未连接,无法示教!");
return;
}
}
//为机器人设置移动速度
await Robot.CalibParameAsync(new PickInfo(), MoveSpeed, 0, false, 0, 0);
while (CurrentProgress < RepeatCount)
{
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
break;
}
DateTime startTime = DateTime.Now;
if (IsVertical)
{
if (CurrentProgress == 0)
{
await Robot.JumpAsync(PointB, null);
}
RPoint verticalPosition = PointB.Clone();
if (CurrentProgress > (RepeatCount / 2))
{
verticalPosition.Z += up++ * Distance;
}
else
{
verticalPosition.Z -= down++ * Distance;
}
//移动机器人至B点
bool isSuccess1 = await Robot.GoAsync(verticalPosition);
if (!isSuccess1)
{
SendTaskMessage("机器人移动到B点失败!");
return;
}
await Task.Delay(BDelay);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
break;
}
}
else if (!IsCylCtrl)
{
IsVertical = false;
//移动机器人至A点
bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
if (!isSuccess1)
{
SendTaskMessage("机器人移动到A点失败!");
return;
}
await Task.Delay(ADelay);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
break;
}
//移动机器人至B点
isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
if (!isSuccess1)
{
SendTaskMessage("机器人移动到B点失败!");
return;
}
await Task.Delay(BDelay);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
break;
}
}
else
{
//气缸升上去
await plc.WriteNodeAsync(manualToWork, true);
await Task.Delay(BDelay);
await plc.ReadNodeAsync(inWork);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
break;
}
}
if (IsTakePhoto)
{
//相机拍照并处理
(bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY) = await ExecutePhotoEx(CurrentProgress);
CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
if (isSuccess)
{
SuccessCount++;
}
else
{
FailCount++;
}
TestResults.Add(new DynamicTestResult(CurrentProgress, isSuccess, PointB, CycleTime, pixelX, pixelY, angleU, transformX, transformY));
OnTestResultAdded();
CurrentProgress++;
SuccessRate = (double)SuccessCount / CurrentProgress * 100;
}
else
{
CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
SuccessCount++;
CurrentProgress++;
SuccessRate = (double)SuccessCount / CurrentProgress * 100;
}
if (IsCylCtrl)
{
await plc.WriteNodeAsync(manualToHome, true);
await Task.Delay(ADelay);
//inHome 气杆在底部 InWork在上面
await plc.ReadNodeAsync(inHome);
if (_cts.Token.IsCancellationRequested)
{
Message = "测试已取消!";
break;
}
}
SendTaskMessage($"测试进行中... {CurrentProgress}/{RepeatCount}");
}
if (CurrentProgress >= RepeatCount)
{
Message = "测试完成!";
}
}
catch (Exception ex)
{
LogHelper.WriteLogError("视觉动态重复测试时出错!", ex);
}
finally
{
IsRunning = false;
}
}
bool CanExecuteStartTestCommand()
{
return !IsRunning && SelectProcedure != null && PointA != null && PointB != null;
}
///
/// 清除数据
///
void ExecuteClearDataCommand()
{
if (TestResults != null)
{
TestResults.Clear();
}
}
///
/// 导出测试报告
/// 使用 iTextSharp 生成 PDF,包含:标题、测试摘要、数据表格、每列稳定性分析结果
/// 详细伪代码:
/// 1. 校验 TestResults 是否存在且有数据,若无则提示并返回。
/// 2. 弹出保存对话框,获取 PDF 保存路径,若取消则返回。
/// 3. 使用 iTextSharp 创建 Document 和 PdfWriter,打开文档流。
/// 4. 尝试从系统字体目录加载中文字体(多候选),若加载失败使用回退字体。
/// 5. 写入标题(居中大号字体)和测试摘要信息(表格格式:导出时间、目标次数、当前进度、成功/失败次数、成功率)。
/// 6. 创建一个 PdfPTable,列为固定的测试结果字段(Index, Timestamp, IsPhotoSuccess, PointB.X/Y/Z, CycleTime, PixelX, PixelY, AngleU, TransformX, TransformY)。
/// - 写入表头(加粗)
/// - 遍历 TestResults,每行按列顺序写入值(数值按 InvariantCulture 格式化,空值写空字符串)
/// 7. 新页,写入“分析结果”标题。
/// 8. 对于需要分析的维度(TransformX, TransformY, AngleU):
/// - 从 TestResults 过滤出 IsPhotoSuccess 为 true 的样本并收集对应维度值(double)
/// - 若样本数为0,写明无法计算;否则调用 StabilityAnalyzer.AnalyzeStability(values) 获取 StabilityMetrics
/// - 将 StabilityMetrics 的公开属性写成两列表(属性名 / 值)写入 PDF
/// 9. 关闭并释放文档、writer、流,提示导出成功;捕获异常记录日志并提示失败。
/// 10. 使用 SendTaskMessage 在 UI 上通知结果。
///
void ExecuteExportReportCommand()
{
try
{
if (TestResults == null || TestResults.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();
// 字体加载,优先中文系统字体
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",
"simsun.ttc,1",
"Microsoft YaHei.ttf",
"msyh.ttc,0",
"msyh.ttc,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)
break;
}
catch
{
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
{
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();
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(System.Globalization.CultureInfo.InvariantCulture));
AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
AddMeta("成功次数", SuccessCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
AddMeta("失败次数", FailCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
AddMeta("成功率(%)", SuccessRate.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
doc.Add(metaTable);
doc.Add(new iTextSharp.text.Paragraph(" ")); // 空行
// 数据表格列定义
var headers = new[]
{
"Index",
"Timestamp",
"IsPhotoSuccess",
"PointB.X",
"PointB.Y",
"PointB.Z",
"CycleTime(s)",
"PixelX",
"PixelY",
"AngleU",
"TransformX",
"TransformY"
};
var table = new iTextSharp.text.pdf.PdfPTable(headers.Length) { WidthPercentage = 100f };
foreach (var h in headers)
{
var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(h, headerFont))
{
HorizontalAlignment = iTextSharp.text.Element.ALIGN_CENTER,
BackgroundColor = new iTextSharp.text.BaseColor(230, 230, 230),
Padding = 4f
};
table.AddCell(cell);
}
// 写入每一行数据
foreach (var r in TestResults)
{
string sIndex = r.Index.ToString(System.Globalization.CultureInfo.InvariantCulture);
string sTime = r.Timestamp.ToString("o", System.Globalization.CultureInfo.InvariantCulture);
string sSucc = r.IsPhotoSuccess ? "True" : "False";
string sPx = "";
string sPy = "";
string sPz = "";
if (r.PointB != null)
{
sPx = Convert.ToString(r.PointB.X, System.Globalization.CultureInfo.InvariantCulture);
sPy = Convert.ToString(r.PointB.Y, System.Globalization.CultureInfo.InvariantCulture);
sPz = Convert.ToString(r.PointB.Z, System.Globalization.CultureInfo.InvariantCulture);
}
string sCycle = Convert.ToString(r.CycleTime, System.Globalization.CultureInfo.InvariantCulture);
string sPixelX = Convert.ToString(r.PixelX, System.Globalization.CultureInfo.InvariantCulture);
string sPixelY = Convert.ToString(r.PixelY, System.Globalization.CultureInfo.InvariantCulture);
string sAngle = Convert.ToString(r.AngleU, System.Globalization.CultureInfo.InvariantCulture);
string sTx = Convert.ToString(r.TransformX, System.Globalization.CultureInfo.InvariantCulture);
string sTy = Convert.ToString(r.TransformY, System.Globalization.CultureInfo.InvariantCulture);
var rowVals = new[] { sIndex, sTime, sSucc, sPx, sPy, sPz, sCycle, sPixelX, sPixelY, sAngle, sTx, sTy };
foreach (var v in rowVals)
{
var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(v ?? "", normalFont)) { Padding = 4f };
table.AddCell(cell);
}
}
doc.Add(table);
// 新页,写入分析结果
doc.NewPage();
var analysisTitle = new iTextSharp.text.Paragraph("分析结果", titleFont) { SpacingAfter = 8f };
doc.Add(analysisTitle);
var dims = new[]
{
new { Name = "TransformX", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformX).ToArray() },
new { Name = "TransformY", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformY).ToArray() },
new { Name = "AngleU", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.AngleU).ToArray() }
};
foreach (var dim in dims)
{
try
{
var header = new iTextSharp.text.Paragraph(dim.Name, headerFont) { SpacingBefore = 6f, SpacingAfter = 4f };
doc.Add(header);
if (dim.Values == null || dim.Values.Length == 0)
{
doc.Add(new iTextSharp.text.Paragraph("样本数为 0,无法计算。", normalFont));
continue;
}
StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dim.Values);
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($"导出报告时处理维度[{dim.Name}]出错", exCol);
doc.Add(new iTextSharp.text.Paragraph($"处理维度 {dim.Name} 时出错: {exCol.Message}", normalFont));
}
}
doc.Close();
writer.Close();
}
SendTaskMessage($"已导出测试报告到:{path}");
}
catch (Exception ex)
{
LogHelper.WriteLogError("导出测试报告时出错", ex);
SendTaskMessage($"导出测试报告失败:{ex.Message}");
}
}
///
/// 导出测试数据为CSV文件
///
void ExecuteExportCSVCommand()
{
try
{
if (TestResults == null || TestResults.Count == 0)
{
SendTaskMessage("无测试数据,无法导出。");
return;
}
var dlg = new Microsoft.Win32.SaveFileDialog
{
DefaultExt = "csv",
Filter = "CSV 文件 (*.csv)|*.csv|所有文件 (*.*)|*.*",
FileName = "TestResults.csv",
Title = "保存测试结果为 CSV"
};
bool? dlgResult = dlg.ShowDialog();
if (dlgResult != true)
return;
string path = dlg.FileName;
var sb = new System.Text.StringBuilder();
// 辅助:CSV 字段转义
Func 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() ?? _configService?.GetDeviceInfo();
if (device != null)
{
sb.AppendLine("设备信息");
sb.AppendLine($"线别,{EscapeCsv(device.Line)}");
sb.AppendLine($"站别,{EscapeCsv(device.Station)}");
sb.AppendLine($"机台号,{EscapeCsv(device.FixtureId)}");
sb.AppendLine();
}
// 写入表头
var headers = new[]
{
"Index",
"Timestamp",
"IsPhotoSuccess",
"PointB.X",
"PointB.Y",
"PointB.Z",
"CycleTime",
"PixelX",
"PixelY",
"AngleU",
"TransformX",
"TransformY"
};
sb.AppendLine(string.Join(",", headers.Select(h => EscapeCsv(h))));
// 写入数据行
foreach (var r in TestResults)
{
var cols = new List();
cols.Add(EscapeCsv(r.Index.ToString()));
cols.Add(EscapeCsv(r.Timestamp.ToString("o"))); // ISO 8601 格式
cols.Add(EscapeCsv(r.IsPhotoSuccess ? "True" : "False"));
if (r.PointB != null)
{
// 假设 RPoint 有 X,Y,Z 属性(若命名不同需调整)
cols.Add(EscapeCsv(Convert.ToString(r.PointB.X, System.Globalization.CultureInfo.InvariantCulture)));
cols.Add(EscapeCsv(Convert.ToString(r.PointB.Y, System.Globalization.CultureInfo.InvariantCulture)));
cols.Add(EscapeCsv(Convert.ToString(r.PointB.Z, System.Globalization.CultureInfo.InvariantCulture)));
}
else
{
cols.Add("");
cols.Add("");
cols.Add("");
}
cols.Add(EscapeCsv(Convert.ToString(r.CycleTime, System.Globalization.CultureInfo.InvariantCulture)));
cols.Add(EscapeCsv(Convert.ToString(r.PixelX, System.Globalization.CultureInfo.InvariantCulture)));
cols.Add(EscapeCsv(Convert.ToString(r.PixelY, System.Globalization.CultureInfo.InvariantCulture)));
cols.Add(EscapeCsv(Convert.ToString(r.AngleU, System.Globalization.CultureInfo.InvariantCulture)));
cols.Add(EscapeCsv(Convert.ToString(r.TransformX, System.Globalization.CultureInfo.InvariantCulture)));
cols.Add(EscapeCsv(Convert.ToString(r.TransformY, System.Globalization.CultureInfo.InvariantCulture)));
sb.AppendLine(string.Join(",", cols));
}
// 在末尾追加分析结果
sb.AppendLine();
sb.AppendLine("分析结果");
sb.AppendLine("维度,指标,值");
// 要分析的维度:TransformX, TransformY, AngleU
var dims = new[]
{
new { Name = "TransformX", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformX).ToArray() },
new { Name = "TransformY", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformY).ToArray() },
new { Name = "AngleU", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.AngleU).ToArray() }
};
foreach (var dim in dims)
{
try
{
if (dim.Values == null || dim.Values.Length == 0)
{
sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv("样本数")},{EscapeCsv("0")}");
continue;
}
// 调用稳定性分析
StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dim.Values);
// 反射写入公开属性
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 = Convert.ToString((double)pv, System.Globalization.CultureInfo.InvariantCulture);
else pvStr = pv.ToString();
sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv(p.Name)},{EscapeCsv(pvStr)}");
}
}
catch (Exception exCol)
{
LogHelper.WriteLogError($"导出分析结果时处理维度[{dim.Name}]出错", exCol);
sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv("Error")},{EscapeCsv(exCol.Message)}");
}
}
// 写入文件(UTF8,无 BOM)
System.IO.File.WriteAllText(path, sb.ToString(), System.Text.Encoding.UTF8);
SendTaskMessage($"已导出测试结果到:{path}");
}
catch (Exception ex)
{
LogHelper.WriteLogError("导出CSV时出错", ex);
SendTaskMessage($"导出CSV失败:{ex.Message}");
}
}
///
/// 选中视觉流程改变时
///
void ExecuteProcedureSelectionChangedCommand()
{
if (SelectProcedure == null)
{
return;
}
//获取相机
Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
VisionTool = SelectProcedure.ToolBlock;
}
///
/// 示教点B
///
void ExecuteTeachPointBCommand()
{
if (Robot == null || !Robot.IsConnected)
{
SendTaskMessage("机器人未连接,无法示教!");
return;
}
try
{
var position = Robot.GetRobotPos();
if (position != null)
{
PointB = position.Clone();
SendTaskMessage($"已示教点B:X={PointB.X:F2} Y={PointB.Y:F2} Z={PointB.Z:F2}");
}
else
{
SendTaskMessage("获取机器人当前位置失败,无法示教点B!");
}
}
catch (Exception ex)
{
LogHelper.WriteLogError("示教点B时出错!", ex);
SendTaskMessage($"示教点B时出错:{ex.Message}");
}
}
///
/// 示教点A
///
void ExecuteTeachPointACommand()
{
if (Robot == null || !Robot.IsConnected)
{
SendTaskMessage("机器人未连接,无法示教!");
return;
}
try
{
var position = Robot.GetRobotPos();
if (position != null)
{
PointA = position.Clone();
SendTaskMessage($"已示教点A:X={PointA.X:F2} Y={PointA.Y:F2} Z={PointA.Z:F2}");
}
else
{
SendTaskMessage("获取机器人当前位置失败,无法示教点A!");
}
}
catch (Exception ex)
{
LogHelper.WriteLogError("示教点A时出错!", ex);
SendTaskMessage($"示教点A时出错:{ex.Message}");
}
}
///
/// 显示趋势图
///
void ExecuteShowTrendChartCommand()
{
}
///
/// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
///
///
///
public Task<(bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY)> ExecutePhotoEx(int index)
{
return Task.Run(() =>
{
CogToolBlockTerminalCollection outputCollection;
List