diff --git a/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs b/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs
index 1130f59..2222c99 100644
--- a/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs
+++ b/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs
@@ -394,6 +394,23 @@ namespace XP.ImageProcessing.RoiControl.Controls
public void SetBubbleBrushSize(int val) => _bubbleBrushSize = val;
public Rect? BubbleRoi => _bubbleRoi;
+ /// 设置 BGA 测量的气泡/焊球绘制模式
+ public void SetBgaDrawBall(bool drawBall)
+ {
+ _bgaDrawBall = drawBall;
+ RaiseMeasureStatusChanged(drawBall ? "BGA - 画焊球模式" : "BGA - 画气泡模式");
+ }
+
+ /// 设置所有 BGA 组的 VoidLimit 并刷新标签
+ public void SetBgaVoidLimit(double limit)
+ {
+ foreach (var g in _bgaGroups)
+ {
+ g.VoidLimit = limit;
+ g.UpdateLabel();
+ }
+ }
+
// 拖拽状态
private Ellipse _mDraggingDot;
private object _mDraggingOwner;
@@ -722,9 +739,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
{
_bgaCurrent = new Models.BgaVoidGroup();
var currentGroup = _bgaCurrent; // 局部变量供闭包捕获
- _bgaCurrent.Label = new TextBlock { FontSize = 13, FontWeight = FontWeights.Bold, Cursor = System.Windows.Input.Cursors.Hand, Visibility = Visibility.Collapsed };
- _bgaCurrent.Label.SetValue(ContextMenuService.IsEnabledProperty, false);
- _bgaCurrent.Label.PreviewMouseRightButtonUp += (s, ev) => { ShowBgaLimitEditor(currentGroup); ev.Handled = true; };
+ _bgaCurrent.Label = new TextBlock { FontSize = 13, FontWeight = FontWeights.Bold, IsHitTestVisible = false, Visibility = Visibility.Collapsed };
_measureOverlay.Children.Add(_bgaCurrent.Label);
_bgaDrawBall = false;
RaiseMeasureStatusChanged("BGA空隙 - 点击画气泡圆心(右键切换为画焊球)");
@@ -924,10 +939,12 @@ namespace XP.ImageProcessing.RoiControl.Controls
if (g.E4BH == dot) { _mDraggingOwner = g; _mDraggingRole = "E4B"; break; }
}
}
- // 查找 BGA 组
+ // 查找 BGA 组(已完成的 + 正在编辑的)
if (_mDraggingOwner == null)
{
- foreach (var g in _bgaGroups)
+ var allBga = new System.Collections.Generic.List(_bgaGroups);
+ if (_bgaCurrent != null) allBga.Add(_bgaCurrent);
+ foreach (var g in allBga)
{
if (g.Ball?.CenterDot == dot) { _mDraggingOwner = g; _mDraggingRole = "BallCenter"; break; }
if (g.Ball?.EdgeDot == dot) { _mDraggingOwner = g; _mDraggingRole = "BallEdge"; break; }
diff --git a/XplorePlane/ViewModels/Main/MainViewModel.cs b/XplorePlane/ViewModels/Main/MainViewModel.cs
index d6e784e..44c4138 100644
--- a/XplorePlane/ViewModels/Main/MainViewModel.cs
+++ b/XplorePlane/ViewModels/Main/MainViewModel.cs
@@ -492,11 +492,29 @@ namespace XplorePlane.ViewModels
_eventAggregator.GetEvent().Publish(MeasurementToolMode.ThroughHoleFillRate);
}
+ private Window _bgaMeasurePanel;
+
private void ExecuteBgaVoidMeasure()
{
if (!CheckImageLoaded()) return;
_logger.Info("BGA空隙测量功能已触发");
_eventAggregator.GetEvent().Publish(MeasurementToolMode.BgaVoid);
+
+ if (_bgaMeasurePanel != null && _bgaMeasurePanel.IsVisible)
+ {
+ _bgaMeasurePanel.Activate();
+ return;
+ }
+
+ _bgaMeasurePanel = new Views.ImageProcessing.BgaMeasurePanel
+ {
+ Owner = System.Windows.Application.Current.MainWindow
+ };
+ _bgaMeasurePanel.Closed += (s, e) =>
+ {
+ _eventAggregator.GetEvent().Publish(MeasurementToolMode.None);
+ };
+ _bgaMeasurePanel.Show();
}
private Window _bubbleMeasurePanel;
diff --git a/XplorePlane/Views/ImageProcessing/BgaMeasurePanel.xaml b/XplorePlane/Views/ImageProcessing/BgaMeasurePanel.xaml
new file mode 100644
index 0000000..6467a2e
--- /dev/null
+++ b/XplorePlane/Views/ImageProcessing/BgaMeasurePanel.xaml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/XplorePlane/Views/ImageProcessing/BgaMeasurePanel.xaml.cs b/XplorePlane/Views/ImageProcessing/BgaMeasurePanel.xaml.cs
new file mode 100644
index 0000000..ee34e2f
--- /dev/null
+++ b/XplorePlane/Views/ImageProcessing/BgaMeasurePanel.xaml.cs
@@ -0,0 +1,74 @@
+using System.Windows;
+using XP.ImageProcessing.RoiControl.Controls;
+
+namespace XplorePlane.Views.ImageProcessing
+{
+ public partial class BgaMeasurePanel : Window
+ {
+ private PolygonRoiCanvas _canvas;
+
+ public BgaMeasurePanel()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ var mainWin = Owner as MainWindow;
+ if (mainWin != null)
+ _canvas = FindChild(mainWin);
+ }
+ catch { }
+
+ // 模式切换:通知 canvas 切换气泡/焊球
+ RbVoid.Checked += (s, ev) =>
+ {
+ if (_canvas != null) _canvas.SetBgaDrawBall(false);
+ };
+ RbBall.Checked += (s, ev) =>
+ {
+ if (_canvas != null) _canvas.SetBgaDrawBall(true);
+ };
+
+ // VoidLimit 同步
+ SliderVoidLimit.ValueChanged += (s, ev) =>
+ {
+ TbVoidLimit.Text = SliderVoidLimit.Value.ToString("F1");
+ _canvas?.SetBgaVoidLimit(SliderVoidLimit.Value);
+ };
+
+ // 监听测量完成事件更新结果
+ if (_canvas != null)
+ {
+ _canvas.MeasureCompleted += (s, ev) =>
+ {
+ if (ev is MeasureCompletedEventArgs args && args.MeasureType == "BgaVoid")
+ {
+ TbResult.Text = $"空隙率: {args.Distance:F1}%";
+ }
+ };
+ }
+ }
+
+ private void Finish_Click(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ private static T FindChild(DependencyObject parent) where T : DependencyObject
+ {
+ int count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < count; i++)
+ {
+ var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i);
+ if (child is T t) return t;
+ var result = FindChild(child);
+ if (result != null) return result;
+ }
+ return null;
+ }
+ }
+}