From 80c86e2ed7a49773d16e811fe238f4a87e52f037 Mon Sep 17 00:00:00 2001 From: "zhengxuan.zhang" Date: Tue, 19 May 2026 11:38:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=94=E9=9A=99=E6=A3=80=E6=B5=8B=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=BC=95=E5=85=A5=E5=88=B0CNC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageProcessing/BgaDetectionViewModel.cs | 4 + .../ImageProcessing/VoidDetectionViewModel.cs | 119 ++++++++++++++++++ .../ImageProcessing/BgaDetectionPanel.xaml | 6 +- .../ImageProcessing/VoidDetectionPanel.xaml | 6 +- .../VoidDetectionPanel.xaml.cs | 12 ++ 5 files changed, 143 insertions(+), 4 deletions(-) diff --git a/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs index 55885f3..f30ea24 100644 --- a/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs +++ b/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs @@ -481,6 +481,10 @@ namespace XplorePlane.ViewModels.ImageProcessing pipeline.UpdatedAt = DateTime.UtcNow; targetModuleNode.Pipeline = pipeline; + // 强制刷新右侧检测模块面板:将选中节点切换到目标检测模块,触发重新加载 + _cncEditorViewModel.SelectedNode = null; + _cncEditorViewModel.SelectedNode = targetModuleNode; + MessageBox.Show( $"已将 BGA 检测参数插入到检测模块「{targetModuleNode.Name}」。", "插入成功", MessageBoxButton.OK, MessageBoxImage.Information); diff --git a/XplorePlane/ViewModels/ImageProcessing/VoidDetectionViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/VoidDetectionViewModel.cs index 96e7646..bc1296a 100644 --- a/XplorePlane/ViewModels/ImageProcessing/VoidDetectionViewModel.cs +++ b/XplorePlane/ViewModels/ImageProcessing/VoidDetectionViewModel.cs @@ -14,24 +14,35 @@ using Prism.Mvvm; using XP.ImageProcessing.Processors; using XP.ImageProcessing.RoiControl.Controls; using XP.ImageProcessing.RoiControl.Models; +using XplorePlane.Models; using XplorePlane.Services.MainViewport; +using XplorePlane.ViewModels.Cnc; namespace XplorePlane.ViewModels.ImageProcessing { public class VoidDetectionViewModel : BindableBase { private readonly IMainViewportService _viewportService; + private CncEditorViewModel _cncEditorViewModel; private BitmapSource _originalImage; private System.Threading.CancellationTokenSource _debounceCts; private const int DebounceMs = 300; + private const string VoidMeasurementOperatorKey = "VoidMeasurement"; public VoidDetectionViewModel(IMainViewportService viewportService) { _viewportService = viewportService; ExecuteCommand = new DelegateCommand(Execute); + InsertToCncCommand = new DelegateCommand(ExecuteInsertToCnc); PropertyChanged += OnAnyPropertyChanged; } + /// 设置 CNC 编辑器 ViewModel 引用,用于插入参数到激活的 CNC 位置节点 + public void SetCncEditorViewModel(CncEditorViewModel cncEditorViewModel) + { + _cncEditorViewModel = cncEditorViewModel; + } + private void OnAnyPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == nameof(ResultText) || e.PropertyName == nameof(ResultImage) || e.PropertyName == nameof(RoiEnabled)) @@ -166,6 +177,7 @@ namespace XplorePlane.ViewModels.ImageProcessing public ObservableCollection Results { get; } = new(); public DelegateCommand ExecuteCommand { get; } + public DelegateCommand InsertToCncCommand { get; } private void Execute() { @@ -235,6 +247,113 @@ namespace XplorePlane.ViewModels.ImageProcessing } } + /// + /// 将当前 ROI 和算子参数插入到激活的 CNC 位置节点的检测模块中空隙检测模块的参数 + /// + private void ExecuteInsertToCnc() + { + if (_cncEditorViewModel == null) + { + MessageBox.Show("CNC 编辑器未就绪,无法插入参数。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + var selectedNode = _cncEditorViewModel.SelectedNode; + CncNodeViewModel targetModuleNode = null; + + if (selectedNode == null) + { + MessageBox.Show("请先在 CNC 编辑器中选择一个位置节点或检测模块。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + if (selectedNode.IsInspectionModule) + { + targetModuleNode = selectedNode; + } + else if (selectedNode.IsSavePosition) + { + targetModuleNode = selectedNode.Children.FirstOrDefault(c => c.IsInspectionModule); + } + else + { + var allNodes = _cncEditorViewModel.Nodes; + CncNodeViewModel ownerPosition = null; + foreach (var node in allNodes) + { + if (node.IsSavePosition) + ownerPosition = node; + if (node.Id == selectedNode.Id) + break; + } + if (ownerPosition != null) + targetModuleNode = ownerPosition.Children.FirstOrDefault(c => c.IsInspectionModule); + } + + if (targetModuleNode == null) + { + MessageBox.Show("未找到激活的检测模块节点。\n请在 CNC 编辑器中选择一个包含检测模块的位置节点。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + // 获取或创建 Pipeline + var pipeline = targetModuleNode.Pipeline ?? new PipelineModel { Name = targetModuleNode.Name }; + + // 查找已有的 VoidMeasurement 算子节点 + var voidNode = pipeline.Nodes.FirstOrDefault(n => + string.Equals(n.OperatorKey, VoidMeasurementOperatorKey, StringComparison.OrdinalIgnoreCase)); + + if (voidNode == null) + { + voidNode = new PipelineNodeModel + { + Id = Guid.NewGuid(), + OperatorKey = VoidMeasurementOperatorKey, + Order = pipeline.Nodes.Count, + IsEnabled = true, + Parameters = new Dictionary() + }; + pipeline.Nodes.Add(voidNode); + } + + // 写入当前参数 + var parameters = voidNode.Parameters; + parameters["MinThreshold"] = MinThreshold; + parameters["MaxThreshold"] = MaxThreshold; + parameters["MinVoidArea"] = MinVoidArea; + parameters["MergeRadius"] = MergeRadius; + parameters["BlurSize"] = BlurSize; + parameters["VoidLimit"] = VoidLimit; + + // 写入 ROI 参数 + if (RoiEnabled && _roiShape != null && _roiShape.Points.Count >= 3) + { + int count = Math.Min(_roiShape.Points.Count, 32); + parameters["PolyCount"] = count; + for (int i = 0; i < count; i++) + { + parameters[$"PolyX{i}"] = (int)_roiShape.Points[i].X; + parameters[$"PolyY{i}"] = (int)_roiShape.Points[i].Y; + } + } + else + { + parameters["PolyCount"] = 0; + } + + // 更新 Pipeline 到节点 + pipeline.UpdatedAt = DateTime.UtcNow; + targetModuleNode.Pipeline = pipeline; + + // 强制刷新右侧检测模块面板 + _cncEditorViewModel.SelectedNode = null; + _cncEditorViewModel.SelectedNode = targetModuleNode; + + MessageBox.Show( + $"已将空隙检测参数插入到检测模块「{targetModuleNode.Name}」。", + "插入成功", MessageBoxButton.OK, MessageBoxImage.Information); + } + private void ShowResultOnOverlay(BitmapSource resultBmp) { if (_canvas == null) return; diff --git a/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml b/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml index 884d4df..e9991b7 100644 --- a/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml +++ b/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml @@ -76,12 +76,12 @@ + - diff --git a/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml b/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml index 71a9587..2a4fa06 100644 --- a/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml +++ b/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml @@ -72,9 +72,13 @@ - + diff --git a/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml.cs b/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml.cs index 2f9e2b5..1df5a20 100644 --- a/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml.cs +++ b/XplorePlane/Views/ImageProcessing/VoidDetectionPanel.xaml.cs @@ -2,6 +2,7 @@ using System.Windows; using Prism.Ioc; using XP.ImageProcessing.RoiControl.Controls; using XplorePlane.Services.MainViewport; +using XplorePlane.ViewModels.Cnc; using XplorePlane.ViewModels.ImageProcessing; namespace XplorePlane.Views.ImageProcessing @@ -23,6 +24,17 @@ namespace XplorePlane.Views.ImageProcessing if (DataContext is VoidDetectionViewModel vm) vm.SetCanvas(canvas); } + + // 从 MainViewModel 获取 CncEditorViewModel 引用 + if (DataContext is VoidDetectionViewModel voidVm && Owner?.DataContext is ViewModels.MainViewModel mainVm) + { + var cncEditorField = mainVm.GetType().GetField("_cncEditorViewModel", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (cncEditorField?.GetValue(mainVm) is CncEditorViewModel cncEditor) + { + voidVm.SetCncEditorViewModel(cncEditor); + } + } }; Closed += (s, e) =>