Browse Source

新增视觉动态精度分析仪完整功能模块

新增 VisionDynamicAccuracyAnalyzer,包括 ViewModel、XAML 视图及后台代码,支持机器人动态往返测试、视觉精度数据采集与分析、结果导出(CSV/PDF)与报告生成。完成相关对话框注册和工程文件配置,提升系统自动化测试与数据分析能力。
孝锋 徐 7 months ago
parent
commit
7b9ffecbb5

+ 1 - 0
TeamAAS-VM/App.xaml.cs

@@ -184,6 +184,7 @@ namespace TeamAAS_VP
             containerRegistry.RegisterDialog<TorqueCheck>();
             containerRegistry.RegisterDialog<SetAlarmValue>();
             containerRegistry.RegisterDialog<VisionStaticAccuracyAnalyzer>();
+            containerRegistry.RegisterDialog<VisionDynamicAccuracyAnalyzer>();
             //**************************************************************************************
 
             // 注册 SQLSugar 客户端(单例模式)

+ 8 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -546,6 +546,7 @@
     <Compile Include="ViewModels\Setting\AddScrewFeederViewModel.cs" />
     <Compile Include="ViewModels\Statistics\LockResultRecoredQueryViewModel.cs" />
     <Compile Include="ViewModels\Product\VisionStaticAccuracyAnalyzerViewModel.cs" />
+    <Compile Include="ViewModels\Product\VisionDynamicAccuracyAnalyzerViewModel.cs" />
     <Compile Include="Views\DebugMod\FocusAnalyzerl.xaml.cs">
       <DependentUpon>FocusAnalyzerl.xaml</DependentUpon>
     </Compile>
@@ -1014,6 +1015,9 @@
     <Compile Include="Views\Product\TestProductPage.xaml.cs">
       <DependentUpon>TestProductPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Views\Product\VisionDynamicAccuracyAnalyzer.xaml.cs">
+      <DependentUpon>VisionDynamicAccuracyAnalyzer.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Views\Product\VisionScriptPage.xaml.cs">
       <DependentUpon>VisionScriptPage.xaml</DependentUpon>
     </Compile>
@@ -1380,6 +1384,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\Product\VisionDynamicAccuracyAnalyzer.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="Views\Product\VisionScriptPage.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>

+ 1195 - 0
TeamAAS-VM/ViewModels/Product/VisionDynamicAccuracyAnalyzerViewModel.cs

@@ -0,0 +1,1195 @@
+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.Interfaces;
+using TeamAAS_VP.Core;
+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 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 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;
+        /// <summary>
+        /// 移动速度
+        /// </summary>
+        public int MoveSpeed
+        {
+            get { return _MoveSpeed; }
+            set { SetProperty(ref _MoveSpeed, 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));
+
+        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));
+
+        
+        #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()
+        {
+
+
+            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()
+        {
+            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();
+
+                    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);
+                    }
+
+                    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;
+                };
+
+                // 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>
+        /// 示教点B
+        /// </summary>
+        void ExecuteTeachPointBCommand()
+        {
+
+        }
+
+        /// <summary>
+        /// 示教点A
+        /// </summary>
+        void ExecuteTeachPointACommand()
+        {
+
+        }
+
+        /// <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(Lang.相机曝光设置失败);
+                    //}
+                    //succed = Camera.SetGain(SelectProcedure.Gain);
+                    //if (!succed)
+                    //{
+                    //    SendTaskMessage(Lang.相机增益设置失败);
+                    //}
+                    //DateTime nowtime = DateTime.Now;
+                    //LogHelper.WriteLogInfo(Lang.开始采集图像);
+                    //var image = Camera.Grab();
+                    //if (image == null)
+                    //{
+
+                    //    SendTaskMessage(Lang.图像采集失败);
+                    //    outputCollection = null;
+                    //    return (false, result.ToArray());
+                    //}
+                    //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(Lang.开始运行视觉工具);
+                    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)
+                                            {
+                                                var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
+                                                (bool IsSucceed, double X, double Y, double U) = _calibrationService.ConvertPixelToPosition((double.Parse(subitems[0]), double.Parse(subitems[1]), double.Parse(subitems[2])), null, 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(Lang.执行相机取图并执行视觉工具组时出错, 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()
+        {
+
+        }
+
+        
+
+        public void OnDialogOpened(IDialogParameters parameters)
+        {
+            SelectProduct = parameters.GetValue<ProductModel>("SelectProduct");
+            Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
+            VisionTool = parameters.GetValue<CogToolBlock>("ToolBlock");
+            ProcedureModels = new ObservableCollection<ProcedureModel>();
+            foreach (var item in SelectProduct.CameraProcedures)
+            {
+                foreach (var item1 in item.ProcedureModels)
+                {
+                    ProcedureModels.Add(item1);
+                }
+            }
+            VisionTool.Run();
+            TestResult = CreateResultDataTable(VisionTool.Outputs);
+        }
+        #endregion
+    }
+}

+ 779 - 0
TeamAAS-VM/Views/Product/VisionDynamicAccuracyAnalyzer.xaml

@@ -0,0 +1,779 @@
+<UserControl x:Class="TeamAAS_VP.Views.Product.VisionDynamicAccuracyAnalyzer"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:prism="http://prismlibrary.com/"
+             xmlns:wf="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
+             xmlns:vp="clr-namespace:Cognex.VisionPro;assembly=Cognex.VisionPro.Controls"
+             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:sys="clr-namespace:System;assembly=mscorlib"
+             xmlns:uControl="clr-namespace:TeamAAS_VP.Controls"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:vm="clr-namespace:TeamAAS_VP.ViewModels.Product"
+             xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
+             xmlns:lex="http://wpflocalizeextension.codeplex.com"
+             lex:LocalizeDictionary.DesignCulture="zh-CN"
+             lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
+             lex:ResxLocalizationProvider.DefaultDictionary="Lang"
+             prism:ViewModelLocator.AutoWireViewModel="True"
+             mc:Ignorable="d"
+             d:DataContext="{d:DesignInstance Type=vm:VisionDynamicAccuracyAnalyzerViewModel}"
+             d:Height="700"
+             d:Width="1100"
+             MinHeight="750"
+             MinWidth="1000"
+             d:Background="White"
+             HorizontalAlignment="Stretch"
+             VerticalAlignment="Stretch"
+             FontFamily="{DynamicResource DefaultFont}">
+
+    <prism:Dialog.WindowStyle>
+        <Style TargetType="Window">
+            <Setter Property="prism:Dialog.WindowStartupLocation"
+                    Value="CenterScreen" />
+            <Setter Property="AllowDrop"
+                    Value="True" />
+            <Setter Property="ShowInTaskbar"
+                    Value="True" />
+            <Setter Property="WindowState"
+                    Value="Maximized" />
+            <Setter Property="Topmost"
+                    Value="True" />
+        </Style>
+    </prism:Dialog.WindowStyle>
+
+    <UserControl.Resources>
+        <!-- 复用静态测试的样式 -->
+        <Style TargetType="Button"
+               BasedOn="{StaticResource MaterialDesignFlatDarkBgButton}">
+            <Setter Property="Padding"
+                    Value="10 5" />
+            <Setter Property="Margin"
+                    Value="5" />
+            <Setter Property="FontSize"
+                    Value="14" />
+            <Setter Property="BorderThickness"
+                    Value="0" />
+        </Style>
+
+        <Style TargetType="TextBlock">
+            <Setter Property="VerticalAlignment"
+                    Value="Center" />
+            <Setter Property="Margin"
+                    Value="5" />
+        </Style>
+
+        <Style TargetType="TextBox"
+               BasedOn="{StaticResource MaterialDesignTextBox}">
+            <Setter Property="Margin"
+                    Value="5" />
+            <Setter Property="VerticalContentAlignment"
+                    Value="Center" />
+        </Style>
+
+        <Style x:Key="DataGridStyle"
+               TargetType="DataGrid"
+               BasedOn="{StaticResource MaterialDesignDataGrid}">
+            <Setter Property="Margin"
+                    Value="5" />
+            <Setter Property="AutoGenerateColumns"
+                    Value="False" />
+            <Setter Property="CanUserAddRows"
+                    Value="False" />
+            <Setter Property="CanUserDeleteRows"
+                    Value="False" />
+            <Setter Property="IsReadOnly"
+                    Value="True" />
+            <Setter Property="AlternatingRowBackground"
+                    Value="#F5F5F5" />
+        </Style>
+
+        <Style x:Key="TitleStyle"
+               TargetType="TextBlock">
+            <Setter Property="FontSize"
+                    Value="16" />
+            <Setter Property="FontWeight"
+                    Value="Bold" />
+            <Setter Property="Margin"
+                    Value="0 10 0 5" />
+            <Setter Property="Foreground"
+                    Value="#2C3E50" />
+        </Style>
+    </UserControl.Resources>
+
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="2.5*" />
+            <ColumnDefinition Width="3.5*" />
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧控制面板 -->
+        <Border Grid.Column="0"
+                BorderBrush="#BDC3C7"
+                BorderThickness="0 0 1 0">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="*" />
+                    <RowDefinition Height="Auto" />
+                </Grid.RowDefinitions>
+
+                <!-- 机器人位置设置 -->
+                <Border Grid.Row="0"
+                        Background="#ECF0F1"
+                        Padding="10">
+                    <StackPanel>
+                        <TextBlock Style="{StaticResource TitleStyle}"
+                                   Text="机器人位置设置" />
+
+                        <Grid Margin="0 10 0 0">
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                            </Grid.RowDefinitions>
+
+                            <!-- A点坐标 -->
+                            <Grid Grid.Row="0"
+                                  Margin="0 0 0 10">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="Auto" />
+                                    <ColumnDefinition Width="*" />
+                                    <ColumnDefinition Width="Auto" />
+                                </Grid.ColumnDefinitions>
+
+                                <TextBlock Text="A点坐标:"
+                                           Grid.Column="0"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Column="1"
+                                         Text="{Binding PointA, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
+                                         d:Text="X:0.000 Y:0.000 Z:0.000 RX:0.000 RY:0.000 RZ:0.000"
+                                         ToolTip="机器人A点位置坐标"
+                                         IsReadOnly="True"
+                                         Background="#F8F9FA" />
+                                <Button Grid.Column="2"
+                                        Content="示教"
+                                        Command="{Binding TeachPointACommand}"
+                                        Background="#3498DB"
+                                        Foreground="White"
+                                        Width="60"
+                                        Margin="5,0,0,0" />
+                            </Grid>
+
+                            <!-- B点坐标 -->
+                            <Grid Grid.Row="1"
+                                  Margin="0 0 0 10">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="Auto" />
+                                    <ColumnDefinition Width="*" />
+                                    <ColumnDefinition Width="Auto" />
+                                </Grid.ColumnDefinitions>
+
+                                <TextBlock Text="B点坐标:"
+                                           Grid.Column="0"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Column="1"
+                                         Text="{Binding PointB, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
+                                         d:Text="X:100.000 Y:50.000 Z:0.000 RX:0.000 RY:0.000 RZ:0.000"
+                                         ToolTip="机器人B点拍照位置坐标"
+                                         IsReadOnly="True"
+                                         Background="#F8F9FA" />
+                                <Button Grid.Column="2"
+                                        Content="示教"
+                                        Command="{Binding TeachPointBCommand}"
+                                        Background="#3498DB"
+                                        Foreground="White"
+                                        Width="60"
+                                        Margin="5,0,0,0" />
+                            </Grid>
+
+                            <!-- 视觉流程选择 -->
+                            <Grid Grid.Row="2">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="Auto" />
+                                    <ColumnDefinition Width="*" />
+                                </Grid.ColumnDefinitions>
+
+                                <TextBlock Text="视觉流程:"
+                                           Grid.Column="0"
+                                           VerticalAlignment="Center" />
+                                <ComboBox Grid.Column="1"
+                                          ItemsSource="{Binding ProcedureModels}"
+                                          SelectedItem="{Binding SelectProcedure, Mode=TwoWay}"
+                                          ToolTip="选择视觉处理流程">
+                                    <ComboBox.ItemTemplate>
+                                        <DataTemplate>
+                                            <TextBlock Text="{Binding Name}" />
+                                        </DataTemplate>
+                                    </ComboBox.ItemTemplate>
+                                </ComboBox>
+                            </Grid>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 测试参数设置 -->
+                <Border Grid.Row="1"
+                        Background="#F8F9FA"
+                        Padding="10">
+                    <StackPanel>
+                        <TextBlock Style="{StaticResource TitleStyle}"
+                                   Text="测试参数设置" />
+
+                        <Grid Margin="0 10 0 0">
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="*" />
+                                <ColumnDefinition Width="*" />
+                            </Grid.ColumnDefinitions>
+
+                            <!-- 重复次数 -->
+                            <StackPanel Grid.Column="0">
+                                <TextBlock Text="重复次数:" />
+                                <mah:NumericUpDown Value="{Binding RepeatCount, Mode=TwoWay}"
+                                                   Minimum="1"
+                                                   Maximum="1000"
+                                                   d:Value="50"
+                                                   TextAlignment="Center"
+                                                   HorizontalContentAlignment="Center"
+                                                   HorizontalAlignment="Stretch"
+                                                   IsEnabled="{Binding IsRunning, Converter={StaticResource InvertBooleanConverter}}" />
+                            </StackPanel>
+
+                            <!-- 移动速度 -->
+                            <StackPanel Grid.Column="1"
+                                        Margin="10,0,0,0">
+                                <TextBlock Text="移动速度(%):" />
+                                <mah:NumericUpDown Value="{Binding MoveSpeed, Mode=TwoWay}"
+                                                   Minimum="1"
+                                                   Maximum="100"
+                                                   d:Value="50"
+                                                   TextAlignment="Center"
+                                                   HorizontalContentAlignment="Center"
+                                                   HorizontalAlignment="Stretch"
+                                                   IsEnabled="{Binding IsRunning, Converter={StaticResource InvertBooleanConverter}}" />
+                            </StackPanel>
+                        </Grid>
+
+                        
+                    </StackPanel>
+                </Border>
+
+                <!-- 测试控制区域 -->
+                <Border Grid.Row="2"
+                        Background="#ECF0F1"
+                        Padding="10">
+                    <StackPanel>
+                        <TextBlock Style="{StaticResource TitleStyle}"
+                                   Text="测试控制" />
+
+                        <WrapPanel HorizontalAlignment="Center"
+                                   Margin="0 10 0 0">
+                            <Button Content="单次测试"
+                                    Background="#3498DB"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Height="35"
+                                    FontWeight="Bold"
+                                    Command="{Binding SingleTestCommand}"
+                                    IsEnabled="{Binding IsRunning, Converter={StaticResource InvertBooleanConverter}}" />
+
+                            <Button Content="连续测试"
+                                    Background="#27AE60"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Height="35"
+                                    FontWeight="Bold"
+                                    Command="{Binding StartTestCommand}" />
+
+                            <Button Content="停止"
+                                    Background="#E74C3C"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Height="35"
+                                    Command="{Binding StopTestCommand}" />
+
+                            <Button Content="暂停"
+                                    Background="#F39C12"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Height="35"
+                                    Command="{Binding PauseTestCommand}" />
+                        </WrapPanel>
+                    </StackPanel>
+                </Border>
+
+                <!-- 进度显示 -->
+                <Border Grid.Row="3"
+                        Background="White"
+                        Padding="10">
+                    <StackPanel>
+                        <TextBlock Style="{StaticResource TitleStyle}"
+                                   Text="测试进度" />
+
+                        <Grid Margin="0 10 0 0">
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                            </Grid.RowDefinitions>
+
+                            <ProgressBar Grid.Row="0"
+                                         Height="25"
+                                         Margin="5"
+                                         Minimum="0"
+                                         Maximum="{Binding RepeatCount}"
+                                         Value="{Binding CurrentProgress}"
+                                         IsEnabled="False"
+                                         d:Maximum="100"
+                                         d:Value="50" />
+
+                            <TextBlock Grid.Row="1"
+                                       HorizontalAlignment="Center"
+                                       FontSize="12"
+                                       Foreground="#7F8C8D"
+                                       d:Text="等待开始..."
+                                       Text="{Binding Message}" />
+                        </Grid>
+
+                        <!-- 实时统计信息 -->
+                        <WrapPanel Margin="0 15 0 0"
+                                   HorizontalAlignment="Center">
+                            <Border Background="#3498DB"
+                                    Padding="10 5"
+                                    Margin="5"
+                                    CornerRadius="3">
+                                <StackPanel>
+                                    <TextBlock Text="当前循环"
+                                               Foreground="White"
+                                               FontSize="11" />
+                                    <TextBlock Text="{Binding CurrentCycle}"
+                                               d:Text="1"
+                                               Foreground="White"
+                                               FontSize="16"
+                                               FontWeight="Bold"
+                                               HorizontalAlignment="Center" />
+                                </StackPanel>
+                            </Border>
+
+                            <Border Background="#2ECC71"
+                                    Padding="10 5"
+                                    Margin="5"
+                                    CornerRadius="3">
+                                <StackPanel>
+                                    <TextBlock Text="成功率"
+                                               Foreground="White"
+                                               FontSize="11" />
+                                    <TextBlock Text="{Binding SuccessRate, StringFormat={}{0:F1}%}"
+                                               d:Text="100%"
+                                               Foreground="White"
+                                               FontSize="16"
+                                               FontWeight="Bold"
+                                               HorizontalAlignment="Center" />
+                                </StackPanel>
+                            </Border>
+
+                            <Border Background="#9B59B6"
+                                    Padding="10 5"
+                                    Margin="5"
+                                    CornerRadius="3">
+                                <StackPanel>
+                                    <TextBlock Text="往返时间"
+                                               Foreground="White"
+                                               FontSize="11" />
+                                    <TextBlock Text="{Binding CycleTime, StringFormat={}{0:F2}s}"
+                                               d:Text="2.34s"
+                                               Foreground="White"
+                                               FontSize="16"
+                                               FontWeight="Bold"
+                                               HorizontalAlignment="Center" />
+                                </StackPanel>
+                            </Border>
+                        </WrapPanel>
+                    </StackPanel>
+                </Border>
+
+                <!-- 测试结果列表 -->
+                <Border Grid.Row="4"
+                        Background="White"
+                        Padding="10">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto" />
+                            <RowDefinition Height="*" />
+                        </Grid.RowDefinitions>
+
+                        <StackPanel Grid.Row="0"
+                                    Orientation="Horizontal">
+                            <TextBlock Style="{StaticResource TitleStyle}"
+                                       Text="测试结果列表" />
+                            <TextBlock Margin="10,0,0,0"
+                                       VerticalAlignment="Center"
+                                       Foreground="#7F8C8D"
+                                       Text="(显示最近20次结果)" />
+                        </StackPanel>
+
+                        <DataGrid x:Name="dgResults"
+                                  Grid.Row="1"
+                                  Style="{StaticResource DataGridStyle}"
+                                  MaxHeight="250"
+                                  AutoGenerateColumns="False"
+                                  CanUserAddRows="False"
+                                  IsReadOnly="True"
+                                  ItemsSource="{Binding TestResults}">
+                            <DataGrid.Columns>
+                                <DataGridTextColumn Header="序号"
+                                                    Binding="{Binding Index}"
+                                                    Width="50" />
+                                <DataGridTextColumn Header="状态"
+                                                    Binding="{Binding Status}"
+                                                    Width="60">
+                                    <DataGridTextColumn.CellStyle>
+                                        <Style TargetType="DataGridCell">
+                                            <Setter Property="Foreground"
+                                                    Value="{Binding StatusColor}" />
+                                        </Style>
+                                    </DataGridTextColumn.CellStyle>
+                                </DataGridTextColumn>
+                                <DataGridTextColumn Header="X坐标"
+                                                    Binding="{Binding X, StringFormat={}{0:F3}}"
+                                                    Width="70" />
+                                <DataGridTextColumn Header="Y坐标"
+                                                    Binding="{Binding Y, StringFormat={}{0:F3}}"
+                                                    Width="70" />
+                                <DataGridTextColumn Header="角度"
+                                                    Binding="{Binding Angle, StringFormat={}{0:F2}}"
+                                                    Width="60" />
+                                <DataGridTextColumn Header="偏差X"
+                                                    Binding="{Binding DeviationX, StringFormat={}{0:F3}}"
+                                                    Width="70" />
+                                <DataGridTextColumn Header="偏差Y"
+                                                    Binding="{Binding DeviationY, StringFormat={}{0:F3}}"
+                                                    Width="70" />
+                                <DataGridTextColumn Header="循环时间"
+                                                    Binding="{Binding CycleTime, StringFormat={}{0:F2}s}"
+                                                    Width="80" />
+                                <DataGridTextColumn Header="时间戳"
+                                                    Binding="{Binding Timestamp}"
+                                                    Width="120" />
+                            </DataGrid.Columns>
+                        </DataGrid>
+                    </Grid>
+                </Border>
+
+                <!-- 导出和精度分析 -->
+                <Border Grid.Row="5"
+                        Background="#ECF0F1"
+                        Padding="10">
+                    <StackPanel>
+                        <!-- 导出按钮 -->
+                        <WrapPanel HorizontalAlignment="Right">
+                            <Button Content="导出CSV"
+                                    Background="#3498DB"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Command="{Binding ExportCSVCommand}" />
+
+                            <Button Content="生成报告"
+                                    Background="#9B59B6"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Command="{Binding ExportReportCommand}" />
+
+                            <Button Content="清空数据"
+                                    Background="#95A5A6"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Command="{Binding ClearDataCommand}" />
+                        </WrapPanel>
+
+                        <!-- 动态精度分析 -->
+                        <Border Margin="0 10 0 0"
+                                Background="#F8F9FA"
+                                Padding="10"
+                                CornerRadius="5">
+                            <StackPanel>
+                                <TextBlock Text="动态重复精度分析"
+                                           FontWeight="Bold"
+                                           Margin="0 0 0 5" />
+
+                                <Grid Margin="0 5 0 0">
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="*" />
+                                        <ColumnDefinition Width="*" />
+                                    </Grid.ColumnDefinitions>
+
+                                    <StackPanel Grid.Column="0">
+                                        <TextBlock>
+                                            <Run Text="X方向精度: " />
+                                            <Run Text="{Binding XPrecision, StringFormat={}{0:F3}mm}"
+                                                 d:Text="0.023mm"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                        <TextBlock>
+                                            <Run Text="Y方向精度: " />
+                                            <Run Text="{Binding YPrecision, StringFormat={}{0:F3}mm}"
+                                                 d:Text="0.018mm"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                        <TextBlock>
+                                            <Run Text="角度精度: " />
+                                            <Run Text="{Binding AnglePrecision, StringFormat={}{0:F2}°}"
+                                                 d:Text="0.12°"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                    </StackPanel>
+
+                                    <StackPanel Grid.Column="1">
+                                        <TextBlock>
+                                            <Run Text="3σ范围: " />
+                                            <Run Text="{Binding ThreeSigmaRange, StringFormat={}±{0:F3}mm}"
+                                                 d:Text="±0.065mm"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                        <TextBlock>
+                                            <Run Text="CPK: " />
+                                            <Run Text="{Binding ProcessCpk, StringFormat={}{0:F2}}"
+                                                 d:Text="1.85"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                        <TextBlock>
+                                            <Run Text="评级: " />
+                                            <Run Text="{Binding ProcessRating}"
+                                                 d:Text="优秀"
+                                                 Foreground="#27AE60"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                    </StackPanel>
+                                </Grid>
+
+                                <!-- 趋势图按钮 -->
+                                <Button Content="显示趋势图"
+                                        Background="#E67E22"
+                                        Foreground="White"
+                                        HorizontalAlignment="Right"
+                                        Margin="0 10 0 0"
+                                        MinWidth="100"
+                                        Command="{Binding ShowTrendChartCommand}" />
+                            </StackPanel>
+                        </Border>
+                    </StackPanel>
+                </Border>
+            </Grid>
+        </Border>
+
+        <!-- 右侧图像和轨迹显示区域 -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="*" />
+                <RowDefinition Height="Auto" />
+                <RowDefinition Height="Auto" />
+            </Grid.RowDefinitions>
+
+            <!-- 图像显示区域 -->
+            <Border Background="#2C3E50">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="*" />
+                        <RowDefinition Height="Auto" />
+                    </Grid.RowDefinitions>
+
+                    <!-- VisionPro图像显示 -->
+                    <wf:WindowsFormsHost Grid.Row="0">
+                        <vp:CogRecordDisplay x:Name="display"
+                                             Dock="Fill" />
+                    </wf:WindowsFormsHost>
+
+                    <!-- 图像信息栏 -->
+                    <Border Grid.Row="1"
+                            Background="#34495E"
+                            Padding="10">
+                        <Grid>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="*" />
+                                <ColumnDefinition Width="Auto" />
+                            </Grid.ColumnDefinitions>
+
+                            <StackPanel Grid.Column="0"
+                                        Orientation="Horizontal">
+                                <TextBlock Text="图像信息: "
+                                           Foreground="White" />
+                                <TextBlock Text="{Binding ImageInfo}"
+                                           d:Text="第15次采集 - 检测成功"
+                                           Foreground="#F1C40F"
+                                           FontWeight="Bold" />
+                            </StackPanel>
+
+                            <StackPanel Grid.Column="1"
+                                        Orientation="Horizontal">
+                                <TextBlock Text="检测时间: "
+                                           Foreground="White" />
+                                <TextBlock Text="{Binding DetectTime, StringFormat={}{0:F3}s}"
+                                           d:Text="0.123s"
+                                           Foreground="#F1C40F"
+                                           FontWeight="Bold"
+                                           Width="60" />
+                            </StackPanel>
+                        </Grid>
+                    </Border>
+                </Grid>
+            </Border>
+
+            <!-- 轨迹可视化区域 -->
+            <Border Grid.Row="1"
+                    Background="#ECF0F1"
+                    Padding="10"
+                    BorderBrush="#BDC3C7"
+                    BorderThickness="0 1 0 0">
+                <StackPanel>
+                    <TextBlock Text="机器人轨迹可视化"
+                               FontWeight="Bold"
+                               Margin="0 0 0 5" />
+
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*" />
+                            <ColumnDefinition Width="Auto" />
+                        </Grid.ColumnDefinitions>
+
+                        <!-- 简化轨迹图 -->
+                        <Canvas x:Name="trajectoryCanvas"
+                                Height="100"
+                                Background="White"
+                                Margin="0 5 0 0">
+                            <!-- A点标记 -->
+                            <Ellipse Canvas.Left="50"
+                                     Canvas.Top="50"
+                                     Width="12"
+                                     Height="12"
+                                     Fill="#3498DB"
+                                     Stroke="#2C3E50"
+                                     StrokeThickness="1">
+                                <Ellipse.ToolTip>
+                                    <ToolTip>
+                                        <StackPanel>
+                                            <TextBlock Text="A点 - 起始位置"
+                                                       FontWeight="Bold" />
+                                            <TextBlock Text="{Binding PointA}" />
+                                        </StackPanel>
+                                    </ToolTip>
+                                </Ellipse.ToolTip>
+                            </Ellipse>
+                            <TextBlock Canvas.Left="45"
+                                       Canvas.Top="65"
+                                       Text="A"
+                                       FontWeight="Bold"
+                                       Foreground="#2C3E50" />
+
+                            <!-- B点标记 -->
+                            <Ellipse Canvas.Left="200"
+                                     Canvas.Top="50"
+                                     Width="12"
+                                     Height="12"
+                                     Fill="#E74C3C"
+                                     Stroke="#2C3E50"
+                                     StrokeThickness="1">
+                                <Ellipse.ToolTip>
+                                    <ToolTip>
+                                        <StackPanel>
+                                            <TextBlock Text="B点 - 拍照位置"
+                                                       FontWeight="Bold" />
+                                            <TextBlock Text="{Binding PointB}" />
+                                        </StackPanel>
+                                    </ToolTip>
+                                </Ellipse.ToolTip>
+                            </Ellipse>
+                            <TextBlock Canvas.Left="195"
+                                       Canvas.Top="65"
+                                       Text="B"
+                                       FontWeight="Bold"
+                                       Foreground="#2C3E50" />
+
+                            <!-- 移动轨迹线 -->
+                            <Line x:Name="trajectoryLine"
+                                  X1="56"
+                                  Y1="56"
+                                  X2="194"
+                                  Y2="56"
+                                  Stroke="#7F8C8D"
+                                  StrokeThickness="2"
+                                  StrokeDashArray="5,3" />
+
+                            <!-- 当前位置标记 -->
+                            <Ellipse x:Name="currentPositionMarker"
+                                     Canvas.Left="125"
+                                     Canvas.Top="50"
+                                     Width="8"
+                                     Height="8"
+                                     Fill="#27AE60"
+                                     Stroke="White"
+                                     StrokeThickness="1"
+                                     Visibility="Visible">
+                                <Ellipse.ToolTip>
+                                    <ToolTip>
+                                        <StackPanel>
+                                            <TextBlock Text="当前位置"
+                                                       FontWeight="Bold" />
+                                            <TextBlock Text="{Binding CurrentPosition}" />
+                                        </StackPanel>
+                                    </ToolTip>
+                                </Ellipse.ToolTip>
+                            </Ellipse>
+                        </Canvas>
+
+                        <!-- 轨迹控制 -->
+                        <StackPanel Grid.Column="1"
+                                    Orientation="Vertical"
+                                    Margin="10 0 0 0">
+                            <CheckBox Content="显示轨迹"
+                                      IsChecked="{Binding ShowTrajectory, Mode=TwoWay}"
+                                      Margin="0 0 0 5" />
+                            <CheckBox Content="实时更新"
+                                      IsChecked="{Binding RealTimeUpdate, Mode=TwoWay}"
+                                      Margin="0 0 0 5" />
+                            <Button Content="重置视图"
+                                    MinWidth="80"
+                                    Height="25"
+                                    Command="{Binding ResetTrajectoryViewCommand}" />
+                        </StackPanel>
+                    </Grid>
+                </StackPanel>
+            </Border>
+
+            <!-- 底部按钮 -->
+            <StackPanel Grid.Row="2"
+                        Orientation="Horizontal"
+                        VerticalAlignment="Center"
+                        HorizontalAlignment="Right"
+                        Margin="0 5 5 5">
+                <Button Height="30"
+                        MinWidth="100"
+                        Content="{lex:Loc 确定}"
+                        materialDesign:ButtonAssist.CornerRadius="10"
+                        ToolTip="{lex:Loc 确定}"
+                        Command="{Binding ConfirmCommand}" />
+                <Button Height="30"
+                        Margin="10,0"
+                        MinWidth="100"
+                        materialDesign:ButtonAssist.CornerRadius="10"
+                        ToolTip="{lex:Loc 取消}"
+                        Content="{lex:Loc 取消}"
+                        Command="{Binding CancelCommand}" />
+            </StackPanel>
+        </Grid>
+
+        <!-- 消息提示 -->
+        <materialDesign:Snackbar Grid.ColumnSpan="2"
+                                 Opacity="0.8"
+                                 HorizontalContentAlignment="Center"
+                                 FontSize="14"
+                                 MessageQueue="{Binding MessageQueue}"
+                                 VerticalAlignment="Bottom" />
+    </Grid>
+</UserControl>

+ 92 - 0
TeamAAS-VM/Views/Product/VisionDynamicAccuracyAnalyzer.xaml.cs

@@ -0,0 +1,92 @@
+using Cognex.VisionPro;
+using Cognex.VisionPro.Dimensioning;
+using System;
+using System.ComponentModel;
+using System.Data;
+using System.Windows.Controls;
+using TeamAAS_VP.ViewModels.Product;
+
+namespace TeamAAS_VP.Views.Product
+{
+    /// <summary>
+    /// Interaction logic for VisionDynamicAccuracyAnalyzer
+    /// </summary>
+    public partial class VisionDynamicAccuracyAnalyzer : UserControl
+    {
+        VisionDynamicAccuracyAnalyzerViewModel VM;
+        public VisionDynamicAccuracyAnalyzer()
+        {
+            InitializeComponent();
+            VM = DataContext as VisionDynamicAccuracyAnalyzerViewModel;
+            VM.PropertyChanged += VM_PropertyChanged;
+            this.display.VerticalScrollBar = false;
+            this.display.HorizontalScrollBar = false;
+            this.display.AutoFit = true;
+            this.display.BackColor = System.Drawing.SystemColors.ActiveCaption;
+        }
+
+        private int _imageWidth;
+        private int _imageHeight;
+        private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+        {
+            try
+            {
+                if (e.PropertyName == "Image")
+                {
+                    this.display.Image = VM.Image;
+                    //图像中心点坐标
+                    _imageWidth = this.display.Image.Width;
+                    _imageHeight = this.display.Image.Height;
+                }
+                else if (e.PropertyName == "Graphic")
+                {
+                    this.display.StaticGraphics.Clear();
+                    CogGraphicCollection graphic = new CogGraphicCollection();
+                    CogCreateLineTool line1 = new CogCreateLineTool();
+                    CogCreateLineTool line2 = new CogCreateLineTool();
+                    line1.InputImage = this.display.Image;
+                    line2.InputImage = this.display.Image;
+                    line1.Line.X = _imageWidth / 2;
+                    line1.Line.Y = _imageHeight / 2;
+                    line1.Line.Rotation = 0;
+                    line2.Line.X = _imageWidth / 2;
+                    line2.Line.Y = _imageHeight / 2;
+                    line2.Line.Rotation = Math.PI / 180 * 90;
+                    line1.Run();
+                    line2.Run();
+                    graphic.Add(line1.GetOutputLine());
+                    graphic.Add(line2.GetOutputLine());
+                    this.display.StaticGraphics.Clear();
+                    this.display.StaticGraphics.AddList(graphic, "");
+                    if (VM.Graphic != null)
+                    {
+                        this.display.StaticGraphics.AddList(VM.Graphic, "");
+                    }
+
+                }
+            }
+            catch (System.Exception)
+            {
+
+            }
+        }
+
+        private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
+        {
+            // If the source is a DataTable, we can read the DataColumn's Caption
+            if (e.PropertyDescriptor is PropertyDescriptor pd)
+            {
+                var columnName = pd.Name;
+                var dv = dgResults.ItemsSource as DataView;
+                if (dv != null && dv.Table != null && dv.Table.Columns.Contains(columnName))
+                {
+                    var dataCol = dv.Table.Columns[columnName];
+                    if (!string.IsNullOrEmpty(dataCol.Caption))
+                    {
+                        e.Column.Header = dataCol.Caption;
+                    }
+                }
+            }
+        }
+    }
+}