diff --git a/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs b/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs index 47791c7..3282f6b 100644 --- a/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs +++ b/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs @@ -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 mockExecSvc = null, - CncProgram initialProgram = null) + CncProgram initialProgram = null, + Mock mockPipelinePersistenceService = null) { var mockCncProgramSvc = new Mock(); var mockAppState = new Mock(); var mockLogger = new Mock(); var mockDataPathService = new Mock(); + mockPipelinePersistenceService ??= new Mock(); mockLogger.Setup(l => l.ForModule()).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 { - 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())) + .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(), It.IsAny(), It.IsAny())) + .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(), It.IsAny(), It.IsAny())) + .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(); + mockPipelinePersistenceService + .Setup(s => s.LoadAsync(pipelineFile)) + .ReturnsAsync(expectedPipeline); + + var initialProgram = new CncProgram( + Guid.NewGuid(), + "TestProgram", + DateTime.UtcNow, + DateTime.UtcNow, + new List + { + new SavePositionNode(Guid.NewGuid(), 0, "保存位置_0", MotionState.Default) + }.AsReadOnly()); + + var vm = CreateVm( + initialProgram: initialProgram, + mockPipelinePersistenceService: mockPipelinePersistenceService); + + await vm.InsertInspectionModuleFromPipelineFileAsync(pipelineFile); + + var insertedNode = Assert.IsType(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); + } + } } } diff --git a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs index 5b0e31f..9b04275 100644 --- a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs +++ b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs @@ -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 _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(); _cncExecutionService = cncExecutionService ?? throw new ArgumentNullException(nameof(cncExecutionService)); _dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService)); + _pipelinePersistenceService = pipelinePersistenceService ?? throw new ArgumentNullException(nameof(pipelinePersistenceService)); _nodes = new ObservableCollection(); _treeNodes = new ObservableCollection(); @@ -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; diff --git a/XplorePlane/ViewModels/Main/MainViewModel.cs b/XplorePlane/ViewModels/Main/MainViewModel.cs index eeeac48..fbb4497 100644 --- a/XplorePlane/ViewModels/Main/MainViewModel.cs +++ b/XplorePlane/ViewModels/Main/MainViewModel.cs @@ -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 BuiltInInspectionModules { get; } = new(); + + public BuiltInInspectionModuleItem SelectedBuiltInInspectionModule + { + get => _selectedBuiltInInspectionModule; + set + { + if (SetProperty(ref _selectedBuiltInInspectionModule, value)) + { + InsertBuiltInInspectionModuleCommand?.RaiseCanExecuteChanged(); + } + } + } + /// 右侧图像区域内容 | Right-side image panel content 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().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() .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().Publish(MeasurementToolMode.PointDistance); } private void ExecutePointLineDistanceMeasure() { if (!CheckImageLoaded()) return; - _logger.Info("点线距测量功能已触发"); + _logger.Info("Point-line distance measurement triggered."); _eventAggregator.GetEvent().Publish(MeasurementToolMode.PointLineDistance); } private void ExecuteAngleMeasure() { if (!CheckImageLoaded()) return; - _logger.Info("角度测量功能已触发"); + _logger.Info("Angle measurement triggered."); _eventAggregator.GetEvent().Publish(MeasurementToolMode.Angle); } private void ExecuteThroughHoleFillRateMeasure() { if (!CheckImageLoaded()) return; - _logger.Info("通孔填锡率测量功能已触发"); + _logger.Info("Through-hole fill-rate measurement triggered."); _eventAggregator.GetEvent().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().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().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().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("[图像链路] OnPipelinePreviewUpdated:payload 或 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 } } diff --git a/XplorePlane/Views/Main/MainWindow.xaml b/XplorePlane/Views/Main/MainWindow.xaml index 15ba9c8..4de574a 100644 --- a/XplorePlane/Views/Main/MainWindow.xaml +++ b/XplorePlane/Views/Main/MainWindow.xaml @@ -279,6 +279,33 @@ Size="Large" SmallImage="/Assets/Icons/cnc.png" Text="CNC 编辑" /> + + + + + + - +