下拉检测模块列表

This commit is contained in:
zhengxuan.zhang
2026-05-06 15:28:29 +08:00
parent 3bee2898c5
commit 7c0f9dab73
4 changed files with 323 additions and 73 deletions
@@ -1,4 +1,4 @@
// Feature: cnc-run-execution
// Feature: cnc-run-execution
// Properties 1, 2, 12: CncEditorViewModel execution control
using System;
@@ -17,6 +17,7 @@ using XplorePlane.Models;
using XplorePlane.Services.AppState;
using XplorePlane.Services.Cnc;
using XplorePlane.Services.Storage;
using XplorePlane.Services;
using XplorePlane.ViewModels.Cnc;
using Xunit;
@@ -24,16 +25,18 @@ namespace XplorePlane.Tests.ViewModels
{
public class CncEditorViewModelTests
{
// ── Helpers ──────────────────────────────────────────────────────────
// 鈹€鈹€ Helpers 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
private static CncEditorViewModel CreateVm(
Mock<ICncExecutionService> mockExecSvc = null,
CncProgram initialProgram = null)
CncProgram initialProgram = null,
Mock<IPipelinePersistenceService> mockPipelinePersistenceService = null)
{
var mockCncProgramSvc = new Mock<ICncProgramService>();
var mockAppState = new Mock<IAppStateService>();
var mockLogger = new Mock<ILoggerService>();
var mockDataPathService = new Mock<IXpDataPathService>();
mockPipelinePersistenceService ??= new Mock<IPipelinePersistenceService>();
mockLogger.Setup(l => l.ForModule<CncEditorViewModel>()).Returns(mockLogger.Object);
mockDataPathService.SetupGet(s => s.PlanPath).Returns(System.IO.Path.GetTempPath());
@@ -47,16 +50,52 @@ namespace XplorePlane.Tests.ViewModels
DateTime.UtcNow, DateTime.UtcNow,
new List<CncNode>
{
new ReferencePointNode(Guid.NewGuid(), 0, "参考点_0", 0, 0, 0, 0, 0, 0, false, 0, 0)
new ReferencePointNode(Guid.NewGuid(), 0, "鍙傝€冪偣_0", 0, 0, 0, 0, 0, 0, false, 0, 0)
}.AsReadOnly()));
mockCncProgramSvc
.Setup(s => s.CreateNode(It.IsAny<CncNodeType>()))
.Returns((CncNodeType nodeType) => nodeType switch
{
CncNodeType.InspectionModule => new InspectionModuleNode(Guid.NewGuid(), 0, "检测模块_0", new PipelineModel()),
CncNodeType.ReferencePoint => new ReferencePointNode(Guid.NewGuid(), 0, "参考点_0", 0, 0, 0, 0, 0, 0, false, 0, 0),
_ => throw new InvalidOperationException($"Unsupported node type in test: {nodeType}")
});
mockCncProgramSvc
.Setup(s => s.InsertNode(It.IsAny<CncProgram>(), It.IsAny<int>(), It.IsAny<CncNode>()))
.Returns((CncProgram program, int afterIndex, CncNode node) =>
{
var nodes = program.Nodes.ToList();
var insertIndex = Math.Clamp(afterIndex + 1, 0, nodes.Count);
nodes.Insert(insertIndex, node with { Index = insertIndex });
for (var i = 0; i < nodes.Count; i++)
{
nodes[i] = nodes[i] with { Index = i };
}
return program with { Nodes = nodes.AsReadOnly(), UpdatedAt = DateTime.UtcNow };
});
mockCncProgramSvc
.Setup(s => s.UpdateNode(It.IsAny<CncProgram>(), It.IsAny<int>(), It.IsAny<CncNode>()))
.Returns((CncProgram program, int index, CncNode updatedNode) =>
{
var nodes = program.Nodes.ToList();
nodes[index] = updatedNode with { Index = index };
return program with { Nodes = nodes.AsReadOnly(), UpdatedAt = DateTime.UtcNow };
});
var vm = new CncEditorViewModel(
mockCncProgramSvc.Object,
mockAppState.Object,
new EventAggregator(),
mockLogger.Object,
mockExecSvc.Object,
mockDataPathService.Object);
mockDataPathService.Object,
mockPipelinePersistenceService.Object);
if (initialProgram != null)
{
@@ -82,9 +121,9 @@ namespace XplorePlane.Tests.ViewModels
return new CncProgram(Guid.NewGuid(), "TestProgram", DateTime.UtcNow, DateTime.UtcNow, nodes);
}
// ── Property 1: 运行/停止按钮状态互斥 ────────────────────────────────
// 鈹€鈹€ Property 1: 杩愯/鍋滄鎸夐挳鐘舵€佷簰鏂?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
// Feature: cnc-run-execution, Property 1: 运行/停止按钮状态互斥
// Feature: cnc-run-execution, Property 1: 杩愯/鍋滄鎸夐挳鐘舵€佷簰鏂?
// Validates: Requirements 1.1, 1.3, 1.4
[Property(MaxTest = 100)]
public Property RunStop_Commands_AreMutuallyExclusive()
@@ -129,9 +168,9 @@ namespace XplorePlane.Tests.ViewModels
});
}
// ── Property 2: 执行完成后状态重置 ───────────────────────────────────
// 鈹€鈹€ Property 2: 鎵ц瀹屾垚鍚庣姸鎬侀噸缃?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
// Feature: cnc-run-execution, Property 2: 执行完成后状态重置
// Feature: cnc-run-execution, Property 2: 鎵ц瀹屾垚鍚庣姸鎬侀噸缃?
// Validates: Requirements 1.7, 6.5
[Property(MaxTest = 100)]
public Property AfterExecution_IsRunningFalse_AllNodesIdle()
@@ -180,9 +219,9 @@ namespace XplorePlane.Tests.ViewModels
});
}
// ── Property 12: 执行中编辑命令全部禁用 ──────────────────────────────
// 鈹€鈹€ Property 12: 鎵ц涓紪杈戝懡浠ゅ叏閮ㄧ鐢?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
// Feature: cnc-run-execution, Property 12: 执行中编辑命令全部禁用
// Feature: cnc-run-execution, Property 12: 鎵ц涓紪杈戝懡浠ゅ叏閮ㄧ鐢?
// Validates: Requirements 6.7
[Property(MaxTest = 100)]
public Property WhileRunning_AllEditCommands_AreDisabled()
@@ -232,5 +271,50 @@ namespace XplorePlane.Tests.ViewModels
return allDisabled;
});
}
[Fact]
public async Task InsertInspectionModuleFromPipelineFileAsync_LoadsPipelineAndInsertsNode()
{
var pipelineFile = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"{Guid.NewGuid():N}.xpm");
await System.IO.File.WriteAllTextAsync(pipelineFile, "{}");
try
{
var expectedPipeline = new PipelineModel
{
Name = "BuiltIn/ModuleA"
};
var mockPipelinePersistenceService = new Mock<IPipelinePersistenceService>();
mockPipelinePersistenceService
.Setup(s => s.LoadAsync(pipelineFile))
.ReturnsAsync(expectedPipeline);
var initialProgram = new CncProgram(
Guid.NewGuid(),
"TestProgram",
DateTime.UtcNow,
DateTime.UtcNow,
new List<CncNode>
{
new SavePositionNode(Guid.NewGuid(), 0, "保存位置_0", MotionState.Default)
}.AsReadOnly());
var vm = CreateVm(
initialProgram: initialProgram,
mockPipelinePersistenceService: mockPipelinePersistenceService);
await vm.InsertInspectionModuleFromPipelineFileAsync(pipelineFile);
var insertedNode = Assert.IsType<InspectionModuleNode>(vm.Nodes.Last().Model);
Assert.Same(expectedPipeline, insertedNode.Pipeline);
Assert.Equal("BuiltIn/ModuleA", insertedNode.Pipeline.Name);
}
finally
{
if (System.IO.File.Exists(pipelineFile))
System.IO.File.Delete(pipelineFile);
}
}
}
}
@@ -16,6 +16,7 @@ using XplorePlane.Events;
using XplorePlane.Models;
using XplorePlane.Services.AppState;
using XplorePlane.Services.Cnc;
using XplorePlane.Services;
using XplorePlane.Services.Storage;
namespace XplorePlane.ViewModels.Cnc
@@ -30,6 +31,7 @@ namespace XplorePlane.ViewModels.Cnc
private readonly ILoggerService _logger;
private readonly ICncExecutionService _cncExecutionService;
private readonly IXpDataPathService _dataPathService;
private readonly IPipelinePersistenceService _pipelinePersistenceService;
private CncProgram _currentProgram;
private ObservableCollection<CncNodeViewModel> _nodes;
@@ -54,7 +56,8 @@ namespace XplorePlane.ViewModels.Cnc
IEventAggregator eventAggregator,
ILoggerService logger,
ICncExecutionService cncExecutionService,
IXpDataPathService dataPathService)
IXpDataPathService dataPathService,
IPipelinePersistenceService pipelinePersistenceService)
{
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
ArgumentNullException.ThrowIfNull(appStateService);
@@ -62,6 +65,7 @@ namespace XplorePlane.ViewModels.Cnc
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<CncEditorViewModel>();
_cncExecutionService = cncExecutionService ?? throw new ArgumentNullException(nameof(cncExecutionService));
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
_pipelinePersistenceService = pipelinePersistenceService ?? throw new ArgumentNullException(nameof(pipelinePersistenceService));
_nodes = new ObservableCollection<CncNodeViewModel>();
_treeNodes = new ObservableCollection<CncNodeViewModel>();
@@ -444,6 +448,57 @@ namespace XplorePlane.ViewModels.Cnc
}
}
public async Task InsertInspectionModuleFromPipelineFileAsync(string filePath)
{
if (IsRunning)
return;
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("检测模块文件路径不能为空。", nameof(filePath));
if (!File.Exists(filePath))
throw new FileNotFoundException("检测模块文件不存在。", filePath);
if (_currentProgram == null)
{
ExecuteNewProgram();
}
var pipeline = await _pipelinePersistenceService.LoadAsync(filePath);
try
{
var node = _cncProgramService.CreateNode(CncNodeType.InspectionModule);
if (node is not InspectionModuleNode inspectionModuleNode)
throw new InvalidOperationException("无法创建检测模块节点。");
var pipelineName = string.IsNullOrWhiteSpace(pipeline.Name)
? Path.GetFileNameWithoutExtension(filePath)
: pipeline.Name;
pipeline.Name = pipelineName;
var configuredNode = inspectionModuleNode with
{
Pipeline = pipeline,
Name = pipelineName
};
int afterIndex = ResolveInsertAfterIndex(CncNodeType.InspectionModule);
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, configuredNode);
_preferredSelectedNodeId = configuredNode.Id;
ClearPendingInsertAnchor();
OnProgramEdited();
StatusMessage = $"已插入检测模块:{pipelineName}";
_logger.Info("Inserted built-in inspection module from file: {FilePath}", filePath);
}
catch (InvalidOperationException ex)
{
_logger.Warn("Built-in inspection module insertion blocked: {Message}", ex.Message);
throw;
}
}
private string GetPlanDirectory()
{
var directory = _dataPathService.PlanPath;
+144 -54
View File
@@ -1,4 +1,4 @@
using Microsoft.Win32;
using Microsoft.Win32;
using Prism.Commands;
using Prism.Events;
using Prism.Ioc;
@@ -7,6 +7,8 @@ using System;
using System.Collections.ObjectModel;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using XplorePlane.Events;
@@ -79,6 +81,7 @@ namespace XplorePlane.ViewModels
public DelegateCommand InsertCompleteProgramCommand { get; }
public DelegateCommand InsertInspectionMarkerCommand { get; }
public DelegateCommand InsertInspectionModuleCommand { get; }
public DelegateCommand InsertBuiltInInspectionModuleCommand { get; }
public DelegateCommand InsertSaveNodeCommand { get; }
public DelegateCommand InsertPauseDialogCommand { get; }
public DelegateCommand InsertWaitDelayCommand { get; }
@@ -138,6 +141,20 @@ namespace XplorePlane.ViewModels
public string ReportRootPath => _xpDataPathService.ReportPath;
public ObservableCollection<BuiltInInspectionModuleItem> BuiltInInspectionModules { get; } = new();
public BuiltInInspectionModuleItem SelectedBuiltInInspectionModule
{
get => _selectedBuiltInInspectionModule;
set
{
if (SetProperty(ref _selectedBuiltInInspectionModule, value))
{
InsertBuiltInInspectionModuleCommand?.RaiseCanExecuteChanged();
}
}
}
/// <summary>右侧图像区域内容 | Right-side image panel content</summary>
public object ImagePanelContent
{
@@ -160,7 +177,6 @@ namespace XplorePlane.ViewModels
}
// 窗口引用(单例窗口防止重复打开)
private Window _motionDebugWindow;
private Window _detectorConfigWindow;
private Window _plcAddrConfigWindow;
@@ -175,6 +191,7 @@ namespace XplorePlane.ViewModels
private string _licenseInfo = "当前时间";
private string _dataRootPath = string.Empty;
private BuiltInInspectionModuleItem _selectedBuiltInInspectionModule;
public MainViewModel(
ILoggerService logger,
@@ -208,8 +225,8 @@ namespace XplorePlane.ViewModels
var node = _cncEditorViewModel.SelectedNode;
if (node?.ResultImage != null)
{
_logger.Info("[图像链路] 切换到节点 [{Name}],显示缓存结果图像", node.Name);
_mainViewportService.SetManualImage(node.ResultImage, $"CNC节点:{node.Name}");
_logger.Info("[Image] Switched to node [{Name}], showing cached result image.", node.Name);
_mainViewportService.SetManualImage(node.ResultImage, $"CNC node: {node.Name}");
}
}
};
@@ -232,7 +249,7 @@ namespace XplorePlane.ViewModels
LoadImageCommand = new DelegateCommand(ExecuteLoadImage);
OpenCncEditorCommand = new DelegateCommand(ExecuteOpenCncEditor);
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编"));
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编"));
OpenToolboxCommand = new DelegateCommand(ExecuteOpenToolbox);
OpenLibraryVersionsCommand = new DelegateCommand(() => ShowWindow(new Views.LibraryVersionsWindow(), "关于"));
OpenUserManualCommand = new DelegateCommand(ExecuteOpenUserManual);
@@ -249,6 +266,9 @@ namespace XplorePlane.ViewModels
InsertCompleteProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertCompleteProgramCommand.Execute()));
InsertInspectionMarkerCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertInspectionMarkerCommand.Execute()));
InsertInspectionModuleCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertInspectionModuleCommand.Execute()));
InsertBuiltInInspectionModuleCommand = new DelegateCommand(
async () => await ExecuteInsertBuiltInInspectionModuleAsync(),
CanExecuteInsertBuiltInInspectionModule);
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertSaveNodeCommand.Execute()));
InsertPauseDialogCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertPauseDialogCommand.Execute()));
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertWaitDelayCommand.Execute()));
@@ -267,7 +287,6 @@ namespace XplorePlane.ViewModels
BubbleMeasureCommand = new DelegateCommand(ExecuteBubbleMeasure);
// 辅助线命令
ToggleCrosshairCommand = new DelegateCommand(() =>
_eventAggregator.GetEvent<ToggleCrosshairEvent>().Publish());
@@ -286,6 +305,7 @@ namespace XplorePlane.ViewModels
ViewportPanelWidth = new GridLength(1, GridUnitType.Star);
ImagePanelWidth = new GridLength(320);
DataRootPath = _xpDataPathService.RootPath;
LoadBuiltInInspectionModules();
_logger.Info("MainViewModel 已初始化");
}
@@ -319,7 +339,7 @@ namespace XplorePlane.ViewModels
private void ExecuteOpenToolbox()
{
ShowOrActivate(_toolboxWindow, w => _toolboxWindow = w, () => new Views.OperatorToolboxWindow(), "算子工具箱");
ShowOrActivate(_toolboxWindow, w => _toolboxWindow = w, () => new Views.OperatorToolboxWindow(), "Operator Toolbox");
}
private void ExecuteOpenCncEditor()
@@ -360,15 +380,15 @@ namespace XplorePlane.ViewModels
var manualPath = ConfigurationManager.AppSettings["UserManual"];
if (string.IsNullOrEmpty(manualPath))
{
_logger.Warn("未配置用户手册路径");
MessageBox.Show("未配置用户手册路径,请检查 App.config 中的 UserManual 配置项。",
_logger.Warn("User manual path is not configured.");
MessageBox.Show("User manual path is not configured. Please check the UserManual setting in App.config.",
"提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (!File.Exists(manualPath))
{
_logger.Warn("用户手册文件不存在:{Path}", manualPath);
_logger.Warn("User manual file not found: {Path}", manualPath);
MessageBox.Show($"用户手册文件不存在:\n{manualPath}",
"提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
@@ -381,8 +401,8 @@ namespace XplorePlane.ViewModels
}
catch (Exception ex)
{
_logger.Error(ex, "打开用户手册失败");
MessageBox.Show($"打开用户手册失败:{ex.Message}",
_logger.Error(ex, "Failed to open user manual.");
MessageBox.Show($"Failed to open user manual: {ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -403,7 +423,7 @@ namespace XplorePlane.ViewModels
}
catch (Exception ex)
{
_logger.Error(ex, "打开相机设置失败");
_logger.Error(ex, "Failed to open camera settings.");
}
}
@@ -439,8 +459,8 @@ namespace XplorePlane.ViewModels
}
catch (Exception ex)
{
_logger.Error(ex, "浏览 XP 数据根目录失败");
MessageBox.Show($"浏览数据根目录失败:{ex.Message}",
_logger.Error(ex, "Failed to browse XP data root.");
MessageBox.Show($"Failed to browse data root: {ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -460,21 +480,89 @@ namespace XplorePlane.ViewModels
RaisePropertyChanged(nameof(ToolsRootPath));
RaisePropertyChanged(nameof(ResultsRootPath));
RaisePropertyChanged(nameof(ReportRootPath));
LoadBuiltInInspectionModules();
MessageBox.Show("XP 数据根目录已保存。新的保存/加载对话框会立即使用新路径。",
MessageBox.Show("XP data root saved. New save/load dialogs will use the new path immediately.",
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
_logger.Error(ex, "保存 XP 数据根目录失败");
MessageBox.Show($"保存数据根目录失败:{ex.Message}",
_logger.Error(ex, "Failed to save XP data root.");
MessageBox.Show($"Failed to save data root: {ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private bool CanExecuteInsertBuiltInInspectionModule()
{
return SelectedBuiltInInspectionModule != null;
}
private async Task ExecuteInsertBuiltInInspectionModuleAsync()
{
var module = SelectedBuiltInInspectionModule;
if (module == null)
return;
try
{
ShowCncEditor();
await _cncEditorViewModel.InsertInspectionModuleFromPipelineFileAsync(module.FilePath);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to insert built-in inspection module: {FilePath}", module.FilePath);
MessageBox.Show($"Failed to insert built-in inspection module: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void LoadBuiltInInspectionModules()
{
BuiltInInspectionModules.Clear();
try
{
var toolsPath = _xpDataPathService.ToolsPath;
if (!Directory.Exists(toolsPath))
{
SelectedBuiltInInspectionModule = null;
return;
}
var files = Directory
.EnumerateFiles(toolsPath, "*.xpm", SearchOption.AllDirectories)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
.Select(path => new BuiltInInspectionModuleItem(
GetBuiltInModuleDisplayName(toolsPath, path),
path))
.ToList();
foreach (var file in files)
{
BuiltInInspectionModules.Add(file);
}
SelectedBuiltInInspectionModule = BuiltInInspectionModules.FirstOrDefault();
_logger.Info("Loaded {Count} built-in inspection modules from {ToolsPath}", BuiltInInspectionModules.Count, toolsPath);
}
catch (Exception ex)
{
SelectedBuiltInInspectionModule = null;
_logger.Error(ex, "Failed to load built-in inspection modules.");
}
}
private static string GetBuiltInModuleDisplayName(string toolsPath, string filePath)
{
var relativePath = Path.GetRelativePath(toolsPath, filePath);
var withoutExtension = Path.ChangeExtension(relativePath, null) ?? relativePath;
return withoutExtension.Replace(Path.DirectorySeparatorChar, '/');
}
private void ExecuteAxisReset()
{
var result = MessageBox.Show("确认执行轴复位操作?", "轴复位",
var result = MessageBox.Show("Confirm axis reset?", "Axis Reset",
MessageBoxButton.OKCancel, MessageBoxImage.Question);
if (result != MessageBoxResult.OK)
return;
@@ -491,7 +579,7 @@ namespace XplorePlane.ViewModels
}
catch (Exception ex)
{
_logger.Error(ex, "轴复位异常");
_logger.Error(ex, "Axis reset failed.");
MessageBox.Show($"轴复位异常:{ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
}
@@ -502,12 +590,12 @@ namespace XplorePlane.ViewModels
try
{
ShowOrActivate(_detectorConfigWindow, w => _detectorConfigWindow = w,
() => new XP.Hardware.Detector.Views.DetectorConfigWindow(), "探测器配置");
() => new XP.Hardware.Detector.Views.DetectorConfigWindow(), "Detector Config");
}
catch (Exception ex)
{
_logger.Error(ex, "打开探测器配置窗口失败");
MessageBox.Show($"打开探测器配置窗口失败:\n{ex.InnerException?.Message ?? ex.Message}",
_logger.Error(ex, "Failed to open detector config window.");
MessageBox.Show($"Failed to open detector config window:\n{ex.InnerException?.Message ?? ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -527,7 +615,7 @@ namespace XplorePlane.ViewModels
private void ExecuteOpenRaySourceConfig()
{
ShowOrActivate(_raySourceConfigWindow, w => _raySourceConfigWindow = w,
() => new XP.Hardware.RaySource.Views.RaySourceConfigWindow(), "射线源配置");
() => new XP.Hardware.RaySource.Views.RaySourceConfigWindow(), "Ray Source Config");
}
private void ExecuteLoadImage()
@@ -550,20 +638,20 @@ namespace XplorePlane.ViewModels
bitmap.EndInit();
bitmap.Freeze();
_logger.Info("[图像链路] ExecuteLoadImage:加载图像 {Path},准备推送到 MainViewportService ManualImageLoadedEvent", dialog.FileName);
_logger.Info("[Image] ExecuteLoadImage loaded image {Path} and will push it to MainViewportService and ManualImageLoadedEvent.", dialog.FileName);
_mainViewportService.SetManualImage(bitmap, dialog.FileName);
// 同时发布事件,让 PipelineEditorViewModel 收到图像并触发流水线执行
// Publish the image to the pipeline editor at the same time.
_eventAggregator.GetEvent<ManualImageLoadedEvent>()
.Publish(new ManualImageLoadedPayload(bitmap, dialog.FileName));
_logger.Info("[图像链路] ManualImageLoadedEvent 已发布");
_logger.Info("[Image] ManualImageLoadedEvent published.");
RaisePropertyChanged(nameof(IsUsingLiveDetectorSource));
}
catch (Exception ex)
{
_logger.Error(ex, "加载图像失败:{Path}", dialog.FileName);
MessageBox.Show($"加载图像失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
_logger.Error(ex, "Failed to load image: {Path}", dialog.FileName);
MessageBox.Show($"Failed to load image: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -586,18 +674,18 @@ namespace XplorePlane.ViewModels
var result = raySourceService.WarmUp();
if (!result.Success)
{
MessageBox.Show($"暖机失败:{result.ErrorMessage}", "错误",
MessageBox.Show($"Warm-up failed: {result.ErrorMessage}", "Error",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
_logger.Info("暖机命令已发送");
_logger.Info("Warm-up command sent.");
}
}
catch (Exception ex)
{
_logger.Error(ex, "暖机异常");
MessageBox.Show($"暖机异常:{ex.Message}", "错误",
_logger.Error(ex, "Warm-up failed.");
MessageBox.Show($"Warm-up error: {ex.Message}", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -620,28 +708,28 @@ namespace XplorePlane.ViewModels
private void ExecutePointDistanceMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("点点距测量功能已触发");
_logger.Info("Point distance measurement triggered.");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointDistance);
}
private void ExecutePointLineDistanceMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("点线距测量功能已触发");
_logger.Info("Point-line distance measurement triggered.");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointLineDistance);
}
private void ExecuteAngleMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("角度测量功能已触发");
_logger.Info("Angle measurement triggered.");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.Angle);
}
private void ExecuteThroughHoleFillRateMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("通孔填锡率测量功能已触发");
_logger.Info("Through-hole fill-rate measurement triggered.");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.ThroughHoleFillRate);
}
@@ -650,7 +738,7 @@ namespace XplorePlane.ViewModels
private void ExecuteBgaVoidMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("BGA空隙测量功能已触发");
_logger.Info("BGA void measurement triggered.");
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.BgaVoid);
if (_bgaMeasurePanel != null && _bgaMeasurePanel.IsVisible)
@@ -675,12 +763,12 @@ namespace XplorePlane.ViewModels
private void ExecuteBubbleMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("气泡测量功能已触发");
_logger.Info("Bubble measurement triggered.");
// 进入气泡测量模式
// Enter bubble measurement mode.
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.BubbleMeasure);
// 弹出工具面板
// Open the tool panel.
if (_bubbleMeasurePanel != null && _bubbleMeasurePanel.IsVisible)
{
_bubbleMeasurePanel.Activate();
@@ -693,7 +781,7 @@ namespace XplorePlane.ViewModels
};
_bubbleMeasurePanel.Closed += (s, e) =>
{
// 关闭面板时退出气泡测量模式
// Exit bubble measurement mode when the panel closes.
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.None);
};
_bubbleMeasurePanel.Show();
@@ -714,7 +802,7 @@ namespace XplorePlane.ViewModels
}
catch (Exception ex)
{
_logger.Error(ex, "打开语言设置失败");
_logger.Error(ex, "Failed to open language settings.");
}
}
@@ -726,38 +814,38 @@ namespace XplorePlane.ViewModels
private void OnNavigateHome()
{
_logger.Info("导航到主页");
LicenseInfo = "页";
_logger.Info("Navigated to home.");
LicenseInfo = "页";
}
private void OnNavigateInspect()
{
_logger.Info("导航到检测页面");
LicenseInfo = "检测页面";
_logger.Info("Navigated to inspection page.");
LicenseInfo = "Inspection";
}
private void OnOpenFile()
{
_logger.Info("打开文件");
_logger.Info("Open file.");
LicenseInfo = "打开文件";
}
private void OnExport()
{
_logger.Info("导出数据");
_logger.Info("Export data.");
LicenseInfo = "导出数据";
}
private void OnClear()
{
_logger.Info("清除数据");
_logger.Info("Clear data.");
LicenseInfo = "清除数据";
}
private void OnEditProperties()
{
_logger.Info("编辑属性");
LicenseInfo = "编辑属性";
_logger.Info("Edit properties.");
LicenseInfo = "Edit properties";
}
private void OnMainViewportStateChanged(object sender, EventArgs e)
@@ -773,12 +861,14 @@ namespace XplorePlane.ViewModels
{
if (payload?.Image == null)
{
_logger.Warn("[图像链路] OnPipelinePreviewUpdatedpayload 或 Image null,跳过");
_logger.Warn("[Image] OnPipelinePreviewUpdated skipped because payload or image is null.");
return;
}
_logger.Info("[图像链路] OnPipelinePreviewUpdated:收到流水线结果图像,推送到 MainViewportService");
_logger.Info("[Image] OnPipelinePreviewUpdated received a pipeline preview image and pushed it to MainViewportService.");
_mainViewportService.SetManualImage(payload.Image, string.Empty);
}
public sealed record BuiltInInspectionModuleItem(string DisplayName, string FilePath);
#endregion
}
}
+28 -7
View File
@@ -279,6 +279,33 @@
Size="Large"
SmallImage="/Assets/Icons/cnc.png"
Text="CNC 编辑" />
<telerik:RadRibbonButton
telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案"
telerik:ScreenTip.Title="矩阵编排"
Command="{Binding OpenMatrixEditorCommand}"
Size="Large"
SmallImage="/Assets/Icons/matrix.png"
Text="矩阵编排" />
<StackPanel Width="170">
<TextBlock
Margin="0,0,0,4"
HorizontalAlignment="Center"
Text="内置检测模块" />
<telerik:RadRibbonComboBox
Width="160"
ItemsSource="{Binding BuiltInInspectionModules}"
DisplayMemberPath="DisplayName"
SelectedItem="{Binding SelectedBuiltInInspectionModule}"
IsEditable="False" />
<telerik:RadRibbonButton
Margin="0,4,0,0"
telerik:ScreenTip.Description="从 Tools 目录扫描到的 .xpm 中选择一个配方,并插入到当前 CNC 程序中"
telerik:ScreenTip.Title="插入内置检测模块"
Command="{Binding InsertBuiltInInspectionModuleCommand}"
Size="Medium"
SmallImage="/Assets/Icons/Module.png"
Text="插入模块" />
</StackPanel>
<!--
<StackPanel>
<telerik:RadRibbonButton
@@ -331,13 +358,7 @@
</StackPanel>
-->
<telerik:RadRibbonButton
telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案"
telerik:ScreenTip.Title="矩阵编排"
Command="{Binding OpenMatrixEditorCommand}"
Size="Large"
SmallImage="/Assets/Icons/matrix.png"
Text="矩阵编排" />
</telerik:RadRibbonGroup>
<telerik:RadRibbonGroup Header="高级模块" IsEnabled="{Binding Path=CellsGroup.IsEnabled}">