693 lines
27 KiB
C#
693 lines
27 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using Prism.Commands;
|
||
using Prism.Mvvm;
|
||
using XP.Common.GeneralForm.Views;
|
||
using XP.Common.Logging.Interfaces;
|
||
using XP.Common.PdfViewer.Interfaces;
|
||
using XP.ReportEngine.Configs;
|
||
using XP.ReportEngine.Interfaces;
|
||
using XP.ReportEngine.Models;
|
||
using XP.ReportEngine.Services;
|
||
|
||
namespace XP.ReportEngine.ViewModels
|
||
{
|
||
/// <summary>
|
||
/// 报告生成演示窗口 ViewModel | Report generation demo window ViewModel
|
||
/// 演示如何使用 IReportService 生成 PDF 报告
|
||
/// Demonstrates how to use IReportService to generate PDF reports
|
||
/// </summary>
|
||
public class ReportDemoViewModel : BindableBase
|
||
{
|
||
private readonly IReportService _reportService;
|
||
private readonly IPdfViewerService _pdfViewerService;
|
||
private readonly IPdfPrintService _pdfPrintService;
|
||
private readonly ILoggerService _logger;
|
||
private readonly ReportIdGenerator _reportIdGenerator;
|
||
private readonly ReportConfig _reportConfig;
|
||
|
||
private string _productName = "PCB-TEST-001";
|
||
private string _operatorName = "戚明轩 mingxuan.qi@hexagon.com";
|
||
private string _description = "BGA 焊球气泡率检测";
|
||
private string _cncProgram = "Prog001";
|
||
private string _productCode = "PCBA-X100";
|
||
private string _workpieceSN = "SN20250001";
|
||
private string _deviceId = "XP-CT-001";
|
||
private string _machineId = "MC01";
|
||
private string _statusMessage = "就绪";
|
||
private string _lastOutputPath;
|
||
private string _lastOutputDir;
|
||
private bool _isGenerating;
|
||
private bool _formatPdf = true;
|
||
private bool _formatExcel;
|
||
private bool _formatCsv;
|
||
private bool _formatTxt;
|
||
|
||
#region 属性 | Properties
|
||
|
||
/// <summary>
|
||
/// 产品名称 | Product name
|
||
/// </summary>
|
||
public string ProductName
|
||
{
|
||
get => _productName;
|
||
set => SetProperty(ref _productName, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 操作员 | Operator
|
||
/// </summary>
|
||
public string OperatorName
|
||
{
|
||
get => _operatorName;
|
||
set => SetProperty(ref _operatorName, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 描述 | Description
|
||
/// </summary>
|
||
public string Description
|
||
{
|
||
get => _description;
|
||
set => SetProperty(ref _description, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// CNC 程序名称 | CNC program name
|
||
/// </summary>
|
||
public string CncProgram
|
||
{
|
||
get => _cncProgram;
|
||
set => SetProperty(ref _cncProgram, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 产品类型码 | Product type code
|
||
/// </summary>
|
||
public string ProductCode
|
||
{
|
||
get => _productCode;
|
||
set => SetProperty(ref _productCode, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 工件 SN 码 | Workpiece serial number
|
||
/// </summary>
|
||
public string WorkpieceSN
|
||
{
|
||
get => _workpieceSN;
|
||
set => SetProperty(ref _workpieceSN, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测设备编号 | Inspection device ID
|
||
/// </summary>
|
||
public string DeviceId
|
||
{
|
||
get => _deviceId;
|
||
set => SetProperty(ref _deviceId, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生产机台号 | Production machine ID
|
||
/// </summary>
|
||
public string MachineId
|
||
{
|
||
get => _machineId;
|
||
set => SetProperty(ref _machineId, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 状态信息 | Status message
|
||
/// </summary>
|
||
public string StatusMessage
|
||
{
|
||
get => _statusMessage;
|
||
set => SetProperty(ref _statusMessage, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否正在生成 | Whether generating
|
||
/// </summary>
|
||
public bool IsGenerating
|
||
{
|
||
get => _isGenerating;
|
||
set
|
||
{
|
||
if (SetProperty(ref _isGenerating, value))
|
||
{
|
||
GenerateReportCommand.RaiseCanExecuteChanged();
|
||
OpenViewerCommand.RaiseCanExecuteChanged();
|
||
PrintReportCommand.RaiseCanExecuteChanged();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否生成 PDF 格式 | Whether to generate PDF format
|
||
/// </summary>
|
||
public bool FormatPdf
|
||
{
|
||
get => _formatPdf;
|
||
set => SetProperty(ref _formatPdf, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否生成 Excel 格式 | Whether to generate Excel format
|
||
/// </summary>
|
||
public bool FormatExcel
|
||
{
|
||
get => _formatExcel;
|
||
set => SetProperty(ref _formatExcel, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否生成 CSV 格式 | Whether to generate CSV format
|
||
/// </summary>
|
||
public bool FormatCsv
|
||
{
|
||
get => _formatCsv;
|
||
set => SetProperty(ref _formatCsv, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否生成 TXT 格式 | Whether to generate TXT format
|
||
/// </summary>
|
||
public bool FormatTxt
|
||
{
|
||
get => _formatTxt;
|
||
set => SetProperty(ref _formatTxt, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 命令 | Commands
|
||
|
||
/// <summary>
|
||
/// 生成报告命令 | Generate report command
|
||
/// </summary>
|
||
public DelegateCommand GenerateReportCommand { get; }
|
||
|
||
/// <summary>
|
||
/// 打开 PDF 阅读器命令 | Open PDF viewer command
|
||
/// </summary>
|
||
public DelegateCommand OpenViewerCommand { get; }
|
||
|
||
/// <summary>
|
||
/// 打印报告命令 | Print report command
|
||
/// </summary>
|
||
public DelegateCommand PrintReportCommand { get; }
|
||
|
||
/// <summary>
|
||
/// 打开输出目录命令 | Open output folder command
|
||
/// </summary>
|
||
public DelegateCommand OpenOutputFolderCommand { get; }
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 构造函数 | Constructor
|
||
/// </summary>
|
||
public ReportDemoViewModel(
|
||
IReportService reportService,
|
||
IPdfViewerService pdfViewerService,
|
||
IPdfPrintService pdfPrintService,
|
||
ILoggerService logger,
|
||
ReportIdGenerator reportIdGenerator,
|
||
ReportConfig reportConfig)
|
||
{
|
||
_reportService = reportService ?? throw new ArgumentNullException(nameof(reportService));
|
||
_pdfViewerService = pdfViewerService ?? throw new ArgumentNullException(nameof(pdfViewerService));
|
||
_pdfPrintService = pdfPrintService ?? throw new ArgumentNullException(nameof(pdfPrintService));
|
||
_logger = logger?.ForModule<ReportDemoViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||
_reportIdGenerator = reportIdGenerator ?? throw new ArgumentNullException(nameof(reportIdGenerator));
|
||
_reportConfig = reportConfig ?? throw new ArgumentNullException(nameof(reportConfig));
|
||
|
||
GenerateReportCommand = new DelegateCommand(async () => await GenerateReportAsync(), () => !IsGenerating);
|
||
OpenViewerCommand = new DelegateCommand(OpenViewer, () => !IsGenerating && !string.IsNullOrEmpty(_lastOutputPath));
|
||
PrintReportCommand = new DelegateCommand(PrintReport, () => !IsGenerating && !string.IsNullOrEmpty(_lastOutputPath));
|
||
OpenOutputFolderCommand = new DelegateCommand(OpenOutputFolder, () => !string.IsNullOrEmpty(_lastOutputDir));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成报告(带进度条,支持多格式)| Generate report (with progress window, multi-format)
|
||
/// </summary>
|
||
private async Task GenerateReportAsync()
|
||
{
|
||
IsGenerating = true;
|
||
StatusMessage = "正在生成报告...";
|
||
|
||
try
|
||
{
|
||
// 收集用户勾选的输出格式 | Collect selected output formats
|
||
var selectedFormats = new List<ReportOutputFormat>();
|
||
if (FormatPdf) selectedFormats.Add(ReportOutputFormat.Pdf);
|
||
if (FormatExcel) selectedFormats.Add(ReportOutputFormat.Excel);
|
||
if (FormatCsv) selectedFormats.Add(ReportOutputFormat.Csv);
|
||
if (FormatTxt) selectedFormats.Add(ReportOutputFormat.Txt);
|
||
|
||
if (selectedFormats.Count == 0)
|
||
{
|
||
MessageBox.Show("请至少选择一种输出格式。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
StatusMessage = "未选择输出格式";
|
||
IsGenerating = false;
|
||
return;
|
||
}
|
||
|
||
// 构建文件名占位符参数 | Build file name placeholder parameters
|
||
var fileNameParams = new Dictionary<string, string>
|
||
{
|
||
["ReportId"] = _reportIdGenerator.GenerateNext(),
|
||
["ProductName"] = ProductName,
|
||
["CncProgram"] = CncProgram,
|
||
["ProductCode"] = ProductCode,
|
||
["WorkpieceSN"] = WorkpieceSN,
|
||
["DeviceId"] = DeviceId,
|
||
["MachineId"] = MachineId,
|
||
["Result"] = "Pass"
|
||
};
|
||
|
||
// 显示即将生成的格式列表 | Show formats to be generated
|
||
var formatNames = string.Join(", ", selectedFormats);
|
||
var defaultOutputDir = _reportConfig.OutputDirectory;
|
||
|
||
var result = MessageBox.Show(
|
||
$"将生成以下格式的报告:{formatNames}\r\n\r\n输出目录:{defaultOutputDir}\r\n\r\n是否继续?",
|
||
"报告生成确认",
|
||
MessageBoxButton.OKCancel,
|
||
MessageBoxImage.Question);
|
||
|
||
if (result == MessageBoxResult.Cancel)
|
||
{
|
||
StatusMessage = "已取消生成";
|
||
IsGenerating = false;
|
||
return;
|
||
}
|
||
|
||
// 创建进度条窗口 | Create progress window
|
||
var progressWindow = new ProgressWindow(
|
||
title: "报告生成中",
|
||
message: "正在准备数据...",
|
||
isCancelable: false,
|
||
logger: _logger);
|
||
|
||
progressWindow.Owner = Application.Current.MainWindow;
|
||
progressWindow.Show();
|
||
|
||
try
|
||
{
|
||
// 步骤 1:准备模拟数据 | Step 1: Prepare mock data
|
||
progressWindow.UpdateProgress("正在准备检测数据...", 10);
|
||
await Task.Delay(300); // 模拟耗时 | Simulate delay
|
||
|
||
var processorOutputs = CreateMockProcessorOutputs();
|
||
|
||
// 步骤 2:构建报告请求 | Step 2: Build report request
|
||
progressWindow.UpdateProgress("正在组装报告数据...", 30);
|
||
await Task.Delay(200);
|
||
|
||
var request = new ReportRequest
|
||
{
|
||
ProcessorOutputs = processorOutputs,
|
||
Metadata = new ReportMetadata
|
||
{
|
||
ReportId = fileNameParams["ReportId"],
|
||
InspectionDate = DateTime.Now,
|
||
SampleName = ProductName,
|
||
OperatorName = OperatorName,
|
||
Description = Description
|
||
},
|
||
FileNameParameters = fileNameParams,
|
||
Formats = selectedFormats
|
||
};
|
||
|
||
// 注入工件整体图片 | Inject workpiece overview image
|
||
var workpieceImagePath = System.IO.Path.Combine(MockImageDirectory, "OverView.png");
|
||
if (File.Exists(workpieceImagePath))
|
||
{
|
||
request.AdditionalImages["workpieceImage"] = new ImageData
|
||
{
|
||
SourceType = ImageSourceType.FilePath,
|
||
FilePath = workpieceImagePath
|
||
};
|
||
}
|
||
|
||
// 步骤 3:调用报告服务生成(在后台线程执行,避免阻塞 UI)
|
||
// Step 3: Call report service (on background thread to avoid blocking UI)
|
||
progressWindow.UpdateProgress($"正在生成报告({formatNames})...", 60);
|
||
await Task.Delay(200);
|
||
|
||
var genResult = await Task.Run(() => _reportService.GenerateAsync(request));
|
||
|
||
// 步骤 4:处理结果 | Step 4: Handle result
|
||
progressWindow.UpdateProgress("正在完成...", 95);
|
||
await Task.Delay(200);
|
||
|
||
if (genResult.IsSuccess)
|
||
{
|
||
_lastOutputPath = genResult.OutputFilePath;
|
||
_lastOutputDir = Path.GetDirectoryName(genResult.OutputFilePath);
|
||
|
||
// 构建状态信息:列出所有生成的文件 | Build status: list all generated files
|
||
if (genResult.OutputFilePaths != null && genResult.OutputFilePaths.Count > 0)
|
||
{
|
||
var paths = new List<string>();
|
||
foreach (var output in genResult.OutputFilePaths)
|
||
{
|
||
paths.Add($"{output.Format}: {Path.GetFileName(output.FilePath)}");
|
||
}
|
||
StatusMessage = $"生成成功 ({genResult.OutputFilePaths.Count} 个文件):{string.Join(" | ", paths)}";
|
||
}
|
||
else
|
||
{
|
||
StatusMessage = $"报告生成成功:{genResult.OutputFilePath}";
|
||
}
|
||
|
||
_logger.Info("报告生成成功:{Path} | Report generated successfully: {Path}", genResult.OutputFilePath);
|
||
|
||
progressWindow.UpdateProgress("报告生成完成!", 100);
|
||
await Task.Delay(500);
|
||
|
||
// 根据配置自动打开 PDF 阅读器(仅当生成了 PDF 时)
|
||
// Auto-open PDF viewer based on config (only when PDF was generated)
|
||
if (_reportConfig.AutoOpenAfterGenerate && FormatPdf && !string.IsNullOrEmpty(genResult.OutputFilePath))
|
||
{
|
||
try
|
||
{
|
||
_pdfViewerService.OpenViewer(genResult.OutputFilePath);
|
||
}
|
||
catch (Exception viewerEx)
|
||
{
|
||
_logger.Warn("自动打开 PDF 失败 | Auto-open PDF failed: {Message}", viewerEx.Message);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
StatusMessage = $"报告生成失败:{genResult.ErrorMessage}";
|
||
_logger.Error(null, "报告生成失败:{Message} | Report generation failed: {Message}", genResult.ErrorMessage);
|
||
|
||
MessageBox.Show(
|
||
$"报告生成失败:\n{genResult.ErrorMessage}",
|
||
"错误",
|
||
MessageBoxButton.OK,
|
||
MessageBoxImage.Error);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
progressWindow.Close();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
StatusMessage = $"报告生成异常:{ex.Message}";
|
||
_logger.Error(ex, "报告生成异常 | Report generation exception: {Message}", ex.Message);
|
||
|
||
MessageBox.Show(
|
||
$"报告生成过程中发生异常:\n{ex.Message}",
|
||
"错误",
|
||
MessageBoxButton.OK,
|
||
MessageBoxImage.Error);
|
||
}
|
||
finally
|
||
{
|
||
IsGenerating = false;
|
||
OpenViewerCommand.RaiseCanExecuteChanged();
|
||
PrintReportCommand.RaiseCanExecuteChanged();
|
||
OpenOutputFolderCommand.RaiseCanExecuteChanged();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开 PDF 阅读器 | Open PDF viewer
|
||
/// </summary>
|
||
private void OpenViewer()
|
||
{
|
||
if (string.IsNullOrEmpty(_lastOutputPath) || !File.Exists(_lastOutputPath))
|
||
{
|
||
MessageBox.Show("PDF 文件不存在,请先生成报告。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 使用 XP.Common PDF 阅读器打开 | Open with XP.Common PDF viewer
|
||
_pdfViewerService.OpenViewer(_lastOutputPath);
|
||
_logger.Info("使用内置 PDF 阅读器打开:{Path} | Opened with built-in PDF viewer: {Path}", _lastOutputPath);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error(ex, "打开 PDF 失败 | Failed to open PDF: {Message}", ex.Message);
|
||
MessageBox.Show($"打开 PDF 失败:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打印报告 | Print report
|
||
/// </summary>
|
||
private void PrintReport()
|
||
{
|
||
if (string.IsNullOrEmpty(_lastOutputPath) || !File.Exists(_lastOutputPath))
|
||
{
|
||
MessageBox.Show("PDF 文件不存在,请先生成报告。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var confirmed = _pdfPrintService.PrintWithDialog(_lastOutputPath);
|
||
if (confirmed)
|
||
{
|
||
_logger.Info("报告已发送到打印机:{Path} | Report sent to printer: {Path}", _lastOutputPath);
|
||
StatusMessage = "报告已发送到打印机";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error(ex, "打印报告失败 | Failed to print report: {Message}", ex.Message);
|
||
MessageBox.Show($"打印失败:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开输出目录 | Open output folder in Explorer
|
||
/// </summary>
|
||
private void OpenOutputFolder()
|
||
{
|
||
if (string.IsNullOrEmpty(_lastOutputDir) || !Directory.Exists(_lastOutputDir))
|
||
{
|
||
MessageBox.Show("输出目录不存在,请先生成报告。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
System.Diagnostics.Process.Start("explorer.exe", _lastOutputDir);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error(ex, "打开输出目录失败 | Failed to open output folder: {Message}", ex.Message);
|
||
MessageBox.Show($"打开目录失败:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
#region 模拟数据 | Mock Data
|
||
|
||
/// <summary>
|
||
/// 模拟图像目录路径 | Mock image directory path
|
||
/// </summary>
|
||
private const string MockImageDirectory = @"D:\XPData\DetectorImages";
|
||
|
||
/// <summary>
|
||
/// 创建模拟处理器输出数据(演示用,覆盖所有检测类型)
|
||
/// Create mock processor outputs (for demo, covers all inspection types)
|
||
/// </summary>
|
||
private List<ProcessorOutput> CreateMockProcessorOutputs()
|
||
{
|
||
return new List<ProcessorOutput>
|
||
{
|
||
CreateLineMeasurementOutput(),
|
||
CreateBgaVoidRateOutput(),
|
||
CreateVoidMeasurementOutput(),
|
||
CreateFillRateOutput()
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建线测量处理器模拟数据 | Create line measurement processor mock data
|
||
/// </summary>
|
||
private ProcessorOutput CreateLineMeasurementOutput()
|
||
{
|
||
return new ProcessorOutput
|
||
{
|
||
ProcessorType = "LineMeasurementProcessor",
|
||
OutputData = new Dictionary<string, object>
|
||
{
|
||
["MeasurementType"] = "TwoPointDistance",
|
||
["Point1"] = "X=125.32, Y=80.15",
|
||
["Point2"] = "X=340.78, Y=80.15",
|
||
["PixelDistance"] = 215.46,
|
||
["ActualDistance"] = 3.256,
|
||
["Unit"] = "mm",
|
||
["Angle"] = 0.0
|
||
},
|
||
AnnotatedImage = LoadMockImage("BGA.png")
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建 BGA 气泡率处理器模拟数据 | Create BGA void rate processor mock data
|
||
/// </summary>
|
||
private ProcessorOutput CreateBgaVoidRateOutput()
|
||
{
|
||
// 生成 64 组 BGA 球模拟数据 | Generate 64 BGA ball mock data
|
||
var bgaBalls = new List<Dictionary<string, object>>();
|
||
var random = new Random(42); // 固定种子确保可重复 | Fixed seed for reproducibility
|
||
var voidLimit = 0.05;
|
||
|
||
for (int i = 1; i <= 64; i++)
|
||
{
|
||
// 大部分合格,少数不合格 | Most pass, a few fail
|
||
double voidRate;
|
||
if (i == 12 || i == 27 || i == 41 || i == 58)
|
||
{
|
||
// 这几个球不合格 | These balls fail
|
||
voidRate = Math.Round(0.05 + random.NextDouble() * 0.03, 4);
|
||
}
|
||
else
|
||
{
|
||
voidRate = Math.Round(random.NextDouble() * 0.045, 4);
|
||
}
|
||
|
||
var area = Math.Round(190.0 + random.NextDouble() * 15.0, 1);
|
||
var classification = voidRate > voidLimit ? "Fail" : "Pass";
|
||
|
||
bgaBalls.Add(new Dictionary<string, object>
|
||
{
|
||
["Index"] = i,
|
||
["VoidRate"] = voidRate,
|
||
["Area"] = area,
|
||
["Classification"] = classification
|
||
});
|
||
}
|
||
|
||
return new ProcessorOutput
|
||
{
|
||
ProcessorType = "BgaVoidRateProcessor",
|
||
OutputData = new Dictionary<string, object>
|
||
{
|
||
["BgaCount"] = 64,
|
||
["VoidRate"] = 0.028,
|
||
["FillRate"] = 0.972,
|
||
["TotalBgaArea"] = 12500.5,
|
||
["TotalVoidArea"] = 350.2,
|
||
["Classification"] = "Pass",
|
||
["VoidLimit"] = 0.05,
|
||
["BgaBalls"] = bgaBalls
|
||
},
|
||
AnnotatedImage = LoadMockImage("BGA.png")
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建空隙测量处理器模拟数据 | Create void measurement processor mock data
|
||
/// </summary>
|
||
private ProcessorOutput CreateVoidMeasurementOutput()
|
||
{
|
||
return new ProcessorOutput
|
||
{
|
||
ProcessorType = "VoidMeasurementProcessor",
|
||
OutputData = new Dictionary<string, object>
|
||
{
|
||
["RoiArea"] = 5000.0,
|
||
["TotalVoidArea"] = 125.8,
|
||
["VoidRate"] = 0.025,
|
||
["VoidLimit"] = 0.05,
|
||
["VoidCount"] = 5,
|
||
["MaxVoidArea"] = 65.2,
|
||
["Classification"] = "Pass",
|
||
["Voids"] = new List<Dictionary<string, object>>
|
||
{
|
||
new() { ["Index"] = 1, ["Area"] = 65.2, ["AreaPercent"] = 1.30, ["CenterX"] = 120.5, ["CenterY"] = 85.3 },
|
||
new() { ["Index"] = 2, ["Area"] = 38.4, ["AreaPercent"] = 0.77, ["CenterX"] = 200.1, ["CenterY"] = 150.8 },
|
||
new() { ["Index"] = 3, ["Area"] = 22.2, ["AreaPercent"] = 0.44, ["CenterX"] = 80.0, ["CenterY"] = 220.5 },
|
||
new() { ["Index"] = 4, ["Area"] = 15.6, ["AreaPercent"] = 0.31, ["CenterX"] = 310.2, ["CenterY"] = 95.7 },
|
||
new() { ["Index"] = 5, ["Area"] = 8.9, ["AreaPercent"] = 0.18, ["CenterX"] = 155.8, ["CenterY"] = 280.1 }
|
||
}
|
||
},
|
||
AnnotatedImage = LoadMockImage("Void.png")
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建通孔填锡率处理器模拟数据 | Create via fill rate processor mock data
|
||
/// </summary>
|
||
private ProcessorOutput CreateFillRateOutput()
|
||
{
|
||
return new ProcessorOutput
|
||
{
|
||
ProcessorType = "FillRateProcessor",
|
||
OutputData = new Dictionary<string, object>
|
||
{
|
||
["FillRate"] = 0.85,
|
||
["VoidRate"] = 0.15,
|
||
["FullDistance"] = 1.60,
|
||
["FillDistance"] = 1.36,
|
||
["THTLimit"] = 0.75,
|
||
["Classification"] = "Pass",
|
||
["E1"] = new Dictionary<string, object>
|
||
{
|
||
["CenterX"] = 256.0, ["CenterY"] = 256.0,
|
||
["SemiAxisA"] = 120.5, ["SemiAxisB"] = 118.2, ["Angle"] = 2.3
|
||
},
|
||
["E2"] = new Dictionary<string, object>
|
||
{
|
||
["CenterX"] = 256.0, ["CenterY"] = 256.0,
|
||
["SemiAxisA"] = 95.8, ["SemiAxisB"] = 93.1, ["Angle"] = 2.3
|
||
},
|
||
["E3"] = new Dictionary<string, object>
|
||
{
|
||
["CenterX"] = 256.0, ["CenterY"] = 256.0,
|
||
["SemiAxisA"] = 70.2, ["SemiAxisB"] = 68.5, ["Angle"] = 1.8
|
||
},
|
||
["E4"] = new Dictionary<string, object>
|
||
{
|
||
["CenterX"] = 256.0, ["CenterY"] = 256.0,
|
||
["SemiAxisA"] = 45.0, ["SemiAxisB"] = 43.7, ["Angle"] = 1.5
|
||
}
|
||
},
|
||
AnnotatedImage = LoadMockImage("Void.png")
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载模拟图像文件 | Load mock image file
|
||
/// 从指定目录加载图像,文件不存在时返回 null
|
||
/// Loads image from specified directory, returns null if file not found
|
||
/// </summary>
|
||
/// <param name="fileName">图像文件名 | Image file name</param>
|
||
/// <returns>图像数据对象或 null | ImageData object or null</returns>
|
||
private ImageData LoadMockImage(string fileName)
|
||
{
|
||
var filePath = System.IO.Path.Combine(MockImageDirectory, fileName);
|
||
if (!File.Exists(filePath))
|
||
{
|
||
_logger.Warn("模拟图像文件不存在:{Path} | Mock image file not found: {Path}", filePath);
|
||
return null;
|
||
}
|
||
|
||
return new ImageData
|
||
{
|
||
SourceType = ImageSourceType.FilePath,
|
||
FilePath = filePath
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|