下拉检测模块列表
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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("[图像链路] 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}">
|
||||
|
||||
Reference in New Issue
Block a user