|
|
@@ -0,0 +1,1890 @@
|
|
|
+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.Enums;
|
|
|
+using TeamAAS_VP.Interfaces;
|
|
|
+using TeamAAS_VP.Models;
|
|
|
+using TeamAAS_VP.Resources.Languages;
|
|
|
+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;
|
|
|
+
|
|
|
+ //测试过程中控制取消的CTS
|
|
|
+ CancellationTokenSource _cts;
|
|
|
+
|
|
|
+ #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;
|
|
|
+
|
|
|
+ /// <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 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;
|
|
|
+ /// <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 ObservableCollection<DynamicTestResult> _TestResults;
|
|
|
+ /// <summary>
|
|
|
+ /// 测试结果列表
|
|
|
+ /// </summary>
|
|
|
+ public ObservableCollection<DynamicTestResult> 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<ProcedureModel> _ProcedureModels;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 流程集合
|
|
|
+ /// </summary>
|
|
|
+ public ObservableCollection<ProcedureModel> ProcedureModels
|
|
|
+ {
|
|
|
+ get { return _ProcedureModels; }
|
|
|
+ set { SetProperty(ref _ProcedureModels, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int _MoveSpeed=100;
|
|
|
+ /// <summary>
|
|
|
+ /// 移动速度
|
|
|
+ /// </summary>
|
|
|
+ public int MoveSpeed
|
|
|
+ {
|
|
|
+ get { return _MoveSpeed; }
|
|
|
+ set { SetProperty(ref _MoveSpeed, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _CycleTime;
|
|
|
+ /// <summary>
|
|
|
+ /// 单次的时长
|
|
|
+ /// </summary>
|
|
|
+ public double CycleTime
|
|
|
+ {
|
|
|
+ get { return _CycleTime; }
|
|
|
+ set { SetProperty(ref _CycleTime, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private PrecisionAnalysisResult _precisionResult = new PrecisionAnalysisResult();
|
|
|
+ /// <summary>
|
|
|
+ /// 精度分析结果
|
|
|
+ /// </summary>
|
|
|
+ public PrecisionAnalysisResult PrecisionResult
|
|
|
+ {
|
|
|
+ get { return _precisionResult; }
|
|
|
+ set { SetProperty(ref _precisionResult, 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));
|
|
|
+
|
|
|
+ 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)
|
|
|
+ {
|
|
|
+ _regionManager = regionManager;
|
|
|
+ _eventAggregator = ea;
|
|
|
+ _container = container;
|
|
|
+ _dialogService = dialogService;
|
|
|
+ _feederService = feederService;
|
|
|
+ _robotService = robotService;
|
|
|
+ _cameraService = cameraService;
|
|
|
+ _systemDatabaseService = systemDatabaseService;
|
|
|
+ MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
|
|
|
+ _calibrationService = calibrationService;
|
|
|
+ }
|
|
|
+
|
|
|
+ #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 ExecuteSingleTestCommand()
|
|
|
+ {
|
|
|
+ if (Robot == null || !Robot.IsConnected)
|
|
|
+ {
|
|
|
+ SendTaskMessage("机器人未连接,无法示教!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Camera == null || !Camera.IsConnected)
|
|
|
+ {
|
|
|
+ SendTaskMessage("相机未连接,无法示教!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //弹窗提示是否进行单次测试
|
|
|
+ var result = System.Windows.MessageBox.Show("确定进行单次测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
|
|
|
+ if (result != System.Windows.MessageBoxResult.Yes)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ IsRunning = true;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ DateTime startTime = DateTime.Now;
|
|
|
+ //为机器人设置移动速度
|
|
|
+ await Robot.CalibParameAsync(new PickInfo(), MoveSpeed,0,false,0,0);
|
|
|
+
|
|
|
+ //移动机器人至A点
|
|
|
+ bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
|
|
|
+ if (!isSuccess1)
|
|
|
+ {
|
|
|
+ SendTaskMessage("机器人移动到A点失败!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await Task.Delay(200);
|
|
|
+
|
|
|
+ //移动机器人至B点
|
|
|
+ isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
|
|
|
+ if (!isSuccess1)
|
|
|
+ {
|
|
|
+ SendTaskMessage("机器人移动到B点失败!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await Task.Delay(300);
|
|
|
+ //相机拍照并处理
|
|
|
+ (bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY) = await ExecutePhotoEx(CurrentProgress);
|
|
|
+
|
|
|
+ CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ LogHelper.WriteLogError("机器人移动到A点失败!", ex);
|
|
|
+ SendTaskMessage("机器人移动到A点失败!");
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ IsRunning = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 连续测试
|
|
|
+ /// </summary>
|
|
|
+ async void ExecuteStartTestCommand()
|
|
|
+ {
|
|
|
+ IsRunning = true;
|
|
|
+ _cts = new CancellationTokenSource();
|
|
|
+ CurrentProgress = 0;
|
|
|
+ SuccessCount = 0;
|
|
|
+ SuccessRate = 0;
|
|
|
+ FailCount = 0;
|
|
|
+ Message = "测试进行中...";
|
|
|
+ TestResults = new ObservableCollection<DynamicTestResult>();
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ //弹窗提示是否进行连续测试
|
|
|
+ var result = System.Windows.MessageBox.Show("确定进行连续测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
|
|
|
+ if (result != System.Windows.MessageBoxResult.Yes)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //为机器人设置移动速度
|
|
|
+ await Robot.CalibParameAsync(new PickInfo(), MoveSpeed, 0, false, 0, 0);
|
|
|
+
|
|
|
+ while (CurrentProgress < RepeatCount)
|
|
|
+ {
|
|
|
+ if (_cts.Token.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ Message = "测试已取消!";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ DateTime startTime = DateTime.Now;
|
|
|
+
|
|
|
+ //移动机器人至A点
|
|
|
+ bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
|
|
|
+ if (!isSuccess1)
|
|
|
+ {
|
|
|
+ SendTaskMessage("机器人移动到A点失败!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await Task.Delay(200);
|
|
|
+ if (_cts.Token.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ Message = "测试已取消!";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ //移动机器人至B点
|
|
|
+ isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
|
|
|
+ if (!isSuccess1)
|
|
|
+ {
|
|
|
+ SendTaskMessage("机器人移动到B点失败!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await Task.Delay(300);
|
|
|
+ if (_cts.Token.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ Message = "测试已取消!";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ //相机拍照并处理
|
|
|
+ (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;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 清除数据
|
|
|
+ /// </summary>
|
|
|
+ void ExecuteClearDataCommand()
|
|
|
+ {
|
|
|
+ if (TestResults != null)
|
|
|
+ {
|
|
|
+ TestResults.Clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 导出测试报告
|
|
|
+ /// 使用 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 上通知结果。
|
|
|
+ /// </summary>
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 导出测试数据为CSV文件
|
|
|
+ /// </summary>
|
|
|
+ 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<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;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 写入表头
|
|
|
+ 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<string>();
|
|
|
+
|
|
|
+ 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}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 选中视觉流程改变时
|
|
|
+ /// </summary>
|
|
|
+ void ExecuteProcedureSelectionChangedCommand()
|
|
|
+ {
|
|
|
+ if (SelectProcedure == null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取相机
|
|
|
+ Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
|
|
|
+ VisionTool = SelectProcedure.ToolBlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 示教点B
|
|
|
+ /// </summary>
|
|
|
+ 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}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 示教点A
|
|
|
+ /// </summary>
|
|
|
+ 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}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 显示趋势图
|
|
|
+ /// </summary>
|
|
|
+ void ExecuteShowTrendChartCommand()
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="index"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public Task<(bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY)> 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, 0d, 0d, 0d, 0d, 0d);
|
|
|
+ }
|
|
|
+ //Image = image;
|
|
|
+ VisionTool.Inputs["InputImage"].Value = image;
|
|
|
+ //LogHelper.WriteLogInfo(string.Format(Lang.采集图像完成用时0ms, (DateTime.Now - nowtime).Milliseconds));
|
|
|
+
|
|
|
+ // 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; //获取输出终端集合
|
|
|
+ Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
|
|
|
+ if (!outputCollection.Contains("Found"))
|
|
|
+ {
|
|
|
+ SendTaskMessage(Lang.未找到视觉输出结果);
|
|
|
+ return (false, 0d, 0d, 0d, 0d, 0d);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取视觉输出结果
|
|
|
+ bool Found = (bool)(outputCollection["Found"].Value);
|
|
|
+ if (!Found)
|
|
|
+ {
|
|
|
+ SendTaskMessage(Lang.拍照NG);
|
|
|
+ return (false, 0d, 0d, 0d, 0d, 0d);
|
|
|
+ }
|
|
|
+
|
|
|
+ //SendTaskMessage(Lang.拍照OK, MessageLevel.Debug);
|
|
|
+
|
|
|
+ // 3. 获取视觉输出结果
|
|
|
+
|
|
|
+ string points = (string)(outputCollection["Point"].Value);
|
|
|
+ string[] strpoints = points.Split(';');
|
|
|
+ //先将pos按照逗号进行分隔,拿到数组
|
|
|
+ var posParts = strpoints[0].Split(',');
|
|
|
+ double _pixel_x = double.Parse(posParts[0]);
|
|
|
+ double _pixel_y = double.Parse(posParts[1]);
|
|
|
+ double _pixel_u = double.Parse(posParts[2]);
|
|
|
+
|
|
|
+ //获取相机校准
|
|
|
+ var calib = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
|
|
|
+ if (calib == null)
|
|
|
+ {
|
|
|
+ return (true, _pixel_x, _pixel_y, _pixel_u, 0, 0); ;
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取机器人当前坐标系
|
|
|
+ RPoint rPoint = Robot.GetRobotPos();
|
|
|
+ double[] point = new double[3];
|
|
|
+ point[0] = rPoint.X;
|
|
|
+ point[1] = rPoint.Y;
|
|
|
+ point[2] = rPoint.U;
|
|
|
+ if (calib.CameraMount == CameraMount.FixedUp || calib.CameraMount == CameraMount.FixedDown)
|
|
|
+ {
|
|
|
+ point = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ //转换点位-像素转换成机器人绝对坐标
|
|
|
+ var calibResult = _calibrationService.ConvertPixelToPosition((_pixel_x, _pixel_y, _pixel_u), point, calib, RobotBrand.XYZ_Platform);
|
|
|
+ if (!calibResult.IsSucceed)
|
|
|
+ {
|
|
|
+ return (false, _pixel_x, _pixel_y, _pixel_u, 0d, 0d);
|
|
|
+ }
|
|
|
+ return (true, _pixel_x, _pixel_y, _pixel_u, calibResult.X, calibResult.Y); ;
|
|
|
+ }
|
|
|
+ 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, 0d, 0d, 0d, 0d, 0d);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ SendTaskMessage(ex.Message);
|
|
|
+ LogHelper.WriteLogError("执行相机取图并执行视觉工具组时出错", ex);
|
|
|
+ outputCollection = null;
|
|
|
+ return (false, 0d, 0d, 0d, 0d, 0d);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SendTaskMessage(string msg)
|
|
|
+ {
|
|
|
+ App.Current.Dispatcher.Invoke(() =>
|
|
|
+ {
|
|
|
+ MessageQueue.Clear();
|
|
|
+ MessageQueue.Enqueue(msg);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 规格限制(根据实际情况设置)
|
|
|
+ private double _xLSL = -0.02; // X下规格限
|
|
|
+ private double _xUSL = 0.02; // X上规格限
|
|
|
+ private double _yLSL = -0.02; // Y下规格限
|
|
|
+ private double _yUSL = 0.02; // Y上规格限
|
|
|
+ private double _angleLSL = -1.0; // 角度下规格限
|
|
|
+ private double _angleUSL = 1.0; // 角度上规格限
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算所有精度指标
|
|
|
+ /// </summary>
|
|
|
+ public void CalculatePrecisionMetrics()
|
|
|
+ {
|
|
|
+ if (TestResults == null || TestResults.Count < 2)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // 过滤成功的测试结果
|
|
|
+ var successResults = TestResults.Where(r => r.IsPhotoSuccess).ToList();
|
|
|
+ if (successResults.Count < 2)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // 提取数据
|
|
|
+ var transformXs = successResults.Select(r => r.TransformX).ToList();
|
|
|
+ var transformYs = successResults.Select(r => r.TransformY).ToList();
|
|
|
+ var angleUs = successResults.Select(r => r.AngleU).ToList();
|
|
|
+
|
|
|
+ // 计算X方向指标
|
|
|
+ var xMetrics = CalculateMetrics(transformXs, _xLSL, _xUSL);
|
|
|
+ PrecisionResult.XMean = xMetrics.Mean;
|
|
|
+ PrecisionResult.XPrecision = xMetrics.StdDeviation;
|
|
|
+ PrecisionResult.XThreeSigmaRange = xMetrics.ThreeSigmaRange;
|
|
|
+ PrecisionResult.XRange = xMetrics.Range;
|
|
|
+ PrecisionResult.XKurtosis = xMetrics.Kurtosis;
|
|
|
+ PrecisionResult.XCpk = xMetrics.Cpk;
|
|
|
+
|
|
|
+ // 计算Y方向指标
|
|
|
+ var yMetrics = CalculateMetrics(transformYs, _yLSL, _yUSL);
|
|
|
+ PrecisionResult.YMean = yMetrics.Mean;
|
|
|
+ PrecisionResult.YPrecision = yMetrics.StdDeviation;
|
|
|
+ PrecisionResult.YThreeSigmaRange = yMetrics.ThreeSigmaRange;
|
|
|
+ PrecisionResult.YRange = yMetrics.Range;
|
|
|
+ PrecisionResult.YKurtosis = yMetrics.Kurtosis;
|
|
|
+ PrecisionResult.YCpk = yMetrics.Cpk;
|
|
|
+
|
|
|
+ // 计算角度指标
|
|
|
+ var angleMetrics = CalculateMetrics(angleUs, _angleLSL, _angleUSL);
|
|
|
+ PrecisionResult.AngleMean = angleMetrics.Mean;
|
|
|
+ PrecisionResult.AnglePrecision = angleMetrics.StdDeviation;
|
|
|
+ PrecisionResult.AngleThreeSigmaRange = angleMetrics.ThreeSigmaRange;
|
|
|
+ PrecisionResult.AngleRange = angleMetrics.Range;
|
|
|
+ PrecisionResult.AngleCpk = angleMetrics.Cpk;
|
|
|
+
|
|
|
+ // 计算综合精度
|
|
|
+ PrecisionResult.OverallPrecision = Math.Sqrt(
|
|
|
+ Math.Pow(PrecisionResult.XPrecision, 2) +
|
|
|
+ Math.Pow(PrecisionResult.YPrecision, 2));
|
|
|
+ PrecisionResult.OverallThreeSigmaRange = PrecisionResult.OverallPrecision * 3;
|
|
|
+
|
|
|
+ // 使用最差的CPK作为整体评级
|
|
|
+ var minCpk = Math.Min(Math.Min(PrecisionResult.XCpk, PrecisionResult.YCpk), PrecisionResult.AngleCpk);
|
|
|
+ PrecisionResult.ProcessRating = PrecisionResult.GetCpkRating(minCpk);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算单个维度的所有指标
|
|
|
+ /// </summary>
|
|
|
+ private DimensionMetrics CalculateMetrics(List<double> values, double lsl, double usl)
|
|
|
+ {
|
|
|
+ if (values == null || values.Count < 2)
|
|
|
+ return new DimensionMetrics();
|
|
|
+
|
|
|
+ var metrics = new DimensionMetrics
|
|
|
+ {
|
|
|
+ Mean = values.Average(),
|
|
|
+ StdDeviation = CalculateStandardDeviation(values),
|
|
|
+ Range = values.Max() - values.Min(),
|
|
|
+ Kurtosis = CalculateKurtosis(values)
|
|
|
+ };
|
|
|
+
|
|
|
+ metrics.ThreeSigmaRange = metrics.StdDeviation * 3;
|
|
|
+ metrics.Cpk = CalculateCpk(values, lsl, usl);
|
|
|
+
|
|
|
+ return metrics;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算标准差
|
|
|
+ /// </summary>
|
|
|
+ private double CalculateStandardDeviation(List<double> values)
|
|
|
+ {
|
|
|
+ if (values.Count < 2)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ var mean = values.Average();
|
|
|
+ var sumSq = values.Sum(v => Math.Pow(v - mean, 2));
|
|
|
+ return Math.Sqrt(sumSq / (values.Count - 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算峰度
|
|
|
+ /// </summary>
|
|
|
+ private double CalculateKurtosis(List<double> values)
|
|
|
+ {
|
|
|
+ if (values.Count < 4)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ var mean = values.Average();
|
|
|
+ var n = values.Count;
|
|
|
+ var sum4 = values.Sum(v => Math.Pow(v - mean, 4));
|
|
|
+ var sum2 = values.Sum(v => Math.Pow(v - mean, 2));
|
|
|
+
|
|
|
+ if (sum2 == 0)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ return (n * sum4) / Math.Pow(sum2, 2) - 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算CPK
|
|
|
+ /// </summary>
|
|
|
+ private double CalculateCpk(List<double> values, double lsl, double usl)
|
|
|
+ {
|
|
|
+ if (values.Count < 2 || usl <= lsl)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ var stdDev = CalculateStandardDeviation(values);
|
|
|
+ if (stdDev == 0)
|
|
|
+ return double.MaxValue;
|
|
|
+
|
|
|
+ var mean = values.Average();
|
|
|
+ var cpu = (usl - mean) / (3 * stdDev);
|
|
|
+ var cpl = (mean - lsl) / (3 * stdDev);
|
|
|
+
|
|
|
+ return Math.Min(cpu, cpl);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 单个维度的指标
|
|
|
+ /// </summary>
|
|
|
+ private class DimensionMetrics
|
|
|
+ {
|
|
|
+ public double Mean { get; set; }
|
|
|
+ public double StdDeviation { get; set; }
|
|
|
+ public double ThreeSigmaRange { get; set; }
|
|
|
+ public double Range { get; set; }
|
|
|
+ public double Kurtosis { get; set; }
|
|
|
+ public double Cpk { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 在添加测试结果后自动更新精度分析
|
|
|
+ /// </summary>
|
|
|
+ private void OnTestResultAdded()
|
|
|
+ {
|
|
|
+ CalculatePrecisionMetrics();
|
|
|
+ RaisePropertyChanged(nameof(PrecisionResult));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 设置规格限制
|
|
|
+ /// </summary>
|
|
|
+ public void SetSpecificationLimits(double xLSL, double xUSL, double yLSL, double yUSL, double angleLSL, double angleUSL)
|
|
|
+ {
|
|
|
+ _xLSL = xLSL;
|
|
|
+ _xUSL = xUSL;
|
|
|
+ _yLSL = yLSL;
|
|
|
+ _yUSL = yUSL;
|
|
|
+ _angleLSL = angleLSL;
|
|
|
+ _angleUSL = angleUSL;
|
|
|
+
|
|
|
+ // 重新计算精度指标
|
|
|
+ CalculatePrecisionMetrics();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 基于3σ自动设置规格限制
|
|
|
+ /// </summary>
|
|
|
+ public void AutoSetSpecificationLimits()
|
|
|
+ {
|
|
|
+ if (TestResults == null || TestResults.Count < 2)
|
|
|
+ return;
|
|
|
+
|
|
|
+ var successResults = TestResults.Where(r => r.IsPhotoSuccess).ToList();
|
|
|
+ if (successResults.Count < 2)
|
|
|
+ return;
|
|
|
+
|
|
|
+ var transformXs = successResults.Select(r => r.TransformX).ToList();
|
|
|
+ var transformYs = successResults.Select(r => r.TransformY).ToList();
|
|
|
+ var angleUs = successResults.Select(r => r.AngleU).ToList();
|
|
|
+
|
|
|
+ var xStd = CalculateStandardDeviation(transformXs);
|
|
|
+ var yStd = CalculateStandardDeviation(transformYs);
|
|
|
+ var angleStd = CalculateStandardDeviation(angleUs);
|
|
|
+
|
|
|
+ var xMean = transformXs.Average();
|
|
|
+ var yMean = transformYs.Average();
|
|
|
+ var angleMean = angleUs.Average();
|
|
|
+
|
|
|
+ // 使用平均值±3σ作为自动规格限
|
|
|
+ SetSpecificationLimits(
|
|
|
+ xLSL: xMean - 3 * xStd,
|
|
|
+ xUSL: xMean + 3 * xStd,
|
|
|
+ yLSL: yMean - 3 * yStd,
|
|
|
+ yUSL: yMean + 3 * yStd,
|
|
|
+ angleLSL: angleMean - 3 * angleStd,
|
|
|
+ angleUSL: angleMean + 3 * angleStd
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region 继承
|
|
|
+ public string Title { get; set; } = "视觉静态精度分析仪";
|
|
|
+
|
|
|
+ public event Action<IDialogResult> RequestClose;
|
|
|
+
|
|
|
+ public bool CanCloseDialog()
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void OnDialogClosed()
|
|
|
+ {
|
|
|
+ _cts?.Cancel();
|
|
|
+ SaveConfig();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void OnDialogOpened(IDialogParameters parameters)
|
|
|
+ {
|
|
|
+ SelectProduct = parameters.GetValue<ProductModel>("SelectProduct");
|
|
|
+ Robot = parameters.GetValue<IRobot>("Robot");
|
|
|
+ ProcedureModels = new ObservableCollection<ProcedureModel>();
|
|
|
+ foreach (var item in SelectProduct.CameraProcedures)
|
|
|
+ {
|
|
|
+ foreach (var item1 in item.ProcedureModels)
|
|
|
+ {
|
|
|
+ ProcedureModels.Add(item1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ LoadConfig();
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 加载配置
|
|
|
+ /// </summary>
|
|
|
+ private void LoadConfig()
|
|
|
+ {
|
|
|
+ //配置文件路径:FilePath.ProductsPath//productName//VisionDynamicAccuracyAnalyzerConfig.json
|
|
|
+ string configPath = System.IO.Path.Combine(FilePath.ProductsPath, SelectProduct.Name, "VisionDynamicAccuracyAnalyzerConfig.json");
|
|
|
+ if (System.IO.File.Exists(configPath))
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ string json = System.IO.File.ReadAllText(configPath);
|
|
|
+ var ConfigModel = JsonConvert.DeserializeObject<VisionDynamicAccuracyAnalyzerConfigModel>(json);
|
|
|
+ //加载点位A、B
|
|
|
+ PointA = ConfigModel.PointA;
|
|
|
+ PointB = ConfigModel.PointB;
|
|
|
+ //加载选中视觉流程
|
|
|
+ var procedure = ProcedureModels.FirstOrDefault(p => p.Id == ConfigModel.SelectProcedureId);
|
|
|
+ if (procedure != null)
|
|
|
+ {
|
|
|
+ SelectProcedure = procedure;
|
|
|
+ }
|
|
|
+ MoveSpeed = ConfigModel.RobotMoveSpeed;
|
|
|
+ RepeatCount = ConfigModel.TestCount;
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ LogHelper.WriteLogError("加载视觉动态精度分析仪配置时出错", ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 保存配置
|
|
|
+ /// </summary>
|
|
|
+ private void SaveConfig()
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ //配置文件路径:FilePath.ProductsPath//productName//VisionDynamicAccuracyAnalyzerConfig.json
|
|
|
+ string configPath = System.IO.Path.Combine(FilePath.ProductsPath, SelectProduct.Name, "VisionDynamicAccuracyAnalyzerConfig.json");
|
|
|
+ var ConfigModel = new VisionDynamicAccuracyAnalyzerConfigModel
|
|
|
+ {
|
|
|
+ PointA = PointA,
|
|
|
+ PointB = PointB,
|
|
|
+ SelectProcedureId = SelectProcedure?.Id ?? Guid.Empty,
|
|
|
+ SelectProcedureName = SelectProcedure?.Name ?? "",
|
|
|
+ RobotMoveSpeed = MoveSpeed,
|
|
|
+ TestCount = RepeatCount
|
|
|
+ };
|
|
|
+ //如果配置文件夹不存在则创建
|
|
|
+ var configDir = System.IO.Path.GetDirectoryName(configPath);
|
|
|
+ if (!Directory.Exists(configDir))
|
|
|
+ {
|
|
|
+ Directory.CreateDirectory(configDir);
|
|
|
+ }
|
|
|
+
|
|
|
+ string json = JsonConvert.SerializeObject(ConfigModel, Formatting.Indented);
|
|
|
+ System.IO.File.WriteAllText(configPath, json);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ LogHelper.WriteLogError("保存视觉动态精度分析仪配置时出错", ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //配置参数模型
|
|
|
+ public class VisionDynamicAccuracyAnalyzerConfigModel : BindableBase
|
|
|
+ {
|
|
|
+ private int _RobotMoveSpeed = 100;
|
|
|
+ /// <summary>
|
|
|
+ /// 机器人移动速度
|
|
|
+ /// </summary>
|
|
|
+ public int RobotMoveSpeed
|
|
|
+ {
|
|
|
+ get { return _RobotMoveSpeed; }
|
|
|
+ set { SetProperty(ref _RobotMoveSpeed, value); }
|
|
|
+ }
|
|
|
+ private int _TestCount = 10;
|
|
|
+ /// <summary>
|
|
|
+ /// 测试次数
|
|
|
+ /// </summary>
|
|
|
+ public int TestCount
|
|
|
+ {
|
|
|
+ get { return _TestCount; }
|
|
|
+ set { SetProperty(ref _TestCount, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ //点位A
|
|
|
+ private RPoint _PointA;
|
|
|
+ /// <summary>
|
|
|
+ /// 点位A
|
|
|
+ /// </summary>
|
|
|
+ public RPoint PointA
|
|
|
+ {
|
|
|
+ get { return _PointA; }
|
|
|
+ set { SetProperty(ref _PointA, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ //点位B
|
|
|
+ private RPoint _PointB;
|
|
|
+ /// <summary>
|
|
|
+ /// 点位B
|
|
|
+ /// </summary>
|
|
|
+ public RPoint PointB
|
|
|
+ {
|
|
|
+ get { return _PointB; }
|
|
|
+ set { SetProperty(ref _PointB, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Guid _SelectProcedureId;
|
|
|
+ /// <summary>
|
|
|
+ /// 选中的视觉流程ID
|
|
|
+ /// </summary>
|
|
|
+ public Guid SelectProcedureId
|
|
|
+ {
|
|
|
+ get { return _SelectProcedureId; }
|
|
|
+ set { SetProperty(ref _SelectProcedureId, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private string _SelectProcedureName;
|
|
|
+ /// <summary>
|
|
|
+ /// 选中的视觉流程Name
|
|
|
+ /// </summary>
|
|
|
+ public string SelectProcedureName
|
|
|
+ {
|
|
|
+ get { return _SelectProcedureName; }
|
|
|
+ set { SetProperty(ref _SelectProcedureName, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public class DynamicTestResult : BindableBase
|
|
|
+ {
|
|
|
+ private int _Index;
|
|
|
+ /// <summary>
|
|
|
+ /// 序号
|
|
|
+ /// </summary>
|
|
|
+ public int Index
|
|
|
+ {
|
|
|
+ get { return _Index; }
|
|
|
+ set { SetProperty(ref _Index, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private DateTime _Timestamp;
|
|
|
+ /// <summary>
|
|
|
+ /// 时间戳
|
|
|
+ /// </summary>
|
|
|
+ public DateTime Timestamp
|
|
|
+ {
|
|
|
+ get { return _Timestamp; }
|
|
|
+ set { SetProperty(ref _Timestamp, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool _isPhotoSuccess;
|
|
|
+ /// <summary>
|
|
|
+ /// 拍照结果
|
|
|
+ /// </summary>
|
|
|
+ public bool IsPhotoSuccess
|
|
|
+ {
|
|
|
+ get { return _isPhotoSuccess; }
|
|
|
+ set { SetProperty(ref _isPhotoSuccess, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private RPoint _PointB;
|
|
|
+ /// <summary>
|
|
|
+ /// 机器人拍照的坐标
|
|
|
+ /// </summary>
|
|
|
+ public RPoint PointB
|
|
|
+ {
|
|
|
+ get { return _PointB; }
|
|
|
+ set { SetProperty(ref _PointB, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _CycleTime;
|
|
|
+ /// <summary>
|
|
|
+ /// 单次节拍
|
|
|
+ /// </summary>
|
|
|
+ public double CycleTime
|
|
|
+ {
|
|
|
+ get { return _CycleTime; }
|
|
|
+ set { SetProperty(ref _CycleTime, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _PixelX;
|
|
|
+ /// <summary>
|
|
|
+ /// 像素X
|
|
|
+ /// </summary>
|
|
|
+ public double PixelX
|
|
|
+ {
|
|
|
+ get { return _PixelX; }
|
|
|
+ set { SetProperty(ref _PixelX, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _PixelY;
|
|
|
+ /// <summary>
|
|
|
+ /// 像素Y
|
|
|
+ /// </summary>
|
|
|
+ public double PixelY
|
|
|
+ {
|
|
|
+ get { return _PixelY; }
|
|
|
+ set { SetProperty(ref _PixelY, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _AngleU;
|
|
|
+ /// <summary>
|
|
|
+ /// 像素U
|
|
|
+ /// </summary>
|
|
|
+ public double AngleU
|
|
|
+ {
|
|
|
+ get { return _AngleU; }
|
|
|
+ set { SetProperty(ref _AngleU, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _TransformX;
|
|
|
+ /// <summary>
|
|
|
+ /// 转换后的机器人坐标X
|
|
|
+ /// </summary>
|
|
|
+ public double TransformX
|
|
|
+ {
|
|
|
+ get { return _TransformX; }
|
|
|
+ set { SetProperty(ref _TransformX, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _TransformY;
|
|
|
+ /// <summary>
|
|
|
+ /// 转换后的机器人坐标Y
|
|
|
+ /// </summary>
|
|
|
+ public double TransformY
|
|
|
+ {
|
|
|
+ get { return _TransformY; }
|
|
|
+ set { SetProperty(ref _TransformY, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ public DynamicTestResult()
|
|
|
+ { }
|
|
|
+
|
|
|
+ public DynamicTestResult(int index, bool isPhotoSuccess, RPoint pointB, double cycleTime, double pixelX, double pixelY, double angleU, double transformX, double transformY)
|
|
|
+ {
|
|
|
+ Index = index;
|
|
|
+ Timestamp = DateTime.Now;
|
|
|
+ IsPhotoSuccess = isPhotoSuccess;
|
|
|
+ PointB = pointB;
|
|
|
+ CycleTime = cycleTime;
|
|
|
+ PixelX = pixelX;
|
|
|
+ PixelY = pixelY;
|
|
|
+ AngleU = angleU;
|
|
|
+ TransformX = transformX;
|
|
|
+ TransformY = transformY;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 精度分析结果
|
|
|
+ /// </summary>
|
|
|
+ public class PrecisionAnalysisResult : BindableBase
|
|
|
+ {
|
|
|
+ private double _xPrecision;
|
|
|
+ /// <summary>
|
|
|
+ /// X方向精度(标准差)
|
|
|
+ /// </summary>
|
|
|
+ public double XPrecision
|
|
|
+ {
|
|
|
+ get { return _xPrecision; }
|
|
|
+ set { SetProperty(ref _xPrecision, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _yPrecision;
|
|
|
+ /// <summary>
|
|
|
+ /// Y方向精度(标准差)
|
|
|
+ /// </summary>
|
|
|
+ public double YPrecision
|
|
|
+ {
|
|
|
+ get { return _yPrecision; }
|
|
|
+ set { SetProperty(ref _yPrecision, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _anglePrecision;
|
|
|
+ /// <summary>
|
|
|
+ /// 角度精度(标准差)
|
|
|
+ /// </summary>
|
|
|
+ public double AnglePrecision
|
|
|
+ {
|
|
|
+ get { return _anglePrecision; }
|
|
|
+ set { SetProperty(ref _anglePrecision, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _xThreeSigmaRange;
|
|
|
+ /// <summary>
|
|
|
+ /// X方向3σ范围
|
|
|
+ /// </summary>
|
|
|
+ public double XThreeSigmaRange
|
|
|
+ {
|
|
|
+ get { return _xThreeSigmaRange; }
|
|
|
+ set { SetProperty(ref _xThreeSigmaRange, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _yThreeSigmaRange;
|
|
|
+ /// <summary>
|
|
|
+ /// Y方向3σ范围
|
|
|
+ /// </summary>
|
|
|
+ public double YThreeSigmaRange
|
|
|
+ {
|
|
|
+ get { return _yThreeSigmaRange; }
|
|
|
+ set { SetProperty(ref _yThreeSigmaRange, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _angleThreeSigmaRange;
|
|
|
+ /// <summary>
|
|
|
+ /// 角度3σ范围
|
|
|
+ /// </summary>
|
|
|
+ public double AngleThreeSigmaRange
|
|
|
+ {
|
|
|
+ get { return _angleThreeSigmaRange; }
|
|
|
+ set { SetProperty(ref _angleThreeSigmaRange, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _xMean;
|
|
|
+ /// <summary>
|
|
|
+ /// X方向平均值
|
|
|
+ /// </summary>
|
|
|
+ public double XMean
|
|
|
+ {
|
|
|
+ get { return _xMean; }
|
|
|
+ set { SetProperty(ref _xMean, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _yMean;
|
|
|
+ /// <summary>
|
|
|
+ /// Y方向平均值
|
|
|
+ /// </summary>
|
|
|
+ public double YMean
|
|
|
+ {
|
|
|
+ get { return _yMean; }
|
|
|
+ set { SetProperty(ref _yMean, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _angleMean;
|
|
|
+ /// <summary>
|
|
|
+ /// 角度平均值
|
|
|
+ /// </summary>
|
|
|
+ public double AngleMean
|
|
|
+ {
|
|
|
+ get { return _angleMean; }
|
|
|
+ set { SetProperty(ref _angleMean, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _xRange;
|
|
|
+ /// <summary>
|
|
|
+ /// X方向极差
|
|
|
+ /// </summary>
|
|
|
+ public double XRange
|
|
|
+ {
|
|
|
+ get { return _xRange; }
|
|
|
+ set { SetProperty(ref _xRange, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _yRange;
|
|
|
+ /// <summary>
|
|
|
+ /// Y方向极差
|
|
|
+ /// </summary>
|
|
|
+ public double YRange
|
|
|
+ {
|
|
|
+ get { return _yRange; }
|
|
|
+ set { SetProperty(ref _yRange, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _angleRange;
|
|
|
+ /// <summary>
|
|
|
+ /// 角度极差
|
|
|
+ /// </summary>
|
|
|
+ public double AngleRange
|
|
|
+ {
|
|
|
+ get { return _angleRange; }
|
|
|
+ set { SetProperty(ref _angleRange, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _xKurtosis;
|
|
|
+ /// <summary>
|
|
|
+ /// X方向峰度
|
|
|
+ /// </summary>
|
|
|
+ public double XKurtosis
|
|
|
+ {
|
|
|
+ get { return _xKurtosis; }
|
|
|
+ set { SetProperty(ref _xKurtosis, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _yKurtosis;
|
|
|
+ /// <summary>
|
|
|
+ /// Y方向峰度
|
|
|
+ /// </summary>
|
|
|
+ public double YKurtosis
|
|
|
+ {
|
|
|
+ get { return _yKurtosis; }
|
|
|
+ set { SetProperty(ref _yKurtosis, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _xCpk;
|
|
|
+ /// <summary>
|
|
|
+ /// X方向CPK
|
|
|
+ /// </summary>
|
|
|
+ public double XCpk
|
|
|
+ {
|
|
|
+ get { return _xCpk; }
|
|
|
+ set { SetProperty(ref _xCpk, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _yCpk;
|
|
|
+ /// <summary>
|
|
|
+ /// Y方向CPK
|
|
|
+ /// </summary>
|
|
|
+ public double YCpk
|
|
|
+ {
|
|
|
+ get { return _yCpk; }
|
|
|
+ set { SetProperty(ref _yCpk, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _angleCpk;
|
|
|
+ /// <summary>
|
|
|
+ /// 角度CPK
|
|
|
+ /// </summary>
|
|
|
+ public double AngleCpk
|
|
|
+ {
|
|
|
+ get { return _angleCpk; }
|
|
|
+ set { SetProperty(ref _angleCpk, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _overallPrecision;
|
|
|
+ /// <summary>
|
|
|
+ /// 综合精度(XY合成标准差)
|
|
|
+ /// </summary>
|
|
|
+ public double OverallPrecision
|
|
|
+ {
|
|
|
+ get { return _overallPrecision; }
|
|
|
+ set { SetProperty(ref _overallPrecision, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double _overallThreeSigmaRange;
|
|
|
+ /// <summary>
|
|
|
+ /// 综合3σ范围
|
|
|
+ /// </summary>
|
|
|
+ public double OverallThreeSigmaRange
|
|
|
+ {
|
|
|
+ get { return _overallThreeSigmaRange; }
|
|
|
+ set { SetProperty(ref _overallThreeSigmaRange, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ private string _processRating;
|
|
|
+ /// <summary>
|
|
|
+ /// 过程能力评级
|
|
|
+ /// </summary>
|
|
|
+ public string ProcessRating
|
|
|
+ {
|
|
|
+ get { return _processRating; }
|
|
|
+ set { SetProperty(ref _processRating, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取CPK评级描述
|
|
|
+ /// </summary>
|
|
|
+ public string GetCpkRating(double cpk)
|
|
|
+ {
|
|
|
+ if (cpk >= 1.67)
|
|
|
+ return "卓越";
|
|
|
+ else if (cpk >= 1.33)
|
|
|
+ return "良好";
|
|
|
+ else if (cpk >= 1.00)
|
|
|
+ return "可接受";
|
|
|
+ else if (cpk >= 0.67)
|
|
|
+ return "不足";
|
|
|
+ else
|
|
|
+ return "严重不足";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|