Browse Source

Merge branch 'LCD组装' of http://49.235.130.76/XXF_1122/DaisyNPILine into LCD组装

孝锋 徐 7 months ago
parent
commit
d3ba91afd3

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

@@ -185,6 +185,8 @@ namespace TeamAAS_VP
             containerRegistry.RegisterDialog<LightChannelList>();
             containerRegistry.RegisterDialog<AutoCorrectLockPoint>();
             containerRegistry.RegisterDialog<ImageDisplay>();
+            containerRegistry.RegisterDialog<VisionStaticAccuracyAnalyzer>();
+            containerRegistry.RegisterDialog<VisionDynamicAccuracyAnalyzer>();
             //**************************************************************************************
 
             // 注册 SQLSugar 客户端(单例模式)

+ 334 - 0
TeamAAS-VM/Core/StabilityAnalyzer.cs

@@ -0,0 +1,334 @@
+/* 
+详细的伪代码计划 (以注释形式嵌入文件头部)
+1. 定义 StabilityAnalyzer 类及其内部类 StabilityMetrics:
+   - StabilityMetrics 包含:Mean, StdDeviation, ThreeSigmaRange, Kurtosis, Cpk, Range, CV, Confidence95
+   - 每个属性在注释中说明含义与计算公式
+
+2. AnalyzeStability 方法:
+   - 将输入值转换为数组并验证长度(至少10个数据点)
+   - 计算平均值 mean(data.Average())
+   - 计算标准差 stdDev(调用 CalculateStandardDeviation)
+   - 构建 StabilityMetrics 实例并填充:
+       - Mean = mean
+       - StdDeviation = stdDev
+       - ThreeSigmaRange = 6 * stdDev (表示 ±3σ 的区间宽度)
+       - Kurtosis = CalculateKurtosis(data)
+       - Range = data.Max() - data.Min()
+       - CV = (stdDev / mean) * 100 (百分比)
+       - Confidence95 = Calculate95ConfidenceInterval(data)
+   - 如果提供上下规格限(lowerSpec 和 upperSpec),调用 CalculateProcessCapability 并设置 metrics.Cpk
+   - 返回 metrics
+
+3. GetStabilityRating 方法:
+   - 基于标准差评级返回稳定性评价字符串
+
+4. GetCpkRating 方法:
+   - 根据 cpk 返回等级字符串
+
+5. 统计计算辅助方法:
+   - CalculateStandardDeviation:样本标准差,采用 n-1 分母(样本标准差)
+   - CalculateRange:极差(最大值 - 最小值)
+   - CalculateCV:变异系数,返回百分比
+   - CalculateAccuracy:与理论值的绝对偏差
+   - Calculate95ConfidenceInterval:95%置信区间,使用 1.96 * s / sqrt(n)
+   - GetStdDeviation:封装标准差计算
+   - Get3SigmaRange:返回平均值 ± 3σ 的上下限
+   - CalculateKurtosis:使用 Fisher 定义(正态分布为 0)的峰度计算公式
+   - CalculateProcessCapability:计算 Cp 和 Cpk,使用 6σ 和 3σ 的定义,返回 (cp, cpk)
+
+4. 注释策略:
+   - 对所有公开类、属性和方法添加 XML 注释(供 VS IntelliSense 使用)
+   - 在方法内部添加行内注释,解释关键步骤与公式
+   - 保持注释为中文,简洁明确,便于维护
+
+以上伪代码被置于文件顶部注释中,随后是实现代码,包含完整的中文注释与 XML 文档注释。
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TeamAAS_VP.Core
+{
+    /// <summary>
+    /// 稳定性分析器:提供一组统计方法用于评估一组数值数据的稳定性与过程能力。
+    /// </summary>
+    public class StabilityAnalyzer
+    {
+        /// <summary>
+        /// 稳定性度量结果集
+        /// </summary>
+        public class StabilityMetrics
+        {
+            /// <summary>
+            /// 样本平均值
+            /// </summary>
+            public double Mean { get; set; }               // 平均值
+
+            /// <summary>
+            /// 样本标准差(使用 n-1 分母的样本标准差)
+            /// </summary>
+            public double StdDeviation { get; set; }       // 标准差 (核心1)
+
+            /// <summary>
+            /// 3σ 区间的宽度(等于 6 * StdDeviation,表示 ±3σ 总宽)
+            /// </summary>
+            public double ThreeSigmaRange { get; set; }    // 3σ范围 (核心2)
+
+            /// <summary>
+            /// 峰度(使用 Fisher 定义,正态分布为 0)
+            /// </summary>
+            public double Kurtosis { get; set; }           // 峰度 (核心3)
+
+            /// <summary>
+            /// 过程能力指数 Cpk(若未提供规格限则为默认 0)
+            /// </summary>
+            public double Cpk { get; set; }                // 过程能力指数 (核心4)
+
+            /// <summary>
+            /// 极差(最大值 - 最小值)
+            /// </summary>
+            public double Range { get; set; }              // 极差
+
+            /// <summary>
+            /// 变异系数(标准差 / 平均值 * 100%)
+            /// </summary>
+            public double CV { get; set; }                 // 变异系数
+
+            /// <summary>
+            /// 95% 置信区间,返回 (Lower, Upper)
+            /// </summary>
+            public (double Lower, double Upper) Confidence95 { get; set; }
+        }
+
+        /// <summary>
+        /// 对一组数值进行稳定性分析,返回各项统计度量。
+        /// </summary>
+        /// <param name="values">输入数据序列(至少 10 个点)</param>
+        /// <param name="lowerSpec">下规格限(可选)</param>
+        /// <param name="upperSpec">上规格限(可选)</param>
+        /// <returns>StabilityMetrics 包含多项指标</returns>
+        /// <exception cref="ArgumentException">当数据点少于 10 个时抛出</exception>
+        public static StabilityMetrics AnalyzeStability(IEnumerable<double> values,
+                                                       double? lowerSpec = null,
+                                                       double? upperSpec = null)
+        {
+            // 将输入转换为数组以便重复使用并获取长度
+            var data = values.ToArray();
+            if (data.Length < 10) throw new ArgumentException("至少需要10个数据点");
+
+            // 计算平均值与标准差(样本标准差)
+            var mean = data.Average();
+            var stdDev = CalculateStandardDeviation(data);
+
+            // 构建指标对象并填充常规统计量
+            var metrics = new StabilityMetrics
+            {
+                Mean = mean,
+                StdDeviation = stdDev,
+                // ThreeSigmaRange 表示 ±3σ 的总宽度(6σ)
+                ThreeSigmaRange = CalculateKurtosis(data),//6 * stdDev,  
+                Kurtosis = CalculateKurtosis(data),
+                Range = data.Max() - data.Min(),
+                // 变异系数以百分比形式表示
+                CV = (stdDev / mean) * 100,
+                Confidence95 = Calculate95ConfidenceInterval(data)
+            };
+
+            // 如果提供了规格限,则计算并设置 Cpk(cp 也被计算但不保存)
+            if (lowerSpec.HasValue && upperSpec.HasValue)
+            {
+                metrics.Cpk = CalculateProcessCapability(data,
+                    lowerSpec.Value, upperSpec.Value).cpk;
+            }
+
+            return metrics;
+        }
+
+        /// <summary>
+        /// 根据标准差(StdDeviation)返回稳定性等级描述。
+        /// 阈值为经验值,可根据业务需求调整。
+        /// </summary>
+        /// <param name="metrics">稳定性度量结果</param>
+        /// <returns>稳定性等级字符串(中文 + 英文)</returns>
+        public static string GetStabilityRating(StabilityMetrics metrics)
+        {
+            // 根据标准差的绝对值分级:阈值为示例值,应结合量纲与业务场景判断
+            if (metrics.StdDeviation < 0.001)
+                return "优秀 (Excellent)";
+            else if (metrics.StdDeviation < 0.005)
+                return "良好 (Good)";
+            else if (metrics.StdDeviation < 0.01)
+                return "合格 (Acceptable)";
+            else
+                return "不稳定 (Unstable)";
+        }
+
+        /// <summary>
+        /// 根据 Cpk 值返回过程能力等级(常用阈值)
+        /// </summary>
+        /// <param name="cpk">Cpk 值</param>
+        /// <returns>等级描述(中文)</returns>
+        public static string GetCpkRating(double cpk)
+        {
+            if (cpk >= 1.67) return "卓越";
+            if (cpk >= 1.33) return "良好";
+            if (cpk >= 1.00) return "可接受";
+            if (cpk >= 0.67) return "不足";
+            return "严重不足";
+        }
+
+        /// <summary>
+        /// 计算样本标准差(除以 n-1),适用于样本数据的离散程度估计。
+        /// 公式:sqrt( Sum((x - mean)^2) / (n - 1) )
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <returns>样本标准差</returns>
+        public static double CalculateStandardDeviation(IEnumerable<double> values)
+        {
+            var vals = values.ToArray();
+            var n = vals.Length;
+            if (n < 2) return 0.0;
+
+            var avg = vals.Average();
+            var sumSq = vals.Sum(v => Math.Pow(v - avg, 2));
+            // 使用样本标准差(除以 n-1)
+            return Math.Sqrt(sumSq / (n - 1));
+        }
+
+        /// <summary>
+        /// 计算极差(最大值 - 最小值)
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <returns>极差</returns>
+        public static double CalculateRange(IEnumerable<double> values)
+        {
+            var vals = values.ToArray();
+            if (vals.Length == 0) return 0.0;
+            return vals.Max() - vals.Min();
+        }
+
+        /// <summary>
+        /// 计算变异系数(标准差 / 平均值 * 100%)
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <returns>变异系数的百分比表示</returns>
+        public static double CalculateCV(IEnumerable<double> values)
+        {
+            var vals = values.ToArray();
+            var stdDev = CalculateStandardDeviation(vals);
+            var mean = vals.Average();
+            if (Math.Abs(mean) < double.Epsilon) return double.NaN; // 避免除以 0
+            return (stdDev / mean) * 100;  // 百分比
+        }
+
+        /// <summary>
+        /// 计算与理论值的绝对偏差(常用于评估准确性)
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <param name="theoreticalValue">理论或目标值</param>
+        /// <returns>平均值与理论值的绝对差</returns>
+        public static double CalculateAccuracy(IEnumerable<double> values, double theoreticalValue)
+        {
+            var mean = values.Average();
+            return Math.Abs(mean - theoreticalValue);
+        }
+
+        /// <summary>
+        /// 计算 95% 置信区间(基于正态近似,使用 z=1.96)
+        /// 置信区间 = mean ± 1.96 * s / sqrt(n)
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <returns>置信区间下限与上限</returns>
+        public static (double lower, double upper) Calculate95ConfidenceInterval(IEnumerable<double> values)
+        {
+            var vals = values.ToArray();
+            var n = vals.Length;
+            if (n == 0) return (0, 0);
+
+            var mean = vals.Average();
+            var stdDev = CalculateStandardDeviation(vals);
+            var margin = 1.96 * stdDev / Math.Sqrt(n);  // 1.96 对应 95% 置信度(正态分布近似)
+
+            return (mean - margin, mean + margin);
+        }
+
+        /// <summary>
+        /// 便捷方法:返回样本标准差
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <returns>样本标准差</returns>
+        public static double GetStdDeviation(IEnumerable<double> values)
+        {
+            return CalculateStandardDeviation(values);
+        }
+
+        /// <summary>
+        /// 返回平均值 ± 3σ 的上下限(约包含 99.73% 的正态分布数据)
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <returns>(lower, upper)</returns>
+        public static (double lower, double upper) Get3SigmaRange(IEnumerable<double> values)
+        {
+            var vals = values.ToArray();
+            if (vals.Length == 0) return (0, 0);
+
+            var mean = vals.Average();
+            var stdDev = CalculateStandardDeviation(vals);
+            return (mean - 3 * stdDev, mean + 3 * stdDev);
+        }
+
+        /// <summary>
+        /// 计算峰度(Kurtosis),使用 Fisher 定义(返回值在正态分布时为 0)
+        /// 公式(简化样本版本): (n * sum((x-mean)^4)) / (sum((x-mean)^2)^2) - 3
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <returns>峰度值</returns>
+        public static double CalculateKurtosis(IEnumerable<double> values)
+        {
+            var vals = values.ToArray();
+            var n = vals.Length;
+            if (n < 4) return 0.0; // 数据过少时峰度意义不大
+
+            var mean = vals.Average();
+            var sum4 = vals.Sum(v => Math.Pow(v - mean, 4));
+            var sum2 = vals.Sum(v => Math.Pow(v - mean, 2));
+
+            if (Math.Abs(sum2) < double.Epsilon) return 0.0;
+
+            // Fisher 峰度(正态分布为 0)
+            return (n * sum4) / Math.Pow(sum2, 2) - 3;
+        }
+
+        /// <summary>
+        /// 计算过程能力指标 Cp 与 Cpk
+        /// Cp = (USL - LSL) / (6 * sigma)
+        /// Cpk = min( (USL - mean) / (3 * sigma), (mean - LSL) / (3 * sigma) )
+        /// </summary>
+        /// <param name="values">输入数据序列</param>
+        /// <param name="lowerSpec">下规格限(LSL)</param>
+        /// <param name="upperSpec">上规格限(USL)</param>
+        /// <returns>(cp, cpk)</returns>
+        public static (double cp, double cpk) CalculateProcessCapability(
+            IEnumerable<double> values,
+            double lowerSpec,
+            double upperSpec)
+        {
+            var vals = values.ToArray();
+            var stdDev = CalculateStandardDeviation(vals);
+            var mean = vals.Average();
+
+            if (stdDev <= 0) return (double.NaN, double.NaN); // 避免除以 0
+
+            // Cp 反映公差带相对于总体变异的宽度
+            var cp = (upperSpec - lowerSpec) / (6 * stdDev);
+
+            // Cpk 考虑均值偏移,取靠近任一边的能力
+            var cpu = (upperSpec - mean) / (3 * stdDev);
+            var cpl = (mean - lowerSpec) / (3 * stdDev);
+            var cpk = Math.Min(cpu, cpl);
+
+            return (cp, cpk);
+        }
+    }
+}

+ 3 - 0
TeamAAS-VM/Resources/Languages/Lang.resx

@@ -2129,4 +2129,7 @@
   <data name="CameraMount_MobileDownIndependentXYPlatform" xml:space="preserve">
     <value>独立XY移动相机</value>
   </data>
+  <data name="视觉静态精度分析" xml:space="preserve">
+    <value>视觉静态精度分析</value>
+  </data>
 </root>

+ 13 - 5
TeamAAS-VM/Services/RemoteCommandService.cs

@@ -1752,13 +1752,21 @@ namespace TeamAAS_VP.Services
                     double _pixel_x = double.Parse(posParts[0]);
                     double _pixel_y = double.Parse(posParts[1]);
                     double _pixel_u = double.Parse(posParts[2]);
-                    //转换点位-像素转换成机器人绝对坐标
-                    var calibResult = _calibrationService.ConvertPixelToPosition((_pixel_x, _pixel_y, _pixel_u), robotCoord, calib, RobotBrand.XYZ_Platform);
-                    if (!calibResult.IsSucceed)
+                    if (calib!=null)
                     {
-                        return (false, 0, 0, 0);
+                        //转换点位-像素转换成机器人绝对坐标
+                        var calibResult = _calibrationService.ConvertPixelToPosition((_pixel_x, _pixel_y, _pixel_u), robotCoord, calib, RobotBrand.XYZ_Platform);
+                        if (!calibResult.IsSucceed)
+                        {
+                            return (false, 0, 0, 0);
+                        }
+                        return (true, calibResult.X, calibResult.Y, calibResult.U);
+                    }
+                    else
+                    {
+                        return (true, _pixel_x, _pixel_y, _pixel_u);
                     }
-                    return (true, calibResult.X, calibResult.Y, calibResult.U);
+                        
                 }
                 SendTaskMessage(Lang.多次拍照失败, MessageLevel.Alarm);
                 return (false, 0, 0, 0);

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

@@ -198,6 +198,9 @@
     <Reference Include="ICSharpCode.AvalonEdit, Version=6.3.0.90, Culture=neutral, PublicKeyToken=9cc39be672370310, processorArchitecture=MSIL">
       <HintPath>..\packages\AvalonEdit.6.3.0.90\lib\net462\ICSharpCode.AvalonEdit.dll</HintPath>
     </Reference>
+    <Reference Include="itextsharp, Version=5.5.13.4, Culture=neutral, PublicKeyToken=8354ae6d2174ddca, processorArchitecture=MSIL">
+      <HintPath>..\packages\iTextSharp.5.5.13.4\lib\net461\itextsharp.dll</HintPath>
+    </Reference>
     <Reference Include="log4net, Version=3.2.0.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
       <HintPath>..\packages\log4net.3.2.0\lib\net462\log4net.dll</HintPath>
     </Reference>
@@ -530,6 +533,7 @@
       <DependentUpon>PlcRobotManual.xaml</DependentUpon>
     </Compile>
     <Compile Include="Core\RectangleCenterCalculator.cs" />
+    <Compile Include="Core\StabilityAnalyzer.cs" />
     <Compile Include="Events\MainTabSwitchNotification.cs" />
     <Compile Include="Models\Feeder\ScrewFeederInfo.cs" />
     <Compile Include="Models\ScrewFeederBatchRecord.cs" />
@@ -537,6 +541,8 @@
     <Compile Include="ViewModels\Calibration\CalibIndependentCameraViewModel.cs" />
     <Compile Include="ViewModels\DebugMod\PlcRobotManualStepViewModel.cs" />
     <Compile Include="ViewModels\Product\ImageDisplayViewModel.cs" />
+    <Compile Include="ViewModels\Product\VisionDynamicAccuracyAnalyzerViewModel.cs" />
+    <Compile Include="ViewModels\Product\VisionStaticAccuracyAnalyzerViewModel.cs" />
     <Compile Include="ViewModels\Setting\AddScrewFeederViewModel.cs" />
     <Compile Include="ViewModels\Statistics\LockResultRecoredQueryViewModel.cs" />
     <Compile Include="Views\Calibration\CalibIndependentCamera.xaml.cs">
@@ -1015,9 +1021,15 @@
     <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>
+    <Compile Include="Views\Product\VisionStaticAccuracyAnalyzer.xaml.cs">
+      <DependentUpon>VisionStaticAccuracyAnalyzer.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Views\SettingView.xaml.cs">
       <DependentUpon>SettingView.xaml</DependentUpon>
     </Compile>
@@ -1386,10 +1398,18 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\Product\VisionDynamicAccuracyAnalyzer.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Views\Product\VisionScriptPage.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\Product\VisionStaticAccuracyAnalyzer.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Views\SettingView.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>

+ 47 - 9
TeamAAS-VM/ViewModels/Product/AdvancedProcedureViewModel.cs

@@ -55,11 +55,13 @@ namespace TeamAAS_VP.ViewModels.Product
         public ProcedureModel SelectProcedure
         {
             get { return _SelectProcedure; }
-            set { SetProperty(ref _SelectProcedure, value);
+            set
+            {
+                SetProperty(ref _SelectProcedure, value);
             }
         }
 
-        public CogToolBlockEditV2 ToolBlockEdit { get;set; }
+        public CogToolBlockEditV2 ToolBlockEdit { get; set; }
 
         private IRobot _Robot;
 
@@ -102,6 +104,10 @@ namespace TeamAAS_VP.ViewModels.Product
         public DelegateCommand RunVisionProcessCommand =>
             _RunVisionProcessCommand ?? (_RunVisionProcessCommand = new DelegateCommand(ExecuteRunVisionProcessCommand));
 
+        private DelegateCommand _VisionStaticAccuracyAnalyzerCommand;
+        public DelegateCommand VisionStaticAccuracyAnalyzerCommand =>
+            _VisionStaticAccuracyAnalyzerCommand ?? (_VisionStaticAccuracyAnalyzerCommand = new DelegateCommand(ExecuteVisionStaticAccuracyAnalyzerCommand));
+
         #endregion
 
         #region 事件
@@ -109,7 +115,7 @@ namespace TeamAAS_VP.ViewModels.Product
         #endregion
 
         public AdvancedProcedureViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
-            IFeederService feederService, IRobotService robotService,ICameraService cameraService, ISystemDatabaseService systemDatabaseService)
+            IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService)
         {
             _regionManager = regionManager;
             _eventAggregator = ea;
@@ -121,7 +127,7 @@ namespace TeamAAS_VP.ViewModels.Product
             _systemDatabaseService = systemDatabaseService;
             //订阅权限登录事件
             _eventAggregator.GetEvent<UserLoginNotification>().Subscribe(LoginChange);
-            
+
         }
 
         #region 方法
@@ -145,7 +151,7 @@ namespace TeamAAS_VP.ViewModels.Product
                 {
                     var management = _container.Resolve<Management>();
                     var Feeder = _feederService.GetFeeder(SelectProcedure.FeederId);
-                    
+
                     if (Feeder.FeederBrand == FeederBrand.AFAG)
                     {
                         _regionManager.RequestNavigate("ProductRegionContext", "AfagFeederParams", param);
@@ -158,7 +164,7 @@ namespace TeamAAS_VP.ViewModels.Product
             }
             catch (Exception ex)
             {
-                LogHelper.WriteLogError(ex.Message,ex);
+                LogHelper.WriteLogError(ex.Message, ex);
             }
         }
 
@@ -197,17 +203,17 @@ namespace TeamAAS_VP.ViewModels.Product
             try
             {
                 SelectProcedure.ToolBlock = ToolBlockEdit.Subject;
-                var image= Camera.Grab();
+                var image = Camera.Grab();
                 if (image != null)
                 {
                     ToolBlockEdit.Subject.Inputs["InputImage"].Value = image;
                     ToolBlockEdit.Subject.Run();
-                    
+
                     Message = $"{Lang.图像采集耗时}:{Camera.TotalTime.TotalMilliseconds.ToString("F2")} ms {Lang.工具运行耗时}:{ToolBlockEdit.Subject.RunStatus.TotalTime.ToString("F2")} ms";
                 }
                 else
                 {
-                    Message=Lang.图像采集失败耗时.Replace("{0}", Camera.TotalTime.TotalMilliseconds.ToString("F2")).Replace("{1}", Camera.ErrorMessage);  
+                    Message = Lang.图像采集失败耗时.Replace("{0}", Camera.TotalTime.TotalMilliseconds.ToString("F2")).Replace("{1}", Camera.ErrorMessage);
                 }
             }
             catch (Exception ex)
@@ -216,6 +222,38 @@ namespace TeamAAS_VP.ViewModels.Product
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
             }
         }
+
+        /// <summary>
+        /// 执行视觉静态精度分析仪命令
+        /// </summary>
+        void ExecuteVisionStaticAccuracyAnalyzerCommand()
+        {
+            try
+            {
+                IDialogParameters parameters = new DialogParameters();
+                parameters.Add("ToolBlock", ToolBlockEdit.Subject);
+                parameters.Add("SelectedProcedure", SelectProcedure);
+                _dialogService.Show("VisionStaticAccuracyAnalyzer", parameters, rst =>
+                {
+                    //对话框关闭之后的回调函数,可以在这解析结果。
+                    ButtonResult result1 = rst.Result;
+
+                    if (result1 == ButtonResult.OK)
+                    {
+                        //var param = rst.Parameters.GetValue<RobotTool>("Tool");
+                        //var param1 = rst.Parameters.GetValue<Matrix<double>>("RobotCameraRotationMatrix");
+                        //var param2 = rst.Parameters.GetValue<double>("Angle");
+                        _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+                    }
+
+                });
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("执行视觉静态精度分析仪命令时出错", ex);
+                MessageBox.Show(ex.Message);
+            }
+        }
         #endregion
 
         #region 继承

+ 36 - 0
TeamAAS-VM/ViewModels/Product/PlcPointParamsViewModel.cs

@@ -429,6 +429,10 @@ namespace TeamAAS_VP.ViewModels.Product
         public DelegateCommand<string> CalPickPosCommand =>
             _CalPickPosCommand ?? (_CalPickPosCommand = new DelegateCommand<string>(ExecuteCalPickPosCommand));
 
+        private DelegateCommand _DynamicTestAnalyzerCommand;
+        public DelegateCommand DynamicTestAnalyzerCommand =>
+            _DynamicTestAnalyzerCommand ?? (_DynamicTestAnalyzerCommand = new DelegateCommand(ExecuteDynamicTestAnalyzerCommand));
+
         #endregion
 
         #region 事件
@@ -1881,6 +1885,38 @@ namespace TeamAAS_VP.ViewModels.Product
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
             }
         }
+
+        /// <summary>
+        /// 动态测试分析仪
+        /// </summary>
+        void ExecuteDynamicTestAnalyzerCommand()
+        {
+            try
+            {
+                IDialogParameters parameters = new DialogParameters();
+                parameters.Add("SelectProduct", SelectProduct);
+                parameters.Add("Robot", Robot);
+                _dialogService.Show("VisionDynamicAccuracyAnalyzer", parameters, rst =>
+                {
+                    //对话框关闭之后的回调函数,可以在这解析结果。
+                    ButtonResult result1 = rst.Result;
+
+                    if (result1 == ButtonResult.OK)
+                    {
+                        //var param = rst.Parameters.GetValue<RobotTool>("Tool");
+                        //var param1 = rst.Parameters.GetValue<Matrix<double>>("RobotCameraRotationMatrix");
+                        //var param2 = rst.Parameters.GetValue<double>("Angle");
+                        _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+                    }
+
+                });
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("执行动态测试分析仪命令时出错", ex);
+                MessageBox.Show(ex.Message);
+            }
+        }
         #endregion
 
         #region 继承

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

@@ -0,0 +1,1890 @@
+using Cognex.VisionPro;
+using Cognex.VisionPro.ToolBlock;
+using MaterialDesignThemes.Wpf;
+using Newtonsoft.Json;
+using OxyPlot;
+using Prism.Commands;
+using Prism.Events;
+using Prism.Ioc;
+using Prism.Mvvm;
+using Prism.Regions;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Team.FFFeederService.Interfaces;
+using TeamAAS_VP.Core;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Resources.Languages;
+using static TeamAAS_VP.Core.StabilityAnalyzer;
+
+namespace TeamAAS_VP.ViewModels.Product
+{
+    public class VisionDynamicAccuracyAnalyzerViewModel : BindableBase, IDialogAware
+    {
+        IRegionManager _regionManager;
+        IEventAggregator _eventAggregator;
+        IContainerProvider _container;
+        IDialogService _dialogService;
+        IFeederService _feederService;
+        IRobotService _robotService;
+        ICameraService _cameraService;
+        ISystemDatabaseService _systemDatabaseService;
+        ICalibrationService _calibrationService;
+
+        //测试过程中控制取消的CTS
+        CancellationTokenSource _cts;
+
+        #region 属性
+
+        private ProductModel _SelectProduct;
+        public ProductModel SelectProduct
+        {
+            get { return _SelectProduct; }
+            set { SetProperty(ref _SelectProduct, value); }
+        }
+
+        private ICogImage _Image;
+
+        public ICogImage Image
+        {
+            get { return _Image; }
+            set { SetProperty(ref _Image, value); }
+        }
+
+        private Cognex.VisionPro.CogGraphicCollection _Graphic;
+
+        public Cognex.VisionPro.CogGraphicCollection Graphic
+        {
+            get { return _Graphic; }
+            set { SetProperty(ref _Graphic, value); }
+        }
+
+        private ProcedureModel _SelectProcedure;
+
+        /// <summary>
+        /// 选中的流程
+        /// </summary>
+        public ProcedureModel SelectProcedure
+        {
+            get { return _SelectProcedure; }
+            set
+            {
+                SetProperty(ref _SelectProcedure, value);
+            }
+        }
+
+        private ICamera _Camera;
+
+        public ICamera Camera
+        {
+            get { return _Camera; }
+            set { SetProperty(ref _Camera, value); }
+        }
+
+        private IRobot _Robot;
+
+        public IRobot Robot
+        {
+            get { return _Robot; }
+            set { SetProperty(ref _Robot, value); }
+        }
+
+        private string _Message = "等待开始...";
+        public string Message
+        {
+            get { return _Message; }
+            set { SetProperty(ref _Message, value); }
+        }
+
+        private bool _IsRunning;
+
+        public bool IsRunning
+        {
+            get { return _IsRunning; }
+            set { SetProperty(ref _IsRunning, value); }
+        }
+
+        private CogToolBlock _VisionTool;
+        public CogToolBlock VisionTool
+        {
+            get { return _VisionTool; }
+            set { SetProperty(ref _VisionTool, value); }
+        }
+
+        private int _RepeatCount = 10;
+        /// <summary>
+        /// 重复次数
+        /// </summary>
+        public int RepeatCount
+        {
+            get { return _RepeatCount; }
+            set { SetProperty(ref _RepeatCount, value); }
+        }
+
+        private int _CurrentProgress;
+        /// <summary>
+        /// 当前进度
+        /// </summary>
+        public int CurrentProgress
+        {
+            get { return _CurrentProgress; }
+            set { SetProperty(ref _CurrentProgress, value); }
+        }
+
+        private int _SuccessCount;
+        /// <summary>
+        /// 成功次数
+        /// </summary>
+        public int SuccessCount
+        {
+            get { return _SuccessCount; }
+            set { SetProperty(ref _SuccessCount, value); }
+        }
+
+        private int _FailCount;
+        /// <summary>
+        /// 失败次数
+        /// </summary>
+        public int FailCount
+        {
+            get { return _FailCount; }
+            set { SetProperty(ref _FailCount, value); }
+        }
+
+        private double _SuccessRate;
+        /// <summary>
+        /// 成功率
+        /// </summary>
+        public double SuccessRate
+        {
+            get { return _SuccessRate; }
+            set { SetProperty(ref _SuccessRate, value); }
+        }
+
+        private ObservableCollection<DynamicTestResult> _TestResults;
+        /// <summary>
+        /// 测试结果列表
+        /// </summary>
+        public ObservableCollection<DynamicTestResult> TestResults
+        {
+            get { return _TestResults; }
+            set { SetProperty(ref _TestResults, value); }
+        }
+
+        private SnackbarMessageQueue _MessageQueue;
+        public SnackbarMessageQueue MessageQueue
+        {
+            get { return _MessageQueue; }
+            set { SetProperty(ref _MessageQueue, value); }
+        }
+
+        private RPoint _PointA;
+        public RPoint PointA
+        {
+            get { return _PointA; }
+            set { SetProperty(ref _PointA, value); }
+        }
+
+        private RPoint _PointB;
+        public RPoint PointB
+        {
+            get { return _PointB; }
+            set { SetProperty(ref _PointB, value); }
+        }
+
+        private ObservableCollection<ProcedureModel> _ProcedureModels;
+
+        /// <summary>
+        /// 流程集合
+        /// </summary>
+        public ObservableCollection<ProcedureModel> ProcedureModels
+        {
+            get { return _ProcedureModels; }
+            set { SetProperty(ref _ProcedureModels, value); }
+        }
+
+        private int _MoveSpeed=100;
+        /// <summary>
+        /// 移动速度
+        /// </summary>
+        public int MoveSpeed
+        {
+            get { return _MoveSpeed; }
+            set { SetProperty(ref _MoveSpeed, value); }
+        }
+
+        private double _CycleTime;
+        /// <summary>
+        /// 单次的时长
+        /// </summary>
+        public double CycleTime
+        {
+            get { return _CycleTime; }
+            set { SetProperty(ref _CycleTime, value); }
+        }
+
+        private PrecisionAnalysisResult _precisionResult = new PrecisionAnalysisResult();
+        /// <summary>
+        /// 精度分析结果
+        /// </summary>
+        public PrecisionAnalysisResult PrecisionResult
+        {
+            get { return _precisionResult; }
+            set { SetProperty(ref _precisionResult, value); }
+        }
+        #endregion
+
+        #region 命令
+        private DelegateCommand _ConfirmCommand;
+        public DelegateCommand ConfirmCommand =>
+            _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _CancelCommand;
+        public DelegateCommand CancelCommand =>
+            _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _StartTestCommand;
+        public DelegateCommand StartTestCommand =>
+            _StartTestCommand ?? (_StartTestCommand = new DelegateCommand(ExecuteStartTestCommand, CanExecuteStartTestCommand).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectProcedure).ObservesProperty(() => PointA).ObservesProperty(() => PointB));
+
+        private DelegateCommand _StopTestCommand;
+        public DelegateCommand StopTestCommand =>
+            _StopTestCommand ?? (_StopTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
+
+        //暂停测试命令
+        private DelegateCommand _PauseTestCommand;
+        public DelegateCommand PauseTestCommand =>
+            _PauseTestCommand ?? (_PauseTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _ExportCSVCommand;
+        public DelegateCommand ExportCSVCommand =>
+            _ExportCSVCommand ?? (_ExportCSVCommand = new DelegateCommand(ExecuteExportCSVCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _ExportReportCommand;
+        public DelegateCommand ExportReportCommand =>
+            _ExportReportCommand ?? (_ExportReportCommand = new DelegateCommand(ExecuteExportReportCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _ClearDataCommand;
+        public DelegateCommand ClearDataCommand =>
+            _ClearDataCommand ?? (_ClearDataCommand = new DelegateCommand(ExecuteClearDataCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _TeachPointACommand;
+        public DelegateCommand TeachPointACommand =>
+            _TeachPointACommand ?? (_TeachPointACommand = new DelegateCommand(ExecuteTeachPointACommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _TeachPointBCommand;
+        public DelegateCommand TeachPointBCommand =>
+            _TeachPointBCommand ?? (_TeachPointBCommand = new DelegateCommand(ExecuteTeachPointBCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _ProcedureSelectionChangedCommand;
+        public DelegateCommand ProcedureSelectionChangedCommand =>
+            _ProcedureSelectionChangedCommand ?? (_ProcedureSelectionChangedCommand = new DelegateCommand(ExecuteProcedureSelectionChangedCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _SingleTestCommand;
+        public DelegateCommand SingleTestCommand =>
+            _SingleTestCommand ?? (_SingleTestCommand = new DelegateCommand(ExecuteSingleTestCommand, () => !IsRunning && SelectProcedure != null && PointA != null && PointB != null).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectProcedure).ObservesProperty(() => PointA).ObservesProperty(() => PointB));
+
+        private DelegateCommand _ShowTrendChartCommand;
+        public DelegateCommand ShowTrendChartCommand =>
+            _ShowTrendChartCommand ?? (_ShowTrendChartCommand = new DelegateCommand(ExecuteShowTrendChartCommand));
+
+
+        #endregion
+
+        #region 事件
+
+        #endregion
+
+        public VisionDynamicAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
+            IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService)
+        {
+            _regionManager = regionManager;
+            _eventAggregator = ea;
+            _container = container;
+            _dialogService = dialogService;
+            _feederService = feederService;
+            _robotService = robotService;
+            _cameraService = cameraService;
+            _systemDatabaseService = systemDatabaseService;
+            MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
+            _calibrationService = calibrationService;
+        }
+
+        #region 方法
+        /// <summary>
+        /// 确定
+        /// </summary>
+        void ExecuteConfirmCommand()
+        {
+            _cts?.Cancel();
+
+            IDialogParameters parameters = new DialogParameters();
+            //parameters.Add("Tool", Tool);
+            //parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
+            //parameters.Add("Angle", Angle);
+            RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
+        }
+
+        /// <summary>
+        /// 取消
+        /// </summary>
+        void ExecuteCancelCommand()
+        {
+            _cts?.Cancel();
+            IDialogParameters parameters = new DialogParameters();
+            //parameters.Add("Tool", Tool);
+            RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
+        }
+
+        /// <summary>
+        /// 单次测试
+        /// </summary>
+        async void ExecuteSingleTestCommand()
+        {
+            if (Robot == null || !Robot.IsConnected)
+            {
+                SendTaskMessage("机器人未连接,无法示教!");
+                return;
+            }
+
+            if (Camera == null || !Camera.IsConnected)
+            {
+                SendTaskMessage("相机未连接,无法示教!");
+                return;
+            }
+
+            //弹窗提示是否进行单次测试
+            var result = System.Windows.MessageBox.Show("确定进行单次测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
+            if (result != System.Windows.MessageBoxResult.Yes)
+            {
+                return;
+            }
+
+            IsRunning = true;
+            try
+            {
+                DateTime startTime = DateTime.Now;
+                //为机器人设置移动速度
+                await Robot.CalibParameAsync(new PickInfo(), MoveSpeed,0,false,0,0);
+
+                //移动机器人至A点
+                bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
+                if (!isSuccess1)
+                {
+                    SendTaskMessage("机器人移动到A点失败!");
+                    return;
+                }
+                await Task.Delay(200);
+
+                //移动机器人至B点
+                isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
+                if (!isSuccess1)
+                {
+                    SendTaskMessage("机器人移动到B点失败!");
+                    return;
+                }
+                await Task.Delay(300);
+                //相机拍照并处理
+                (bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY) = await ExecutePhotoEx(CurrentProgress);
+
+                CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("机器人移动到A点失败!", ex);
+                SendTaskMessage("机器人移动到A点失败!");
+            }
+            finally
+            {
+                IsRunning = false;
+            }
+
+        }
+
+        /// <summary>
+        /// 连续测试
+        /// </summary>
+        async void ExecuteStartTestCommand()
+        {
+            IsRunning = true;
+            _cts = new CancellationTokenSource();
+            CurrentProgress = 0;
+            SuccessCount = 0;
+            SuccessRate = 0;
+            FailCount = 0;
+            Message = "测试进行中...";
+            TestResults = new ObservableCollection<DynamicTestResult>();
+
+            try
+            {
+                //弹窗提示是否进行连续测试
+                var result = System.Windows.MessageBox.Show("确定进行连续测试吗?", "提示", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
+                if (result != System.Windows.MessageBoxResult.Yes)
+                {
+                    return;
+                }
+
+                //为机器人设置移动速度
+                await Robot.CalibParameAsync(new PickInfo(), MoveSpeed, 0, false, 0, 0);
+
+                while (CurrentProgress < RepeatCount)
+                {
+                    if (_cts.Token.IsCancellationRequested)
+                    {
+                        Message = "测试已取消!";
+                        break;
+                    }
+                    DateTime startTime = DateTime.Now;
+
+                    //移动机器人至A点
+                    bool isSuccess1 = await Robot.CalibMotionAsync(PointA, null);
+                    if (!isSuccess1)
+                    {
+                        SendTaskMessage("机器人移动到A点失败!");
+                        return;
+                    }
+                    await Task.Delay(200);
+                    if (_cts.Token.IsCancellationRequested)
+                    {
+                        Message = "测试已取消!";
+                        break;
+                    }
+
+                    //移动机器人至B点
+                    isSuccess1 = await Robot.CalibMotionAsync(PointB, null);
+                    if (!isSuccess1)
+                    {
+                        SendTaskMessage("机器人移动到B点失败!");
+                        return;
+                    }
+                    await Task.Delay(300);
+                    if (_cts.Token.IsCancellationRequested)
+                    {
+                        Message = "测试已取消!";
+                        break;
+                    }
+
+                    //相机拍照并处理
+                    (bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY) = await ExecutePhotoEx(CurrentProgress);
+                    CycleTime = DateTime.Now.Subtract(startTime).TotalSeconds;
+
+                    if (isSuccess)
+                    {
+                        SuccessCount++;
+                    }
+                    else
+                    {
+                        FailCount++;
+                    }
+                    TestResults.Add(new DynamicTestResult(CurrentProgress,isSuccess,PointB,CycleTime,pixelX,pixelY,angleU,transformX,transformY));
+                    OnTestResultAdded();
+
+                    CurrentProgress++;
+                    SuccessRate = (double)SuccessCount / CurrentProgress * 100;
+                    SendTaskMessage($"测试进行中... {CurrentProgress}/{RepeatCount}");
+                }
+                if (CurrentProgress >= RepeatCount)
+                {
+                    Message = "测试完成!";
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("视觉静态重复测试时出错!", ex);
+            }
+            finally
+            {
+                IsRunning = false;
+            }
+        }
+
+        bool CanExecuteStartTestCommand()
+        {
+            return !IsRunning && SelectProcedure != null && PointA != null && PointB != null;
+        }
+
+        /// <summary>
+        /// 清除数据
+        /// </summary>
+        void ExecuteClearDataCommand()
+        {
+            if (TestResults != null)
+            {
+                TestResults.Clear();
+            }
+        }
+
+        /// <summary>
+        /// 导出测试报告
+        /// 使用 iTextSharp 生成 PDF,包含:标题、测试摘要、数据表格、每列稳定性分析结果
+        /// 详细伪代码:
+        /// 1. 校验 TestResults 是否存在且有数据,若无则提示并返回。
+        /// 2. 弹出保存对话框,获取 PDF 保存路径,若取消则返回。
+        /// 3. 使用 iTextSharp 创建 Document 和 PdfWriter,打开文档流。
+        /// 4. 尝试从系统字体目录加载中文字体(多候选),若加载失败使用回退字体。
+        /// 5. 写入标题(居中大号字体)和测试摘要信息(表格格式:导出时间、目标次数、当前进度、成功/失败次数、成功率)。
+        /// 6. 创建一个 PdfPTable,列为固定的测试结果字段(Index, Timestamp, IsPhotoSuccess, PointB.X/Y/Z, CycleTime, PixelX, PixelY, AngleU, TransformX, TransformY)。
+        ///    - 写入表头(加粗)
+        ///    - 遍历 TestResults,每行按列顺序写入值(数值按 InvariantCulture 格式化,空值写空字符串)
+        /// 7. 新页,写入“分析结果”标题。
+        /// 8. 对于需要分析的维度(TransformX, TransformY, AngleU):
+        ///    - 从 TestResults 过滤出 IsPhotoSuccess 为 true 的样本并收集对应维度值(double)
+        ///    - 若样本数为0,写明无法计算;否则调用 StabilityAnalyzer.AnalyzeStability(values) 获取 StabilityMetrics
+        ///    - 将 StabilityMetrics 的公开属性写成两列表(属性名 / 值)写入 PDF
+        /// 9. 关闭并释放文档、writer、流,提示导出成功;捕获异常记录日志并提示失败。
+        /// 10. 使用 SendTaskMessage 在 UI 上通知结果。
+        /// </summary>
+        void ExecuteExportReportCommand()
+        {
+            try
+            {
+                if (TestResults == null || TestResults.Count == 0)
+                {
+                    SendTaskMessage("无测试数据,无法导出报告。");
+                    return;
+                }
+
+                var dlg = new Microsoft.Win32.SaveFileDialog
+                {
+                    DefaultExt = "pdf",
+                    Filter = "PDF 文件 (*.pdf)|*.pdf|所有文件 (*.*)|*.*",
+                    FileName = "TestReport.pdf",
+                    Title = "保存测试报告为 PDF"
+                };
+
+                bool? dlgResult = dlg.ShowDialog();
+                if (dlgResult != true)
+                    return;
+
+                string path = dlg.FileName;
+
+                // 创建文档(A4, 边距)
+                var doc = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 36, 36, 54, 54);
+                using (var fs = System.IO.File.Create(path))
+                {
+                    var writer = iTextSharp.text.pdf.PdfWriter.GetInstance(doc, fs);
+                    doc.Open();
+
+                    // 字体加载,优先中文系统字体
+                    iTextSharp.text.Font titleFont;
+                    iTextSharp.text.Font headerFont;
+                    iTextSharp.text.Font normalFont;
+
+                    iTextSharp.text.pdf.BaseFont baseFont = null;
+                    try
+                    {
+                        var fontsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
+                        var candidates = new[]
+                        {
+                            "msyh.ttf",
+                            "msyhbd.ttf",
+                            "simsun.ttc,0",
+                            "simsun.ttc,1",
+                            "Microsoft YaHei.ttf",
+                            "msyh.ttc,0",
+                            "msyh.ttc,1",
+                            "simhei.ttf"
+                        };
+                        foreach (var f in candidates)
+                        {
+                            var path1 = Path.Combine(fontsFolder, f);
+                            try
+                            {
+                                baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(
+                                    path1,
+                                    iTextSharp.text.pdf.BaseFont.IDENTITY_H,
+                                    iTextSharp.text.pdf.BaseFont.EMBEDDED
+                                );
+                                if (baseFont != null)
+                                    break;
+                            }
+                            catch
+                            {
+                                continue;
+                            }
+                        }
+                    }
+                    catch
+                    {
+                        baseFont = null;
+                    }
+
+                    if (baseFont != null)
+                    {
+                        titleFont = new iTextSharp.text.Font(baseFont, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
+                        headerFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
+                        normalFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
+                    }
+                    else
+                    {
+                        titleFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
+                        headerFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
+                        normalFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
+                    }
+
+
+                    // 标题
+                    var title = new iTextSharp.text.Paragraph("视觉动态精度测试报告", titleFont)
+                    {
+                        Alignment = iTextSharp.text.Element.ALIGN_CENTER,
+                        SpacingAfter = 12f
+                    };
+                    doc.Add(title);
+
+                    // 测试摘要
+                    var metaTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 100f };
+                    metaTable.SetWidths(new float[] { 1f, 2f });
+
+                    void AddMeta(string name, string value)
+                    {
+                        var cellName = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(name, headerFont)) { Border = 0, PaddingBottom = 6f };
+                        var cellVal = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(value, normalFont)) { Border = 0, PaddingBottom = 6f };
+                        metaTable.AddCell(cellName);
+                        metaTable.AddCell(cellVal);
+                    }
+
+                    AddMeta("导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
+                    AddMeta("测试次数(目标)", RepeatCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
+                    AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
+                    AddMeta("成功次数", SuccessCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
+                    AddMeta("失败次数", FailCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
+                    AddMeta("成功率(%)", SuccessRate.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
+                    doc.Add(metaTable);
+
+                    doc.Add(new iTextSharp.text.Paragraph(" ")); // 空行
+
+                    // 数据表格列定义
+                    var headers = new[]
+                    {
+                        "Index",
+                        "Timestamp",
+                        "IsPhotoSuccess",
+                        "PointB.X",
+                        "PointB.Y",
+                        "PointB.Z",
+                        "CycleTime(s)",
+                        "PixelX",
+                        "PixelY",
+                        "AngleU",
+                        "TransformX",
+                        "TransformY"
+                    };
+
+                    var table = new iTextSharp.text.pdf.PdfPTable(headers.Length) { WidthPercentage = 100f };
+                    foreach (var h in headers)
+                    {
+                        var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(h, headerFont))
+                        {
+                            HorizontalAlignment = iTextSharp.text.Element.ALIGN_CENTER,
+                            BackgroundColor = new iTextSharp.text.BaseColor(230, 230, 230),
+                            Padding = 4f
+                        };
+                        table.AddCell(cell);
+                    }
+
+                    // 写入每一行数据
+                    foreach (var r in TestResults)
+                    {
+                        string sIndex = r.Index.ToString(System.Globalization.CultureInfo.InvariantCulture);
+                        string sTime = r.Timestamp.ToString("o", System.Globalization.CultureInfo.InvariantCulture);
+                        string sSucc = r.IsPhotoSuccess ? "True" : "False";
+
+                        string sPx = "";
+                        string sPy = "";
+                        string sPz = "";
+                        if (r.PointB != null)
+                        {
+                            sPx = Convert.ToString(r.PointB.X, System.Globalization.CultureInfo.InvariantCulture);
+                            sPy = Convert.ToString(r.PointB.Y, System.Globalization.CultureInfo.InvariantCulture);
+                            sPz = Convert.ToString(r.PointB.Z, System.Globalization.CultureInfo.InvariantCulture);
+                        }
+
+                        string sCycle = Convert.ToString(r.CycleTime, System.Globalization.CultureInfo.InvariantCulture);
+                        string sPixelX = Convert.ToString(r.PixelX, System.Globalization.CultureInfo.InvariantCulture);
+                        string sPixelY = Convert.ToString(r.PixelY, System.Globalization.CultureInfo.InvariantCulture);
+                        string sAngle = Convert.ToString(r.AngleU, System.Globalization.CultureInfo.InvariantCulture);
+                        string sTx = Convert.ToString(r.TransformX, System.Globalization.CultureInfo.InvariantCulture);
+                        string sTy = Convert.ToString(r.TransformY, System.Globalization.CultureInfo.InvariantCulture);
+
+                        var rowVals = new[] { sIndex, sTime, sSucc, sPx, sPy, sPz, sCycle, sPixelX, sPixelY, sAngle, sTx, sTy };
+                        foreach (var v in rowVals)
+                        {
+                            var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(v ?? "", normalFont)) { Padding = 4f };
+                            table.AddCell(cell);
+                        }
+                    }
+                    doc.Add(table);
+
+                    // 新页,写入分析结果
+                    doc.NewPage();
+                    var analysisTitle = new iTextSharp.text.Paragraph("分析结果", titleFont) { SpacingAfter = 8f };
+                    doc.Add(analysisTitle);
+
+                    var dims = new[]
+                    {
+                        new { Name = "TransformX", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformX).ToArray() },
+                        new { Name = "TransformY", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformY).ToArray() },
+                        new { Name = "AngleU",     Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.AngleU).ToArray() }
+                    };
+
+                    foreach (var dim in dims)
+                    {
+                        try
+                        {
+                            var header = new iTextSharp.text.Paragraph(dim.Name, headerFont) { SpacingBefore = 6f, SpacingAfter = 4f };
+                            doc.Add(header);
+
+                            if (dim.Values == null || dim.Values.Length == 0)
+                            {
+                                doc.Add(new iTextSharp.text.Paragraph("样本数为 0,无法计算。", normalFont));
+                                continue;
+                            }
+
+                            StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dim.Values);
+
+                            var metricsTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 60f, SpacingAfter = 6f };
+                            metricsTable.SetWidths(new float[] { 1f, 1f });
+
+                            var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
+                            foreach (var p in props)
+                            {
+                                object pv = p.GetValue(metrics);
+                                string pvStr;
+                                if (pv == null) pvStr = "";
+                                else if (pv is double) pvStr = ((double)pv).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
+                                else pvStr = pv.ToString();
+
+                                var pc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(p.Name, normalFont)) { Padding = 4f };
+                                var vc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(pvStr, normalFont)) { Padding = 4f };
+                                metricsTable.AddCell(pc);
+                                metricsTable.AddCell(vc);
+                            }
+                            doc.Add(metricsTable);
+                        }
+                        catch (Exception exCol)
+                        {
+                            LogHelper.WriteLogError($"导出报告时处理维度[{dim.Name}]出错", exCol);
+                            doc.Add(new iTextSharp.text.Paragraph($"处理维度 {dim.Name} 时出错: {exCol.Message}", normalFont));
+                        }
+                    }
+
+                    doc.Close();
+                    writer.Close();
+                }
+
+                SendTaskMessage($"已导出测试报告到:{path}");
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("导出测试报告时出错", ex);
+                SendTaskMessage($"导出测试报告失败:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 导出测试数据为CSV文件
+        /// </summary>
+        void ExecuteExportCSVCommand()
+        {
+            try
+            {
+                if (TestResults == null || TestResults.Count == 0)
+                {
+                    SendTaskMessage("无测试数据,无法导出。");
+                    return;
+                }
+
+                var dlg = new Microsoft.Win32.SaveFileDialog
+                {
+                    DefaultExt = "csv",
+                    Filter = "CSV 文件 (*.csv)|*.csv|所有文件 (*.*)|*.*",
+                    FileName = "TestResults.csv",
+                    Title = "保存测试结果为 CSV"
+                };
+
+                bool? dlgResult = dlg.ShowDialog();
+                if (dlgResult != true)
+                    return;
+
+                string path = dlg.FileName;
+                var sb = new System.Text.StringBuilder();
+
+                // CSV 字段转义
+                Func<string, string> EscapeCsv = (s) =>
+                {
+                    if (s == null) return "";
+                    bool mustQuote = s.Contains(",") || s.Contains("\"") || s.Contains("\r") || s.Contains("\n");
+                    string esc = s.Replace("\"", "\"\"");
+                    return mustQuote ? $"\"{esc}\"" : esc;
+                };
+
+                // 写入表头
+                var headers = new[]
+                {
+                    "Index",
+                    "Timestamp",
+                    "IsPhotoSuccess",
+                    "PointB.X",
+                    "PointB.Y",
+                    "PointB.Z",
+                    "CycleTime",
+                    "PixelX",
+                    "PixelY",
+                    "AngleU",
+                    "TransformX",
+                    "TransformY"
+                };
+                sb.AppendLine(string.Join(",", headers.Select(h => EscapeCsv(h))));
+
+                // 写入数据行
+                foreach (var r in TestResults)
+                {
+                    var cols = new List<string>();
+
+                    cols.Add(EscapeCsv(r.Index.ToString()));
+                    cols.Add(EscapeCsv(r.Timestamp.ToString("o"))); // ISO 8601 格式
+                    cols.Add(EscapeCsv(r.IsPhotoSuccess ? "True" : "False"));
+
+                    if (r.PointB != null)
+                    {
+                        // 假设 RPoint 有 X,Y,Z 属性(若命名不同需调整)
+                        cols.Add(EscapeCsv(Convert.ToString(r.PointB.X, System.Globalization.CultureInfo.InvariantCulture)));
+                        cols.Add(EscapeCsv(Convert.ToString(r.PointB.Y, System.Globalization.CultureInfo.InvariantCulture)));
+                        cols.Add(EscapeCsv(Convert.ToString(r.PointB.Z, System.Globalization.CultureInfo.InvariantCulture)));
+                    }
+                    else
+                    {
+                        cols.Add("");
+                        cols.Add("");
+                        cols.Add("");
+                    }
+
+                    cols.Add(EscapeCsv(Convert.ToString(r.CycleTime, System.Globalization.CultureInfo.InvariantCulture)));
+                    cols.Add(EscapeCsv(Convert.ToString(r.PixelX, System.Globalization.CultureInfo.InvariantCulture)));
+                    cols.Add(EscapeCsv(Convert.ToString(r.PixelY, System.Globalization.CultureInfo.InvariantCulture)));
+                    cols.Add(EscapeCsv(Convert.ToString(r.AngleU, System.Globalization.CultureInfo.InvariantCulture)));
+                    cols.Add(EscapeCsv(Convert.ToString(r.TransformX, System.Globalization.CultureInfo.InvariantCulture)));
+                    cols.Add(EscapeCsv(Convert.ToString(r.TransformY, System.Globalization.CultureInfo.InvariantCulture)));
+
+                    sb.AppendLine(string.Join(",", cols));
+                }
+
+                // 在末尾追加分析结果
+                sb.AppendLine();
+                sb.AppendLine("分析结果");
+                sb.AppendLine("维度,指标,值");
+
+                // 要分析的维度:TransformX, TransformY, AngleU
+                var dims = new[]
+                {
+                    new { Name = "TransformX", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformX).ToArray() },
+                    new { Name = "TransformY", Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.TransformY).ToArray() },
+                    new { Name = "AngleU",    Values = TestResults.Where(t => t.IsPhotoSuccess).Select(t => t.AngleU).ToArray() }
+                };
+
+                foreach (var dim in dims)
+                {
+                    try
+                    {
+                        if (dim.Values == null || dim.Values.Length == 0)
+                        {
+                            sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv("样本数")},{EscapeCsv("0")}");
+                            continue;
+                        }
+
+                        // 调用稳定性分析
+                        StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dim.Values);
+
+                        // 反射写入公开属性
+                        var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
+                        foreach (var p in props)
+                        {
+                            object pv = p.GetValue(metrics);
+                            string pvStr;
+                            if (pv == null) pvStr = "";
+                            else if (pv is double) pvStr = Convert.ToString((double)pv, System.Globalization.CultureInfo.InvariantCulture);
+                            else pvStr = pv.ToString();
+
+                            sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv(p.Name)},{EscapeCsv(pvStr)}");
+                        }
+                    }
+                    catch (Exception exCol)
+                    {
+                        LogHelper.WriteLogError($"导出分析结果时处理维度[{dim.Name}]出错", exCol);
+                        sb.AppendLine($"{EscapeCsv(dim.Name)},{EscapeCsv("Error")},{EscapeCsv(exCol.Message)}");
+                    }
+                }
+
+                // 写入文件(UTF8,无 BOM)
+                System.IO.File.WriteAllText(path, sb.ToString(), System.Text.Encoding.UTF8);
+
+                SendTaskMessage($"已导出测试结果到:{path}");
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("导出CSV时出错", ex);
+                SendTaskMessage($"导出CSV失败:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 选中视觉流程改变时
+        /// </summary>
+        void ExecuteProcedureSelectionChangedCommand()
+        {
+            if (SelectProcedure == null)
+            {
+                return;
+            }
+
+            //获取相机
+            Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
+            VisionTool = SelectProcedure.ToolBlock;
+        }
+
+        /// <summary>
+        /// 示教点B
+        /// </summary>
+        void ExecuteTeachPointBCommand()
+        {
+            if (Robot == null || !Robot.IsConnected)
+            {
+                SendTaskMessage("机器人未连接,无法示教!");
+                return;
+            }
+
+            try
+            {
+                var position = Robot.GetRobotPos();
+                if (position != null)
+                {
+                    PointB = position.Clone();
+                    SendTaskMessage($"已示教点B:X={PointB.X:F2} Y={PointB.Y:F2} Z={PointB.Z:F2}");
+                }
+                else
+                {
+                    SendTaskMessage("获取机器人当前位置失败,无法示教点B!");
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("示教点B时出错!", ex);
+                SendTaskMessage($"示教点B时出错:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 示教点A
+        /// </summary>
+        void ExecuteTeachPointACommand()
+        {
+            if (Robot == null || !Robot.IsConnected)
+            {
+                SendTaskMessage("机器人未连接,无法示教!");
+                return;
+            }
+
+            try
+            {
+                var position = Robot.GetRobotPos();
+                if (position != null)
+                {
+                    PointA = position.Clone();
+                    SendTaskMessage($"已示教点A:X={PointA.X:F2} Y={PointA.Y:F2} Z={PointA.Z:F2}");
+                }
+                else
+                {
+                    SendTaskMessage("获取机器人当前位置失败,无法示教点A!");
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("示教点A时出错!", ex);
+                SendTaskMessage($"示教点A时出错:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 显示趋势图
+        /// </summary>
+        void ExecuteShowTrendChartCommand()
+        {
+
+        }
+
+        /// <summary>
+        /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
+        /// </summary>
+        /// <param name="index"></param>
+        /// <returns></returns>
+        public Task<(bool isSuccess, double pixelX, double pixelY, double angleU, double transformX, double transformY)> ExecutePhotoEx(int index)
+        {
+            return Task.Run(() =>
+            {
+                CogToolBlockTerminalCollection outputCollection;
+                List<object> result = new List<object>();
+                result.Add(index);
+                try
+                {
+                    // 1. 采集图像
+                    bool succed = Camera.SetExposureTime(SelectProcedure.ExposureTime);
+                    if (!succed)
+                    {
+                        SendTaskMessage("相机曝光设置失败");
+                    }
+                    succed = Camera.SetGain(SelectProcedure.Gain);
+                    if (!succed)
+                    {
+                        SendTaskMessage("相机增益设置失败");
+                    }
+                    DateTime nowtime = DateTime.Now;
+                    LogHelper.WriteLogInfo("开始采集图像");
+                    var image = Camera.Grab();
+                    if (image == null)
+                    {
+
+                        SendTaskMessage(Lang.图像采集失败);
+                        outputCollection = null;
+                        return (false, 0d, 0d, 0d, 0d, 0d);
+                    }
+                    //Image = image;
+                    VisionTool.Inputs["InputImage"].Value = image;
+                    //LogHelper.WriteLogInfo(string.Format(Lang.采集图像完成用时0ms, (DateTime.Now - nowtime).Milliseconds));
+
+                    // 2. 为ToolBlock传入其他输入终端
+                    //if (InputTerminal != null)
+                    //{
+                    //    LogHelper.WriteLogInfo(Lang.为ToolBlock传入输入终端);
+                    //    foreach (var item in InputTerminal)
+                    //    {
+                    //        if (VisionTool.Inputs.Contains(item.Key))
+                    //        {
+                    //            LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
+                    //            VisionTool.Inputs[item.Key].Value = item.Value;
+                    //        }
+                    //        else
+                    //        {
+                    //            LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
+                    //            VisionTool.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
+                    //        }
+                    //    }
+                    //}
+                    Image = VisionTool.Inputs["InputImage"].Value as ICogImage;
+                    // 3. 运行视觉工具
+                    LogHelper.WriteLogInfo("开始运行视觉工具");
+                    VisionTool.Run();      //运行ToolBlock
+
+                    if (VisionTool.RunStatus.Result == CogToolResultConstants.Accept)
+                    {
+                        SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
+                        outputCollection = VisionTool.Outputs;     //获取输出终端集合
+                        Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
+                        if (!outputCollection.Contains("Found"))
+                        {
+                            SendTaskMessage(Lang.未找到视觉输出结果);
+                            return (false, 0d, 0d, 0d, 0d, 0d);
+                        }
+
+                        // 2. 获取视觉输出结果
+                        bool Found = (bool)(outputCollection["Found"].Value);
+                        if (!Found)
+                        {
+                            SendTaskMessage(Lang.拍照NG);
+                            return (false, 0d, 0d, 0d, 0d, 0d);
+                        }
+
+                        //SendTaskMessage(Lang.拍照OK, MessageLevel.Debug);
+
+                        // 3. 获取视觉输出结果
+                        
+                        string points = (string)(outputCollection["Point"].Value);
+                        string[] strpoints = points.Split(';');
+                        //先将pos按照逗号进行分隔,拿到数组
+                        var posParts = strpoints[0].Split(',');
+                        double _pixel_x = double.Parse(posParts[0]);
+                        double _pixel_y = double.Parse(posParts[1]);
+                        double _pixel_u = double.Parse(posParts[2]);
+
+                        //获取相机校准
+                        var calib = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
+                        if (calib == null)
+                        {
+                            return (true, _pixel_x, _pixel_y, _pixel_u, 0, 0); ;
+                        }
+
+                        //获取机器人当前坐标系
+                        RPoint rPoint = Robot.GetRobotPos();
+                        double[] point = new double[3];
+                        point[0] = rPoint.X;
+                        point[1] = rPoint.Y;
+                        point[2] = rPoint.U;
+                        if (calib.CameraMount == CameraMount.FixedUp || calib.CameraMount == CameraMount.FixedDown)
+                        {
+                            point = null;
+                        }
+
+                        //转换点位-像素转换成机器人绝对坐标
+                        var calibResult = _calibrationService.ConvertPixelToPosition((_pixel_x, _pixel_y, _pixel_u), point, calib, RobotBrand.XYZ_Platform);
+                        if (!calibResult.IsSucceed)
+                        {
+                            return (false, _pixel_x, _pixel_y, _pixel_u, 0d, 0d);
+                        }
+                        return (true, _pixel_x, _pixel_y, _pixel_u, calibResult.X, calibResult.Y); ;
+                    }
+                    else
+                    {
+                        SendTaskMessage(Lang.视觉流程执行出错耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
+                        SendTaskMessage(VisionTool.RunStatus.Message);
+                        outputCollection = null;
+                        Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
+                        return (false, 0d, 0d, 0d, 0d, 0d);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    SendTaskMessage(ex.Message);
+                    LogHelper.WriteLogError("执行相机取图并执行视觉工具组时出错", ex);
+                    outputCollection = null;
+                    return (false, 0d, 0d, 0d, 0d, 0d);
+                }
+            });
+        }
+
+        public void SendTaskMessage(string msg)
+        {
+            App.Current.Dispatcher.Invoke(() =>
+            {
+                MessageQueue.Clear();
+                MessageQueue.Enqueue(msg);
+            });
+        }
+
+        // 规格限制(根据实际情况设置)
+        private double _xLSL = -0.02;  // X下规格限
+        private double _xUSL = 0.02;   // X上规格限
+        private double _yLSL = -0.02;  // Y下规格限
+        private double _yUSL = 0.02;   // Y上规格限
+        private double _angleLSL = -1.0;  // 角度下规格限
+        private double _angleUSL = 1.0;   // 角度上规格限
+
+        /// <summary>
+        /// 计算所有精度指标
+        /// </summary>
+        public void CalculatePrecisionMetrics()
+        {
+            if (TestResults == null || TestResults.Count < 2)
+                return;
+
+            // 过滤成功的测试结果
+            var successResults = TestResults.Where(r => r.IsPhotoSuccess).ToList();
+            if (successResults.Count < 2)
+                return;
+
+            // 提取数据
+            var transformXs = successResults.Select(r => r.TransformX).ToList();
+            var transformYs = successResults.Select(r => r.TransformY).ToList();
+            var angleUs = successResults.Select(r => r.AngleU).ToList();
+
+            // 计算X方向指标
+            var xMetrics = CalculateMetrics(transformXs, _xLSL, _xUSL);
+            PrecisionResult.XMean = xMetrics.Mean;
+            PrecisionResult.XPrecision = xMetrics.StdDeviation;
+            PrecisionResult.XThreeSigmaRange = xMetrics.ThreeSigmaRange;
+            PrecisionResult.XRange = xMetrics.Range;
+            PrecisionResult.XKurtosis = xMetrics.Kurtosis;
+            PrecisionResult.XCpk = xMetrics.Cpk;
+
+            // 计算Y方向指标
+            var yMetrics = CalculateMetrics(transformYs, _yLSL, _yUSL);
+            PrecisionResult.YMean = yMetrics.Mean;
+            PrecisionResult.YPrecision = yMetrics.StdDeviation;
+            PrecisionResult.YThreeSigmaRange = yMetrics.ThreeSigmaRange;
+            PrecisionResult.YRange = yMetrics.Range;
+            PrecisionResult.YKurtosis = yMetrics.Kurtosis;
+            PrecisionResult.YCpk = yMetrics.Cpk;
+
+            // 计算角度指标
+            var angleMetrics = CalculateMetrics(angleUs, _angleLSL, _angleUSL);
+            PrecisionResult.AngleMean = angleMetrics.Mean;
+            PrecisionResult.AnglePrecision = angleMetrics.StdDeviation;
+            PrecisionResult.AngleThreeSigmaRange = angleMetrics.ThreeSigmaRange;
+            PrecisionResult.AngleRange = angleMetrics.Range;
+            PrecisionResult.AngleCpk = angleMetrics.Cpk;
+
+            // 计算综合精度
+            PrecisionResult.OverallPrecision = Math.Sqrt(
+                Math.Pow(PrecisionResult.XPrecision, 2) +
+                Math.Pow(PrecisionResult.YPrecision, 2));
+            PrecisionResult.OverallThreeSigmaRange = PrecisionResult.OverallPrecision * 3;
+
+            // 使用最差的CPK作为整体评级
+            var minCpk = Math.Min(Math.Min(PrecisionResult.XCpk, PrecisionResult.YCpk), PrecisionResult.AngleCpk);
+            PrecisionResult.ProcessRating = PrecisionResult.GetCpkRating(minCpk);
+        }
+
+        /// <summary>
+        /// 计算单个维度的所有指标
+        /// </summary>
+        private DimensionMetrics CalculateMetrics(List<double> values, double lsl, double usl)
+        {
+            if (values == null || values.Count < 2)
+                return new DimensionMetrics();
+
+            var metrics = new DimensionMetrics
+            {
+                Mean = values.Average(),
+                StdDeviation = CalculateStandardDeviation(values),
+                Range = values.Max() - values.Min(),
+                Kurtosis = CalculateKurtosis(values)
+            };
+
+            metrics.ThreeSigmaRange = metrics.StdDeviation * 3;
+            metrics.Cpk = CalculateCpk(values, lsl, usl);
+
+            return metrics;
+        }
+
+        /// <summary>
+        /// 计算标准差
+        /// </summary>
+        private double CalculateStandardDeviation(List<double> values)
+        {
+            if (values.Count < 2)
+                return 0;
+
+            var mean = values.Average();
+            var sumSq = values.Sum(v => Math.Pow(v - mean, 2));
+            return Math.Sqrt(sumSq / (values.Count - 1));
+        }
+
+        /// <summary>
+        /// 计算峰度
+        /// </summary>
+        private double CalculateKurtosis(List<double> values)
+        {
+            if (values.Count < 4)
+                return 0;
+
+            var mean = values.Average();
+            var n = values.Count;
+            var sum4 = values.Sum(v => Math.Pow(v - mean, 4));
+            var sum2 = values.Sum(v => Math.Pow(v - mean, 2));
+
+            if (sum2 == 0)
+                return 0;
+
+            return (n * sum4) / Math.Pow(sum2, 2) - 3;
+        }
+
+        /// <summary>
+        /// 计算CPK
+        /// </summary>
+        private double CalculateCpk(List<double> values, double lsl, double usl)
+        {
+            if (values.Count < 2 || usl <= lsl)
+                return 0;
+
+            var stdDev = CalculateStandardDeviation(values);
+            if (stdDev == 0)
+                return double.MaxValue;
+
+            var mean = values.Average();
+            var cpu = (usl - mean) / (3 * stdDev);
+            var cpl = (mean - lsl) / (3 * stdDev);
+
+            return Math.Min(cpu, cpl);
+        }
+
+        /// <summary>
+        /// 单个维度的指标
+        /// </summary>
+        private class DimensionMetrics
+        {
+            public double Mean { get; set; }
+            public double StdDeviation { get; set; }
+            public double ThreeSigmaRange { get; set; }
+            public double Range { get; set; }
+            public double Kurtosis { get; set; }
+            public double Cpk { get; set; }
+        }
+
+        /// <summary>
+        /// 在添加测试结果后自动更新精度分析
+        /// </summary>
+        private void OnTestResultAdded()
+        {
+            CalculatePrecisionMetrics();
+            RaisePropertyChanged(nameof(PrecisionResult));
+        }
+
+        /// <summary>
+        /// 设置规格限制
+        /// </summary>
+        public void SetSpecificationLimits(double xLSL, double xUSL, double yLSL, double yUSL, double angleLSL, double angleUSL)
+        {
+            _xLSL = xLSL;
+            _xUSL = xUSL;
+            _yLSL = yLSL;
+            _yUSL = yUSL;
+            _angleLSL = angleLSL;
+            _angleUSL = angleUSL;
+
+            // 重新计算精度指标
+            CalculatePrecisionMetrics();
+        }
+
+        /// <summary>
+        /// 基于3σ自动设置规格限制
+        /// </summary>
+        public void AutoSetSpecificationLimits()
+        {
+            if (TestResults == null || TestResults.Count < 2)
+                return;
+
+            var successResults = TestResults.Where(r => r.IsPhotoSuccess).ToList();
+            if (successResults.Count < 2)
+                return;
+
+            var transformXs = successResults.Select(r => r.TransformX).ToList();
+            var transformYs = successResults.Select(r => r.TransformY).ToList();
+            var angleUs = successResults.Select(r => r.AngleU).ToList();
+
+            var xStd = CalculateStandardDeviation(transformXs);
+            var yStd = CalculateStandardDeviation(transformYs);
+            var angleStd = CalculateStandardDeviation(angleUs);
+
+            var xMean = transformXs.Average();
+            var yMean = transformYs.Average();
+            var angleMean = angleUs.Average();
+
+            // 使用平均值±3σ作为自动规格限
+            SetSpecificationLimits(
+                xLSL: xMean - 3 * xStd,
+                xUSL: xMean + 3 * xStd,
+                yLSL: yMean - 3 * yStd,
+                yUSL: yMean + 3 * yStd,
+                angleLSL: angleMean - 3 * angleStd,
+                angleUSL: angleMean + 3 * angleStd
+            );
+        }
+
+        #endregion
+
+        #region 继承
+        public string Title { get; set; } = "视觉静态精度分析仪";
+
+        public event Action<IDialogResult> RequestClose;
+
+        public bool CanCloseDialog()
+        {
+            return true;
+        }
+
+        public void OnDialogClosed()
+        {
+            _cts?.Cancel();
+            SaveConfig();
+        }
+
+        public void OnDialogOpened(IDialogParameters parameters)
+        {
+            SelectProduct = parameters.GetValue<ProductModel>("SelectProduct");
+            Robot = parameters.GetValue<IRobot>("Robot");
+            ProcedureModels = new ObservableCollection<ProcedureModel>();
+            foreach (var item in SelectProduct.CameraProcedures)
+            {
+                foreach (var item1 in item.ProcedureModels)
+                {
+                    ProcedureModels.Add(item1);
+                }
+            }
+            LoadConfig();
+        }
+        #endregion
+
+        /// <summary>
+        /// 加载配置
+        /// </summary>
+        private void LoadConfig()
+        {
+            //配置文件路径:FilePath.ProductsPath//productName//VisionDynamicAccuracyAnalyzerConfig.json
+            string configPath = System.IO.Path.Combine(FilePath.ProductsPath, SelectProduct.Name, "VisionDynamicAccuracyAnalyzerConfig.json");
+            if (System.IO.File.Exists(configPath))
+            {
+                try
+                {
+                    string json = System.IO.File.ReadAllText(configPath);
+                    var ConfigModel = JsonConvert.DeserializeObject<VisionDynamicAccuracyAnalyzerConfigModel>(json);
+                    //加载点位A、B
+                    PointA = ConfigModel.PointA;
+                    PointB = ConfigModel.PointB;
+                    //加载选中视觉流程
+                    var procedure = ProcedureModels.FirstOrDefault(p => p.Id == ConfigModel.SelectProcedureId);
+                    if (procedure != null)
+                    {
+                        SelectProcedure = procedure;
+                    }
+                    MoveSpeed = ConfigModel.RobotMoveSpeed;
+                    RepeatCount = ConfigModel.TestCount;
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.WriteLogError("加载视觉动态精度分析仪配置时出错", ex);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 保存配置
+        /// </summary>
+        private void SaveConfig()
+        {
+            try
+            {
+                //配置文件路径:FilePath.ProductsPath//productName//VisionDynamicAccuracyAnalyzerConfig.json
+                string configPath = System.IO.Path.Combine(FilePath.ProductsPath, SelectProduct.Name, "VisionDynamicAccuracyAnalyzerConfig.json");
+                var ConfigModel = new VisionDynamicAccuracyAnalyzerConfigModel
+                {
+                    PointA = PointA,
+                    PointB = PointB,
+                    SelectProcedureId = SelectProcedure?.Id ?? Guid.Empty,
+                    SelectProcedureName = SelectProcedure?.Name ?? "",
+                    RobotMoveSpeed = MoveSpeed,
+                    TestCount = RepeatCount
+                };
+                //如果配置文件夹不存在则创建
+                var configDir = System.IO.Path.GetDirectoryName(configPath);
+                if (!Directory.Exists(configDir))
+                {
+                     Directory.CreateDirectory(configDir);
+                }
+
+                string json = JsonConvert.SerializeObject(ConfigModel, Formatting.Indented);
+                System.IO.File.WriteAllText(configPath, json);
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("保存视觉动态精度分析仪配置时出错", ex);
+            }
+        }
+
+        //配置参数模型
+        public class VisionDynamicAccuracyAnalyzerConfigModel : BindableBase
+        {
+            private int _RobotMoveSpeed = 100;
+            /// <summary>
+            /// 机器人移动速度
+            /// </summary>
+            public int RobotMoveSpeed
+            {
+                get { return _RobotMoveSpeed; }
+                set { SetProperty(ref _RobotMoveSpeed, value); }
+            }
+            private int _TestCount = 10;
+            /// <summary>
+            /// 测试次数
+            /// </summary>
+            public int TestCount
+            {
+                get { return _TestCount; }
+                set { SetProperty(ref _TestCount, value); }
+            }
+
+            //点位A
+            private RPoint _PointA;
+            /// <summary>
+            /// 点位A
+            /// </summary>
+            public RPoint PointA
+            {
+                get { return _PointA; }
+                set { SetProperty(ref _PointA, value); }
+            }
+
+            //点位B
+            private RPoint _PointB;
+            /// <summary>
+            /// 点位B
+            /// </summary>
+            public RPoint PointB
+            {
+                get { return _PointB; }
+                set { SetProperty(ref _PointB, value); }
+            }
+
+            private Guid _SelectProcedureId;
+            /// <summary>
+            /// 选中的视觉流程ID
+            /// </summary>
+            public Guid SelectProcedureId
+            {
+                get { return _SelectProcedureId; }
+                set { SetProperty(ref _SelectProcedureId, value); }
+            }
+
+            private string _SelectProcedureName;
+            /// <summary>
+            /// 选中的视觉流程Name
+            /// </summary>
+            public string SelectProcedureName
+            {
+                get { return _SelectProcedureName; }
+                set { SetProperty(ref _SelectProcedureName, value); }
+            }
+
+        }
+
+    }
+
+    public class DynamicTestResult : BindableBase
+    {
+        private int _Index;
+        /// <summary>
+        /// 序号
+        /// </summary>
+        public int Index
+        {
+            get { return _Index; }
+            set { SetProperty(ref _Index, value); }
+        }
+
+        private DateTime _Timestamp;
+        /// <summary>
+        /// 时间戳
+        /// </summary>
+        public DateTime Timestamp
+        {
+            get { return _Timestamp; }
+            set { SetProperty(ref _Timestamp, value); }
+        }
+
+        private bool _isPhotoSuccess;
+        /// <summary>
+        /// 拍照结果
+        /// </summary>
+        public bool IsPhotoSuccess
+        {
+            get { return _isPhotoSuccess; }
+            set { SetProperty(ref _isPhotoSuccess, value); }
+        }
+
+        private RPoint _PointB;
+        /// <summary>
+        /// 机器人拍照的坐标
+        /// </summary>
+        public RPoint PointB
+        {
+            get { return _PointB; }
+            set { SetProperty(ref _PointB, value); }
+        }
+
+        private double _CycleTime;
+        /// <summary>
+        /// 单次节拍
+        /// </summary>
+        public double CycleTime
+        {
+            get { return _CycleTime; }
+            set { SetProperty(ref _CycleTime, value); }
+        }
+
+        private double _PixelX;
+        /// <summary>
+        /// 像素X
+        /// </summary>
+        public double PixelX
+        {
+            get { return _PixelX; }
+            set { SetProperty(ref _PixelX, value); }
+        }
+
+        private double _PixelY;
+        /// <summary>
+        /// 像素Y
+        /// </summary>
+        public double PixelY
+        {
+            get { return _PixelY; }
+            set { SetProperty(ref _PixelY, value); }
+        }
+
+        private double _AngleU;
+        /// <summary>
+        /// 像素U
+        /// </summary>
+        public double AngleU
+        {
+            get { return _AngleU; }
+            set { SetProperty(ref _AngleU, value); }
+        }
+
+        private double _TransformX;
+        /// <summary>
+        /// 转换后的机器人坐标X
+        /// </summary>
+        public double TransformX
+        {
+            get { return _TransformX; }
+            set { SetProperty(ref _TransformX, value); }
+        }
+
+        private double _TransformY;
+        /// <summary>
+        /// 转换后的机器人坐标Y
+        /// </summary>
+        public double TransformY
+        {
+            get { return _TransformY; }
+            set { SetProperty(ref _TransformY, value); }
+        }
+
+        public DynamicTestResult()
+        { }
+
+        public DynamicTestResult(int index, bool isPhotoSuccess, RPoint pointB, double cycleTime, double pixelX, double pixelY, double angleU, double transformX, double transformY)
+        {
+            Index = index;
+            Timestamp = DateTime.Now;
+            IsPhotoSuccess = isPhotoSuccess;
+            PointB = pointB;
+            CycleTime = cycleTime;
+            PixelX = pixelX;
+            PixelY = pixelY;
+            AngleU = angleU;
+            TransformX = transformX;
+            TransformY = transformY;
+        }
+
+    }
+
+    /// <summary>
+    /// 精度分析结果
+    /// </summary>
+    public class PrecisionAnalysisResult : BindableBase
+    {
+        private double _xPrecision;
+        /// <summary>
+        /// X方向精度(标准差)
+        /// </summary>
+        public double XPrecision
+        {
+            get { return _xPrecision; }
+            set { SetProperty(ref _xPrecision, value); }
+        }
+
+        private double _yPrecision;
+        /// <summary>
+        /// Y方向精度(标准差)
+        /// </summary>
+        public double YPrecision
+        {
+            get { return _yPrecision; }
+            set { SetProperty(ref _yPrecision, value); }
+        }
+
+        private double _anglePrecision;
+        /// <summary>
+        /// 角度精度(标准差)
+        /// </summary>
+        public double AnglePrecision
+        {
+            get { return _anglePrecision; }
+            set { SetProperty(ref _anglePrecision, value); }
+        }
+
+        private double _xThreeSigmaRange;
+        /// <summary>
+        /// X方向3σ范围
+        /// </summary>
+        public double XThreeSigmaRange
+        {
+            get { return _xThreeSigmaRange; }
+            set { SetProperty(ref _xThreeSigmaRange, value); }
+        }
+
+        private double _yThreeSigmaRange;
+        /// <summary>
+        /// Y方向3σ范围
+        /// </summary>
+        public double YThreeSigmaRange
+        {
+            get { return _yThreeSigmaRange; }
+            set { SetProperty(ref _yThreeSigmaRange, value); }
+        }
+
+        private double _angleThreeSigmaRange;
+        /// <summary>
+        /// 角度3σ范围
+        /// </summary>
+        public double AngleThreeSigmaRange
+        {
+            get { return _angleThreeSigmaRange; }
+            set { SetProperty(ref _angleThreeSigmaRange, value); }
+        }
+
+        private double _xMean;
+        /// <summary>
+        /// X方向平均值
+        /// </summary>
+        public double XMean
+        {
+            get { return _xMean; }
+            set { SetProperty(ref _xMean, value); }
+        }
+
+        private double _yMean;
+        /// <summary>
+        /// Y方向平均值
+        /// </summary>
+        public double YMean
+        {
+            get { return _yMean; }
+            set { SetProperty(ref _yMean, value); }
+        }
+
+        private double _angleMean;
+        /// <summary>
+        /// 角度平均值
+        /// </summary>
+        public double AngleMean
+        {
+            get { return _angleMean; }
+            set { SetProperty(ref _angleMean, value); }
+        }
+
+        private double _xRange;
+        /// <summary>
+        /// X方向极差
+        /// </summary>
+        public double XRange
+        {
+            get { return _xRange; }
+            set { SetProperty(ref _xRange, value); }
+        }
+
+        private double _yRange;
+        /// <summary>
+        /// Y方向极差
+        /// </summary>
+        public double YRange
+        {
+            get { return _yRange; }
+            set { SetProperty(ref _yRange, value); }
+        }
+
+        private double _angleRange;
+        /// <summary>
+        /// 角度极差
+        /// </summary>
+        public double AngleRange
+        {
+            get { return _angleRange; }
+            set { SetProperty(ref _angleRange, value); }
+        }
+
+        private double _xKurtosis;
+        /// <summary>
+        /// X方向峰度
+        /// </summary>
+        public double XKurtosis
+        {
+            get { return _xKurtosis; }
+            set { SetProperty(ref _xKurtosis, value); }
+        }
+
+        private double _yKurtosis;
+        /// <summary>
+        /// Y方向峰度
+        /// </summary>
+        public double YKurtosis
+        {
+            get { return _yKurtosis; }
+            set { SetProperty(ref _yKurtosis, value); }
+        }
+
+        private double _xCpk;
+        /// <summary>
+        /// X方向CPK
+        /// </summary>
+        public double XCpk
+        {
+            get { return _xCpk; }
+            set { SetProperty(ref _xCpk, value); }
+        }
+
+        private double _yCpk;
+        /// <summary>
+        /// Y方向CPK
+        /// </summary>
+        public double YCpk
+        {
+            get { return _yCpk; }
+            set { SetProperty(ref _yCpk, value); }
+        }
+
+        private double _angleCpk;
+        /// <summary>
+        /// 角度CPK
+        /// </summary>
+        public double AngleCpk
+        {
+            get { return _angleCpk; }
+            set { SetProperty(ref _angleCpk, value); }
+        }
+
+        private double _overallPrecision;
+        /// <summary>
+        /// 综合精度(XY合成标准差)
+        /// </summary>
+        public double OverallPrecision
+        {
+            get { return _overallPrecision; }
+            set { SetProperty(ref _overallPrecision, value); }
+        }
+
+        private double _overallThreeSigmaRange;
+        /// <summary>
+        /// 综合3σ范围
+        /// </summary>
+        public double OverallThreeSigmaRange
+        {
+            get { return _overallThreeSigmaRange; }
+            set { SetProperty(ref _overallThreeSigmaRange, value); }
+        }
+
+        private string _processRating;
+        /// <summary>
+        /// 过程能力评级
+        /// </summary>
+        public string ProcessRating
+        {
+            get { return _processRating; }
+            set { SetProperty(ref _processRating, value); }
+        }
+
+        /// <summary>
+        /// 获取CPK评级描述
+        /// </summary>
+        public string GetCpkRating(double cpk)
+        {
+            if (cpk >= 1.67)
+                return "卓越";
+            else if (cpk >= 1.33)
+                return "良好";
+            else if (cpk >= 1.00)
+                return "可接受";
+            else if (cpk >= 0.67)
+                return "不足";
+            else
+                return "严重不足";
+        }
+    }
+}

+ 1142 - 0
TeamAAS-VM/ViewModels/Product/VisionStaticAccuracyAnalyzerViewModel.cs

@@ -0,0 +1,1142 @@
+using Cognex.VisionPro;
+using Cognex.VisionPro.ToolBlock;
+using MaterialDesignThemes.Wpf;
+using Prism.Commands;
+using Prism.Events;
+using Prism.Ioc;
+using Prism.Mvvm;
+using Prism.Regions;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Team.FFFeederService;
+using Team.FFFeederService.Interfaces;
+using TeamAAS_VP.Core;
+using TeamAAS_VP.Data;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Events;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Models.Calibration;
+using TeamAAS_VP.Resources.Languages;
+using TeamAAS_VP.Services;
+using TeamAAS_VP.Views.Setting;
+using static TeamAAS_VP.Core.StabilityAnalyzer;
+
+namespace TeamAAS_VP.ViewModels.Product
+{
+    public class VisionStaticAccuracyAnalyzerViewModel : BindableBase, IDialogAware
+    {
+        IRegionManager _regionManager;
+        IEventAggregator _eventAggregator;
+        IContainerProvider _container;
+        IDialogService _dialogService;
+        IFeederService _feederService;
+        IRobotService _robotService;
+        ICameraService _cameraService;
+        ISystemDatabaseService _systemDatabaseService;
+        ICalibrationService _calibrationService;
+
+        //测试过程中控制取消的CTS
+        CancellationTokenSource _cts;
+
+        #region 属性
+
+        private ICogImage _Image;
+
+        public ICogImage Image
+        {
+            get { return _Image; }
+            set { SetProperty(ref _Image, value); }
+        }
+
+        private Cognex.VisionPro.CogGraphicCollection _Graphic;
+
+        public Cognex.VisionPro.CogGraphicCollection Graphic
+        {
+            get { return _Graphic; }
+            set { SetProperty(ref _Graphic, value); }
+        }
+
+        private ProcedureModel _SelectProcedure;
+
+        /// <summary>
+        /// 选中的流程
+        /// </summary>
+        public ProcedureModel SelectProcedure
+        {
+            get { return _SelectProcedure; }
+            set
+            {
+                SetProperty(ref _SelectProcedure, value);
+            }
+        }
+
+        private ICamera _Camera;
+
+        public ICamera Camera
+        {
+            get { return _Camera; }
+            set { SetProperty(ref _Camera, value); }
+        }
+
+        private string _Message = "等待开始...";
+        public string Message
+        {
+            get { return _Message; }
+            set { SetProperty(ref _Message, value); }
+        }
+
+        private bool _IsRunning;
+
+        public bool IsRunning
+        {
+            get { return _IsRunning; }
+            set { SetProperty(ref _IsRunning, value); }
+        }
+
+        private CogToolBlock _VisionTool;
+        public CogToolBlock VisionTool
+        {
+            get { return _VisionTool; }
+            set { SetProperty(ref _VisionTool, value); }
+        }
+
+        private int _RepeatCount = 10;
+        /// <summary>
+        /// 重复次数
+        /// </summary>
+        public int RepeatCount
+        {
+            get { return _RepeatCount; }
+            set { SetProperty(ref _RepeatCount, value); }
+        }
+
+        private int _CurrentProgress;
+        /// <summary>
+        /// 当前进度
+        /// </summary>
+        public int CurrentProgress
+        {
+            get { return _CurrentProgress; }
+            set { SetProperty(ref _CurrentProgress, value); }
+        }
+
+        private int _SuccessCount;
+        /// <summary>
+        /// 成功次数
+        /// </summary>
+        public int SuccessCount
+        {
+            get { return _SuccessCount; }
+            set { SetProperty(ref _SuccessCount, value); }
+        }
+
+        private int _FailCount;
+        /// <summary>
+        /// 失败次数
+        /// </summary>
+        public int FailCount
+        {
+            get { return _FailCount; }
+            set { SetProperty(ref _FailCount, value); }
+        }
+
+        private double _SuccessRate;
+        /// <summary>
+        /// 成功率
+        /// </summary>
+        public double SuccessRate
+        {
+            get { return _SuccessRate; }
+            set { SetProperty(ref _SuccessRate, value); }
+        }
+
+        private DataTable _TestResult;
+        /// <summary>
+        /// 测试结果列表DataTable
+        /// </summary>
+        public DataTable TestResult
+        {
+            get { return _TestResult; }
+            set { SetProperty(ref _TestResult, value); }
+        }
+
+        private ObservableCollection<DataColumnInfo> _ResultDataColumns;
+        /// <summary>
+        /// DataTable的列集合
+        /// </summary>
+        public ObservableCollection<DataColumnInfo> ResultDataColumns
+        {
+            get { return _ResultDataColumns; }
+            set { SetProperty(ref _ResultDataColumns, value); }
+        }
+
+        //
+        private DataColumnInfo _SelectedDataColumn;
+        /// <summary>
+        /// 选中的列
+        /// </summary>
+        public DataColumnInfo SelectedDataColumn
+        {
+            get { return _SelectedDataColumn; }
+            set { SetProperty(ref _SelectedDataColumn, value); }
+        }
+
+        private SnackbarMessageQueue _MessageQueue;
+        public SnackbarMessageQueue MessageQueue
+        {
+            get { return _MessageQueue; }
+            set { SetProperty(ref _MessageQueue, value); }
+        }
+
+        //选中列分析结果
+        private StabilityMetrics _StabilityMetrics;
+        public StabilityMetrics StabilityMetrics
+        {
+            get { return _StabilityMetrics; }
+            set { SetProperty(ref _StabilityMetrics, value); }
+        }
+
+        #endregion
+
+        #region 命令
+        private DelegateCommand _ConfirmCommand;
+        public DelegateCommand ConfirmCommand =>
+            _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _CancelCommand;
+        public DelegateCommand CancelCommand =>
+            _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _StartTestCommand;
+        public DelegateCommand StartTestCommand =>
+            _StartTestCommand ?? (_StartTestCommand = new DelegateCommand(ExecuteStartTestCommand, CanExecuteStartTestCommand).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _StopTestCommand;
+        public DelegateCommand StopTestCommand =>
+            _StopTestCommand ?? (_StopTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
+
+        //暂停测试命令
+        private DelegateCommand _PauseTestCommand;
+        public DelegateCommand PauseTestCommand =>
+            _PauseTestCommand ?? (_PauseTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _ExportCSVCommand;
+        public DelegateCommand ExportCSVCommand =>
+            _ExportCSVCommand ?? (_ExportCSVCommand = new DelegateCommand(ExecuteExportCSVCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _ExportReportCommand;
+        public DelegateCommand ExportReportCommand =>
+            _ExportReportCommand ?? (_ExportReportCommand = new DelegateCommand(ExecuteExportReportCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _ClearDataCommand;
+        public DelegateCommand ClearDataCommand =>
+            _ClearDataCommand ?? (_ClearDataCommand = new DelegateCommand(ExecuteClearDataCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
+
+        private DelegateCommand _SelectColumnCommand;
+        public DelegateCommand SelectColumnCommand =>
+            _SelectColumnCommand ?? (_SelectColumnCommand = new DelegateCommand(ExecuteSelectColumnCommand));
+
+
+        #endregion
+
+        #region 事件
+
+        #endregion
+
+        public VisionStaticAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
+            IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService)
+        {
+            _regionManager = regionManager;
+            _eventAggregator = ea;
+            _container = container;
+            _dialogService = dialogService;
+            _feederService = feederService;
+            _robotService = robotService;
+            _cameraService = cameraService;
+            _systemDatabaseService = systemDatabaseService;
+            MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
+            _calibrationService = calibrationService;
+        }
+
+        #region 方法
+        /// <summary>
+        /// 确定
+        /// </summary>
+        void ExecuteConfirmCommand()
+        {
+            _cts?.Cancel();
+
+            IDialogParameters parameters = new DialogParameters();
+            //parameters.Add("Tool", Tool);
+            //parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
+            //parameters.Add("Angle", Angle);
+            RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
+        }
+
+        /// <summary>
+        /// 取消
+        /// </summary>
+        void ExecuteCancelCommand()
+        {
+            _cts?.Cancel();
+            IDialogParameters parameters = new DialogParameters();
+            //parameters.Add("Tool", Tool);
+            RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
+        }
+
+        /// <summary>
+        /// 开始测试
+        /// </summary>
+        async void 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>
+        /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
+        /// </summary>
+        /// <param name="index"></param>
+        /// <returns></returns>
+        public Task<(bool isSuccess, object[] Result)> ExecutePhotoEx(int index)
+        {
+            return Task.Run(() =>
+            {
+                CogToolBlockTerminalCollection outputCollection;
+                List<object> result = new List<object>();
+                result.Add(index);
+                try
+                {
+                    // 1. 采集图像
+                    bool succed = Camera.SetExposureTime(SelectProcedure.ExposureTime);
+                    if (!succed)
+                    {
+                        SendTaskMessage("相机曝光设置失败");
+                    }
+                    succed = Camera.SetGain(SelectProcedure.Gain);
+                    if (!succed)
+                    {
+                        SendTaskMessage("相机增益设置失败");
+                    }
+                    DateTime nowtime = DateTime.Now;
+                    LogHelper.WriteLogInfo("开始采集图像");
+                    var image = Camera.Grab();
+                    if (image == null)
+                    {
+
+                        SendTaskMessage("图像采集失败");
+                        outputCollection = null;
+                        return (false, result.ToArray());
+                    }
+                    //Image = image;
+                    VisionTool.Inputs["InputImage"].Value = image;
+                    LogHelper.WriteLogInfo($"采集图像完成用时{(DateTime.Now - nowtime).Milliseconds}ms");
+
+                    // 2. 为ToolBlock传入其他输入终端
+                    //if (InputTerminal != null)
+                    //{
+                    //    LogHelper.WriteLogInfo(Lang.为ToolBlock传入输入终端);
+                    //    foreach (var item in InputTerminal)
+                    //    {
+                    //        if (VisionTool.Inputs.Contains(item.Key))
+                    //        {
+                    //            LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
+                    //            VisionTool.Inputs[item.Key].Value = item.Value;
+                    //        }
+                    //        else
+                    //        {
+                    //            LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
+                    //            VisionTool.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
+                    //        }
+                    //    }
+                    //}
+                    Image = VisionTool.Inputs["InputImage"].Value as ICogImage;
+                    // 3. 运行视觉工具
+                    LogHelper.WriteLogInfo("开始运行视觉工具");
+                    VisionTool.Run();      //运行ToolBlock
+
+                    if (VisionTool.RunStatus.Result == CogToolResultConstants.Accept)
+                    {
+                        SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
+                        outputCollection = VisionTool.Outputs;     //获取输出终端集合
+                        foreach (CogToolBlockTerminal terminal in outputCollection)
+                        {
+                            //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
+                            if (terminal.ValueType == typeof(string))
+                            {
+                                //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
+                                if (terminal.Name == "Point")
+                                {
+                                    //是否需要转换为绝对坐标
+                                    needConvertToAbsolute = false;
+
+                                    //如果当前流程的校准为空,则不需要转换为绝对坐标
+                                    if (SelectProcedure.CalibrationId != Guid.Empty)
+                                    {
+                                        var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
+                                        if (calibration != null)
+                                        {
+                                            needConvertToAbsolute = true;
+                                        }
+                                    }
+
+                                    //判断是否是点位格式
+                                    string strpoints = terminal.Value.ToString();
+                                    string[] items = strpoints.Split(';');
+                                    bool isPointFormat = true;
+                                    foreach (string item in items)
+                                    {
+                                        string[] subitems = item.Split(',');
+                                        if (subitems.Length < 3)
+                                        {
+                                            isPointFormat = false;
+                                            break;
+                                        }
+                                        double x, y, u;
+                                        if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
+                                        {
+                                            isPointFormat = false;
+                                            break;
+                                        }
+                                    }
+                                    if (isPointFormat)
+                                    {
+                                        //是点位格式,则创建多列
+                                        for (int i = 0; i < items.Length; i++)
+                                        {
+                                            string[] subitems = items[i].Split(',');
+                                            //先分别创建像素X、像素Y、角度U三列
+                                            result.Add(double.Parse(subitems[0]));
+                                            result.Add(double.Parse(subitems[1]));
+                                            result.Add(double.Parse(subitems[2]));
+
+                                            //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
+                                            if (needConvertToAbsolute)
+                                            {
+                                                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("执行相机取图并执行视觉工具组时出错", ex);
+                    outputCollection = null;
+                    return (false, result.ToArray());
+                }
+            });
+        }
+
+        public void SendTaskMessage(string msg)
+        {
+            App.Current.Dispatcher.Invoke(() =>
+            {
+                MessageQueue.Clear();
+                MessageQueue.Enqueue(msg);
+            });
+        }
+
+        bool needConvertToAbsolute = false;
+        /// <summary>
+        /// 根据VisionTool的输出终端,创建DataTable的列
+        /// </summary>
+        /// <param name="outputCollection"></param>
+        /// <returns></returns>
+        private DataTable CreateResultDataTable(CogToolBlockTerminalCollection outputCollection)
+        {
+            DataTable dt = new DataTable();
+            ResultDataColumns = new ObservableCollection<DataColumnInfo>();
+            //第一列添加序号
+            dt.Columns.Add("序号", typeof(int));
+
+            //列索引
+
+            foreach (CogToolBlockTerminal terminal in outputCollection)
+            {
+                //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
+                if (terminal.ValueType == typeof(string))
+                {
+                    //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
+                    if (terminal.Name == "Point")
+                    {
+                        //是否需要转换为绝对坐标
+                        needConvertToAbsolute = false;
+
+                        //如果当前流程的校准为空,则不需要转换为绝对坐标
+                        if (SelectProcedure.CalibrationId != Guid.Empty)
+                        {
+                            var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
+                            if (calibration != null)
+                            {
+                                needConvertToAbsolute = true;
+                            }
+                        }
+
+                        //判断是否是点位格式
+                        string strpoints = terminal.Value.ToString();
+                        string[] items = strpoints.Split(';');
+                        bool isPointFormat = true;
+                        foreach (string item in items)
+                        {
+                            string[] subitems = item.Split(',');
+                            if (subitems.Length < 3)
+                            {
+                                isPointFormat = false;
+                                break;
+                            }
+                            double x, y, u;
+                            if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
+                            {
+                                isPointFormat = false;
+                                break;
+                            }
+                        }
+                        if (isPointFormat)
+                        {
+                            //是点位格式,则创建多列
+                            for (int i = 0; i < items.Length; i++)
+                            {
+                                //先分别创建像素X、像素Y、角度U三列
+                                dt.Columns.Add($"{terminal.Name}_Pixel_X{i + 1}", typeof(double));
+                                ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_X{i + 1}", dt.Columns.Count - 1));
+                                dt.Columns.Add($"{terminal.Name}_Pixel_Y{i + 1}", typeof(double));
+                                ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_Y{i + 1}", dt.Columns.Count - 1));
+                                dt.Columns.Add($"{terminal.Name}_Angle_U{i + 1}", typeof(double));
+                                ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Angle_U{i + 1}", dt.Columns.Count - 1));
+
+                                //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
+                                if (needConvertToAbsolute)
+                                {
+                                    dt.Columns.Add($"{terminal.Name}_Absolute_X{i + 1}", typeof(double));
+                                    ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_X{i + 1}", dt.Columns.Count - 1));
+                                    dt.Columns.Add($"{terminal.Name}_Absolute_Y{i + 1}", typeof(double));
+                                    ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_Y{i + 1}", dt.Columns.Count - 1));
+                                }
+                            }
+                        }
+                        else
+                        {
+                            dt.Columns.Add(terminal.Name, typeof(string));
+                        }
+                    }
+                    else
+                    {
+                        dt.Columns.Add(terminal.Name, typeof(string));
+                    }
+                }
+                else if (terminal.ValueType == typeof(int))
+                {
+                    dt.Columns.Add(terminal.Name, typeof(int));
+                    ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
+                }
+                else if (terminal.ValueType == typeof(double))
+                {
+                    dt.Columns.Add(terminal.Name, typeof(double));
+                    ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
+                }
+                else if (terminal.ValueType == typeof(bool))
+                {
+                    dt.Columns.Add(terminal.Name, typeof(bool));
+                }
+                //else
+                //    dt.Columns.Add(terminal.Name, typeof(string));
+            }
+            return dt;
+        }
+
+        #endregion
+
+        #region 继承
+        public string Title { get; set; } = "视觉静态精度分析仪";
+
+        public event Action<IDialogResult> RequestClose;
+
+        public bool CanCloseDialog()
+        {
+            return true;
+        }
+
+        public void OnDialogClosed()
+        {
+            //关闭时,停止测试
+            _cts?.Cancel();
+        }
+
+        public void OnDialogOpened(IDialogParameters parameters)
+        {
+            SelectProcedure = parameters.GetValue<ProcedureModel>("SelectedProcedure");
+            Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
+            VisionTool = parameters.GetValue<CogToolBlock>("ToolBlock");
+            VisionTool.Run();
+            TestResult = CreateResultDataTable(VisionTool.Outputs);
+        }
+        #endregion
+    }
+
+    //辅助类,用于定义DataTable的列,仅用来对于数值列进行统计分析
+    public class DataColumnInfo
+    {
+        public string ColumnName { get; set; }
+        //列索引
+        public int ColumnIndex { get; set; }
+
+        public DataColumnInfo(string columnName, int columnIndex)
+        {
+            ColumnName = columnName;
+            ColumnIndex = columnIndex;
+        }
+    }
+}

+ 8 - 1
TeamAAS-VM/Views/Product/AdvancedProcedure.xaml

@@ -131,7 +131,14 @@
                                    VerticalAlignment="Center"
                                    Foreground="Gray" />
                     </StackPanel>
-                    
+                    <Button Grid.Row="1"
+                            Margin="5,3"
+                            HorizontalAlignment="Right"
+                            VerticalAlignment="Center"
+                            Content="{lex:Loc 视觉静态精度分析}"
+                            MinWidth="100"
+                            Command="{Binding VisionStaticAccuracyAnalyzerCommand}" />
+
                     <ToggleButton materialDesign:ToggleButtonAssist.OnContent="{materialDesign:PackIcon Kind=ArrowRight}"
                                   Content="{materialDesign:PackIcon Kind=RobotIndustrial}"
                                   Style="{StaticResource MaterialDesignActionToggleButton}"

+ 7 - 0
TeamAAS-VM/Views/Product/PlcPointParams.xaml

@@ -80,6 +80,13 @@
             <TextBlock Text="{lex:Loc 机器人点位,Converter={StaticResource StringFormatConverter},ConverterParameter=' - {0}'}"
                        VerticalAlignment="Center" />
         </StackPanel>
+        <Button Grid.Row="0"
+                Margin="5,3"
+                HorizontalAlignment="Right"
+                VerticalAlignment="Center"
+                Content="动态精度分析"
+                MinWidth="100"
+                Command="{Binding DynamicTestAnalyzerCommand}" />
         <Border BorderThickness="1,1,1,1"
                 VerticalAlignment="Bottom"
                 Height="1">

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

@@ -0,0 +1,817 @@
+<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="1080"
+             d:Width="1920"
+             MinHeight="500"
+             MinWidth="500"
+             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="MinHeight"
+                    Value="500" />
+            <Setter Property="MinWidth"
+                    Value="500" />
+            <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="45*" />
+            <ColumnDefinition Width="55*" />
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧控制面板 -->
+        <ScrollViewer >
+            <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="Auto" />
+                        <RowDefinition Height="*"
+                           MinHeight="150" />
+                        <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"
+                                 d:Text="X:0.000 Y:0.000 Z:0.000 U:0.000 V:0.000 W:0.000"
+                                 ToolTip="机器人A点位置坐标"
+                                 IsReadOnly="True"
+                                 Background="#F8F9FA">
+                                        <TextBox.Text>
+                                            <MultiBinding StringFormat="X:{0:f3} Y:{1:f3} Z:{2:f3} U:{3:f3} V:{4:f3} W:{5:f3}">
+                                                <Binding Path="PointA.X"/>
+                                                <Binding Path="PointA.Y"/>
+                                                <Binding Path="PointA.Z"/>
+                                                <Binding Path="PointA.U"/>
+                                                <Binding Path="PointA.V"/>
+                                                <Binding Path="PointA.W"/>
+                                            </MultiBinding>
+                                        </TextBox.Text>
+                                    </TextBox>
+                                    <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"
+                                 d:Text="X:100.000 Y:50.000 Z:0.000 U:0.000 V:0.000 W:0.000"
+                                 ToolTip="机器人B点拍照位置坐标"
+                                 IsReadOnly="True"
+                                 Background="#F8F9FA" >
+                                        <TextBox.Text>
+                                            <MultiBinding StringFormat="X:{0:f3} Y:{1:f3} Z:{2:f3} U:{3:f3} V:{4:f3} W:{5:f3}">
+                                                <Binding Path="PointB.X"/>
+                                                <Binding Path="PointB.Y"/>
+                                                <Binding Path="PointB.Z"/>
+                                                <Binding Path="PointB.U"/>
+                                                <Binding Path="PointB.V"/>
+                                                <Binding Path="PointB.W"/>
+                                            </MultiBinding>
+                                        </TextBox.Text>
+                                    </TextBox>
+                                    <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>
+                                        <b:Interaction.Triggers>
+                                            <b:EventTrigger EventName="SelectionChanged">
+                                                <b:InvokeCommandAction Command="{Binding ProcedureSelectionChangedCommand}" />
+                                            </b:EventTrigger>
+                                        </b:Interaction.Triggers>
+                                    </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 CurrentProgress}"
+                                       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}"
+                                            MinWidth="50" />
+                                    <DataGridTextColumn Header="状态"
+                                            Binding="{Binding IsPhotoSuccess}"
+                                            MinWidth="60" />
+                                    <DataGridTextColumn Header="像素X"
+                                            Binding="{Binding PixelX, StringFormat={}{0:F3}}"
+                                            MinWidth="70" />
+                                    <DataGridTextColumn Header="像素Y"
+                                            Binding="{Binding PixelY, StringFormat={}{0:F3}}"
+                                            MinWidth="70" />
+                                    <DataGridTextColumn Header="角度"
+                                            Binding="{Binding AngleU, StringFormat={}{0:F2}}"
+                                            MinWidth="60" />
+                                    <DataGridTextColumn Header="绝对X"
+                                            Binding="{Binding TransformX, StringFormat={}{0:F3}}"
+                                            MinWidth="70" />
+                                    <DataGridTextColumn Header="绝对Y"
+                                            Binding="{Binding TransformY, StringFormat={}{0:F3}}"
+                                            MinWidth="70" />
+                                    <DataGridTextColumn Header="循环时间"
+                                            Binding="{Binding CycleTime, StringFormat={}{0:F2}s}"
+                                            MinWidth="80" />
+                                    <DataGridTextColumn Header="时间戳"
+                                            Binding="{Binding Timestamp}"
+                                            MinWidth="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 PrecisionResult.XPrecision, StringFormat={}{0:F3}mm}"
+                                         d:Text="0.023mm"
+                                         Foreground="#2C3E50"
+                                         FontWeight="Bold" />
+                                            </TextBlock>
+                                            <TextBlock>
+                                    <Run Text="Y方向精度: " />
+                                    <Run Text="{Binding PrecisionResult.YPrecision, StringFormat={}{0:F3}mm}"
+                                         d:Text="0.018mm"
+                                         Foreground="#2C3E50"
+                                         FontWeight="Bold" />
+                                            </TextBlock>
+                                            <TextBlock>
+                                    <Run Text="角度精度: " />
+                                    <Run Text="{Binding PrecisionResult.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 PrecisionResult.OverallThreeSigmaRange, StringFormat={}±{0:F3}mm}"
+                                         d:Text="±0.065mm"
+                                         Foreground="#2C3E50"
+                                         FontWeight="Bold" />
+                                            </TextBlock>
+                                            <TextBlock>
+                                    <Run Text="CPK: " />
+                                    <Run Text="{Binding PrecisionResult.OverallPrecision, 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>
+        </ScrollViewer>
+
+        <!-- 右侧图像和轨迹显示区域 -->
+        <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;
+                    }
+                }
+            }
+        }
+    }
+}

+ 449 - 0
TeamAAS-VM/Views/Product/VisionStaticAccuracyAnalyzer.xaml

@@ -0,0 +1,449 @@
+<UserControl x:Class="TeamAAS_VP.Views.Product.VisionStaticAccuracyAnalyzer"
+             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:VisionStaticAccuracyAnalyzerViewModel}"
+             d:Height="600"
+             d:Width="1024"
+             MinHeight="700"
+             MinWidth="900"
+             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="SizeToContent"
+                 Value="WidthAndHeight" />-->
+            <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*" />
+            <ColumnDefinition Width="3*" />
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧控制面板 -->
+        <Border Grid.Column="0"
+                BorderBrush="#BDC3C7"
+                BorderThickness="0 0 1 0">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <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.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto" />
+                                <ColumnDefinition Width="*" />
+                                <ColumnDefinition Width="Auto" />
+                            </Grid.ColumnDefinitions>
+
+                            <TextBlock Text="重复次数:"
+                                       Grid.Column="0" />
+                            <mah:NumericUpDown Grid.Column="1"
+                                               Value="{Binding RepeatCount, Mode=TwoWay}"
+                                               Minimum="1"
+                                               d:Value="10"
+                                               TextAlignment="Center"
+                                               HorizontalContentAlignment="Center"
+                                               HorizontalAlignment="Stretch"
+                                               IsEnabled="{Binding IsRunning,Converter={StaticResource InvertBooleanConverter}}" />
+                            <!--<Button x:Name="btnSetCount"
+                                    Grid.Column="2"
+                                    Content="设置"
+                                    Width="60" />-->
+                        </Grid>
+
+                        <Separator Margin="0 15 0 10" />
+
+                        <!-- 控制按钮 -->
+                        <WrapPanel HorizontalAlignment="Center">
+                            <Button x:Name="btnStart"
+                                    Content="开始测试"
+                                    Background="#27AE60"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Height="35"
+                                    FontWeight="Bold"
+                                    Command="{Binding StartTestCommand}" />
+
+                            <Button x:Name="btnStop"
+                                    Content="停止"
+                                    Background="#E74C3C"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Height="35"
+                                    d:IsEnabled="False"
+                                    Command="{Binding StopTestCommand}" />
+
+                            <Button x:Name="btnPause"
+                                    Content="暂停"
+                                    Background="#F39C12"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Height="35"
+                                    d:IsEnabled="False"
+                                    Command="{Binding PauseTestCommand}" />
+                        </WrapPanel>
+                    </StackPanel>
+                </Border>
+
+                <!-- 进度显示 -->
+                <Border Grid.Row="1"
+                        Background="#F8F9FA"
+                        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,Mode=TwoWay}"
+                                         Value="{Binding CurrentProgress,Mode=TwoWay}"
+                                         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 x:Name="txtCompleted"
+                                               Text="{Binding CurrentProgress}"
+                                               d:Text="0"
+                                               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 x:Name="txtSuccessRate"
+                                               d:Text="0%"
+                                               Text="{Binding SuccessRate, StringFormat={}{0:F2}%}"
+                                               Foreground="White"
+                                               FontSize="16"
+                                               FontWeight="Bold"
+                                               HorizontalAlignment="Center" />
+                                </StackPanel>
+                            </Border>
+
+                            <Border Background="#E74C3C"
+                                    Padding="10 5"
+                                    Margin="5"
+                                    CornerRadius="3">
+                                <StackPanel>
+                                    <TextBlock Text="失败数"
+                                               Foreground="White"
+                                               FontSize="11" />
+                                    <TextBlock x:Name="txtFailedCount"
+                                               d:Text="0"
+                                               Text="{Binding FailCount}"
+                                               Foreground="White"
+                                               FontSize="16"
+                                               FontWeight="Bold"
+                                               HorizontalAlignment="Center" />
+                                </StackPanel>
+                            </Border>
+                        </WrapPanel>
+                    </StackPanel>
+                </Border>
+
+                <!-- 测试结果列表 -->
+                <Border Grid.Row="2"
+                        Background="White"
+                        Padding="10">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto" />
+                            <RowDefinition Height="*" />
+                        </Grid.RowDefinitions>
+
+                        <TextBlock Style="{StaticResource TitleStyle}"
+                                   Text="测试结果列表" />
+
+                        <DataGrid x:Name="dgResults"
+                                  Grid.Row="1"
+                                  Style="{StaticResource DataGridStyle}"
+                                  MaxHeight="300"
+                                  AutoGenerateColumns="True"
+                                  CanUserAddRows="False"
+                                  IsReadOnly="True"
+                                  ItemsSource="{Binding TestResult.DefaultView}"
+                                  AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" />
+                    </Grid>
+                </Border>
+
+                <!-- 导出按钮 -->
+                <Border Grid.Row="3"
+                        Background="#ECF0F1"
+                        Padding="10">
+                    <StackPanel>
+                        <WrapPanel HorizontalAlignment="Right">
+                            <Button x:Name="btnExportCSV"
+                                    Content="导出CSV"
+                                    Background="#3498DB"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Command="{Binding ExportCSVCommand}" />
+
+                            <Button x:Name="btnExportReport"
+                                    Content="生成报告"
+                                    Background="#9B59B6"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Command="{Binding ExportReportCommand}" />
+
+                            <Button x:Name="btnClearData"
+                                    Content="清空数据"
+                                    Background="#95A5A6"
+                                    Foreground="White"
+                                    MinWidth="100"
+                                    Command="{Binding ClearDataCommand}" />
+                        </WrapPanel>
+
+                        <!-- 精度统计 -->
+                        <Border Margin="0 10 0 0"
+                                Background="#F8F9FA"
+                                Padding="10"
+                                CornerRadius="5">
+                            <StackPanel>
+                                <StackPanel Orientation="Horizontal"
+                                            Margin="0 0 0 5">
+                                    <TextBlock Text="精度统计"
+                                               FontWeight="Bold" />
+                                    <ComboBox ItemsSource="{Binding ResultDataColumns}"
+                                              SelectedItem="{Binding SelectedDataColumn, Mode=TwoWay}"
+                                              Margin="5,0">
+                                        <ComboBox.ItemTemplate>
+                                            <DataTemplate>
+                                                <TextBlock Text="{Binding ColumnName}" />
+                                            </DataTemplate>
+                                        </ComboBox.ItemTemplate>
+                                        <b:Interaction.Triggers>
+                                            <b:EventTrigger EventName="SelectionChanged">
+                                                <b:InvokeCommandAction Command="{Binding SelectColumnCommand}"/>
+                                            </b:EventTrigger>
+                                        </b:Interaction.Triggers>
+                                    </ComboBox>
+                                </StackPanel>
+
+                                <Grid>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="*" />
+                                        <ColumnDefinition Width="*" />
+                                    </Grid.ColumnDefinitions>
+
+                                    <StackPanel Grid.Column="0">
+                                        <TextBlock Text="标准差:">
+                                            <Run x:Name="runStdX"
+                                                 d:Text="0.000"
+                                                 Text="{Binding StabilityMetrics.StdDeviation,StringFormat={}{0:F3}}"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                        <TextBlock Text="3σ范围:">
+                                            <Run x:Name="runStdY"
+                                                 d:Text="±0.000"
+                                                 Text="{Binding StabilityMetrics.ThreeSigmaRange,StringFormat={}±{0:F3}}"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                    </StackPanel>
+
+                                    <StackPanel Grid.Column="1">
+                                        <TextBlock Text="峰度:">
+                                            <Run x:Name="runAvgDeviation"
+                                                 d:Text="0.000"
+                                                 Text="{Binding StabilityMetrics.Kurtosis,StringFormat={}{0:F3}}"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                        <TextBlock Text="极差:">
+                                            <Run x:Name="runMaxDeviation"
+                                                 d:Text="0.000"
+                                                 Text="{Binding StabilityMetrics.Range,StringFormat={}{0:F3}}"
+                                                 Foreground="#2C3E50"
+                                                 FontWeight="Bold" />
+                                        </TextBlock>
+                                    </StackPanel>
+                                </Grid>
+                            </StackPanel>
+                        </Border>
+                    </StackPanel>
+                </Border>
+            </Grid>
+        </Border>
+
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="*" />
+                <RowDefinition Height="Auto" />
+            </Grid.RowDefinitions>
+            <!-- 右侧图像显示区域 -->
+            <Border Grid.Column="1"
+                    Background="#2C3E50">
+                <!-- 图像显示画布 -->
+                <wf:WindowsFormsHost>
+                    <vp:CogRecordDisplay x:Name="display" />
+                </wf:WindowsFormsHost>
+            </Border>
+            <StackPanel Grid.Row="3"
+                        Orientation="Horizontal"
+                        VerticalAlignment="Center"
+                        HorizontalAlignment="Right">
+                <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>

+ 93 - 0
TeamAAS-VM/Views/Product/VisionStaticAccuracyAnalyzer.xaml.cs

@@ -0,0 +1,93 @@
+using Cognex.VisionPro;
+using Cognex.VisionPro.Dimensioning;
+using System;
+using System.ComponentModel;
+using System.Data;
+using System.Windows.Controls;
+using TeamAAS_VP.ViewModels.Calibration;
+using TeamAAS_VP.ViewModels.Product;
+
+namespace TeamAAS_VP.Views.Product
+{
+    /// <summary>
+    /// Interaction logic for VisionStaticAccuracyAnalyzer
+    /// </summary>
+    public partial class VisionStaticAccuracyAnalyzer : UserControl
+    {
+        VisionStaticAccuracyAnalyzerViewModel VM;
+        public VisionStaticAccuracyAnalyzer()
+        {
+            InitializeComponent();
+            VM = DataContext as VisionStaticAccuracyAnalyzerViewModel;
+            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;
+                    }
+                }
+            }
+        }
+    }
+}

+ 1 - 0
TeamAAS-VM/packages.config

@@ -7,6 +7,7 @@
   <package id="EntityFramework" version="6.5.1" targetFramework="net48" />
   <package id="Enums.NET" version="5.0.0" targetFramework="net48" />
   <package id="ExtendedNumerics.BigDecimal" version="3001.0.1.201" targetFramework="net48" />
+  <package id="iTextSharp" version="5.5.13.4" targetFramework="net48" />
   <package id="log4net" version="3.2.0" targetFramework="net48" />
   <package id="MahApps.Metro" version="2.4.10" targetFramework="net48" />
   <package id="MaterialDesignColors" version="5.2.1" targetFramework="net48" />