BGA手动空隙测量:画气泡圆+焊球圆交互、拖拽调整、空隙率计算、VoidLimit编辑、右键删除
This commit is contained in:
@@ -346,6 +346,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
private readonly System.Collections.Generic.List<Models.PointToLineGroup> _ptlGroups = new();
|
||||
private readonly System.Collections.Generic.List<Models.AngleGroup> _angleGroups = new();
|
||||
private readonly System.Collections.Generic.List<Models.FillRateGroup> _frGroups = new();
|
||||
private readonly System.Collections.Generic.List<Models.BgaVoidGroup> _bgaGroups = new();
|
||||
|
||||
// 点点距临时状态
|
||||
private Ellipse _pendingDot;
|
||||
@@ -363,6 +364,12 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
private Line _angleTempLineA;
|
||||
private Point? _angleTempV, _angleTempA;
|
||||
|
||||
// BGA 空隙测量状态
|
||||
private Models.BgaVoidGroup _bgaCurrent; // 当前正在编辑的 BGA 组
|
||||
private bool _bgaDrawBall; // true=画焊球, false=画气泡
|
||||
private Point? _bgaPendingCenter; // 等待第二次点击定半径
|
||||
private Ellipse _bgaPendingDot;
|
||||
|
||||
// 拖拽状态
|
||||
private Ellipse _mDraggingDot;
|
||||
private object _mDraggingOwner;
|
||||
@@ -396,6 +403,8 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
if (_angleTempADot != null) { _measureOverlay.Children.Remove(_angleTempADot); _angleTempADot = null; }
|
||||
if (_angleTempLineA != null) { _measureOverlay.Children.Remove(_angleTempLineA); _angleTempLineA = null; }
|
||||
_angleTempV = _angleTempA = null; _angleClickCount = 0;
|
||||
if (_bgaPendingDot != null) { _measureOverlay.Children.Remove(_bgaPendingDot); _bgaPendingDot = null; }
|
||||
_bgaPendingCenter = null;
|
||||
}
|
||||
|
||||
private void EnsureMeasureOverlay()
|
||||
@@ -410,7 +419,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
private void RemoveMeasureOverlay()
|
||||
{
|
||||
if (_measureOverlay != null) { mainCanvas.Children.Remove(_measureOverlay); _measureOverlay = null; }
|
||||
_ppGroups.Clear(); _ptlGroups.Clear(); _angleGroups.Clear(); _frGroups.Clear();
|
||||
_ppGroups.Clear(); _ptlGroups.Clear(); _angleGroups.Clear(); _frGroups.Clear(); _bgaGroups.Clear();
|
||||
_pendingDot = null; _pendingPoint = null;
|
||||
_ptlTempDot1 = _ptlTempDot2 = null; _ptlTempLine = null;
|
||||
_ptlTempL1 = _ptlTempL2 = null; _ptlClickCount = 0;
|
||||
@@ -420,7 +429,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
}
|
||||
|
||||
public void ClearMeasurements() => RemoveMeasureOverlay();
|
||||
public int MeasureCount => _ppGroups.Count + _ptlGroups.Count + _angleGroups.Count + _frGroups.Count;
|
||||
public int MeasureCount => _ppGroups.Count + _ptlGroups.Count + _angleGroups.Count + _frGroups.Count + _bgaGroups.Count;
|
||||
|
||||
// ── 点击分发 ──
|
||||
|
||||
@@ -437,6 +446,8 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
HandleAngleClick(pos);
|
||||
else if (CurrentMeasureMode == Models.MeasureMode.FillRate)
|
||||
HandleFillRateClick(pos);
|
||||
else if (CurrentMeasureMode == Models.MeasureMode.BgaVoid)
|
||||
HandleBgaVoidClick(pos);
|
||||
}
|
||||
|
||||
// ── 点点距 ──
|
||||
@@ -634,7 +645,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
|
||||
g.Label = new TextBlock { FontSize = 13, FontWeight = FontWeights.Bold, Cursor = Cursors.Hand };
|
||||
g.Label.SetValue(ContextMenuService.IsEnabledProperty, false);
|
||||
g.Label.MouseRightButtonUp += (s, ev) => { ShowTHTLimitEditor(g); ev.Handled = true; };
|
||||
g.Label.PreviewMouseRightButtonUp += (s, ev) => { ShowTHTLimitEditor(g); ev.Handled = true; };
|
||||
|
||||
g.DotE1 = CreateMDot(Brushes.DodgerBlue);
|
||||
g.DotE2 = CreateMDot(Brushes.Cyan);
|
||||
@@ -674,6 +685,153 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
return p;
|
||||
}
|
||||
|
||||
// ── BGA 空隙测量 ──
|
||||
|
||||
private void HandleBgaVoidClick(Point pos)
|
||||
{
|
||||
if (_measureOverlay == null) EnsureMeasureOverlay();
|
||||
if (_measureOverlay == null) return;
|
||||
|
||||
// 第一次进入:创建新的 BGA 组
|
||||
if (_bgaCurrent == null)
|
||||
{
|
||||
_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; };
|
||||
_measureOverlay.Children.Add(_bgaCurrent.Label);
|
||||
_bgaDrawBall = false;
|
||||
RaiseMeasureStatusChanged("BGA空隙 - 点击画气泡圆心(右键切换为画焊球)");
|
||||
}
|
||||
|
||||
if (!_bgaPendingCenter.HasValue)
|
||||
{
|
||||
// 第一次点击:圆心
|
||||
_bgaPendingCenter = pos;
|
||||
_bgaPendingDot = CreateMDot(_bgaDrawBall ? Brushes.Lime : Brushes.Orange);
|
||||
_measureOverlay.Children.Add(_bgaPendingDot);
|
||||
SetDotPos(_bgaPendingDot, pos);
|
||||
RaiseMeasureStatusChanged(_bgaDrawBall ? "BGA - 焊球圆心已定,点击边缘定半径" : "BGA - 气泡圆心已定,点击边缘定半径");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 第二次点击:半径
|
||||
double r = Math.Max(5, Models.FillRateGroup.Dist(pos, _bgaPendingCenter.Value));
|
||||
var circle = CreateBgaCircle(_bgaPendingCenter.Value, r, _bgaDrawBall);
|
||||
|
||||
if (_bgaDrawBall)
|
||||
{
|
||||
// 如果已有焊球,移除旧的
|
||||
if (_bgaCurrent.Ball != null)
|
||||
{
|
||||
_measureOverlay.Children.Remove(_bgaCurrent.Ball.Shape);
|
||||
_measureOverlay.Children.Remove(_bgaCurrent.Ball.CenterDot);
|
||||
_measureOverlay.Children.Remove(_bgaCurrent.Ball.EdgeDot);
|
||||
}
|
||||
_bgaCurrent.Ball = circle;
|
||||
// 画完焊球 → 完成本组,计算结果
|
||||
_bgaGroups.Add(_bgaCurrent);
|
||||
RenumberAll();
|
||||
_bgaCurrent.UpdateLabel();
|
||||
_bgaCurrent.Label.Cursor = System.Windows.Input.Cursors.Hand;
|
||||
RaiseMeasureCompleted(_bgaCurrent.Ball.Center, _bgaCurrent.Ball.Center, _bgaCurrent.VoidRate, MeasureCount, "BgaVoid");
|
||||
_bgaCurrent = null;
|
||||
_bgaDrawBall = false;
|
||||
CurrentMeasureMode = Models.MeasureMode.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bgaCurrent.Voids.Add(circle);
|
||||
RaiseMeasureStatusChanged($"BGA - 已画 {_bgaCurrent.Voids.Count} 个气泡,继续画气泡或右键切换画焊球");
|
||||
}
|
||||
|
||||
// 清除临时点
|
||||
if (_bgaPendingDot != null) { _measureOverlay.Children.Remove(_bgaPendingDot); _bgaPendingDot = null; }
|
||||
_bgaPendingCenter = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>右键切换气泡/焊球模式(在 BGA 测量模式下)</summary>
|
||||
private void HandleBgaRightClick()
|
||||
{
|
||||
if (CurrentMeasureMode != Models.MeasureMode.BgaVoid || _bgaCurrent == null) return;
|
||||
_bgaDrawBall = !_bgaDrawBall;
|
||||
RaiseMeasureStatusChanged(_bgaDrawBall ? "BGA - 已切换为画焊球模式" : "BGA - 已切换为画气泡模式");
|
||||
}
|
||||
|
||||
private Models.BgaCircle CreateBgaCircle(Point center, double radius, bool isBall)
|
||||
{
|
||||
var c = new Models.BgaCircle { Center = center, Radius = radius, IsBall = isBall };
|
||||
c.Shape = new Ellipse
|
||||
{
|
||||
Stroke = isBall ? Brushes.Lime : Brushes.Orange,
|
||||
StrokeThickness = isBall ? 2 : 1.5,
|
||||
Fill = Brushes.Transparent,
|
||||
IsHitTestVisible = false
|
||||
};
|
||||
c.CenterDot = CreateMDot(isBall ? Brushes.Lime : Brushes.Orange);
|
||||
c.EdgeDot = CreateMDot(isBall ? Brushes.Lime : Brushes.Orange);
|
||||
c.EdgeDot.Width = 8; c.EdgeDot.Height = 8;
|
||||
c.EdgeDot.Cursor = System.Windows.Input.Cursors.SizeAll;
|
||||
|
||||
_measureOverlay.Children.Add(c.Shape);
|
||||
_measureOverlay.Children.Add(c.CenterDot);
|
||||
_measureOverlay.Children.Add(c.EdgeDot);
|
||||
c.UpdateVisuals();
|
||||
return c;
|
||||
}
|
||||
|
||||
// BGA Limit 编辑(和 FillRate 类似)
|
||||
private void ShowBgaLimitEditor(Models.BgaVoidGroup g)
|
||||
{
|
||||
if (_measureOverlay == null || g == null) return;
|
||||
RemoveTHTEditor();
|
||||
|
||||
double left = Canvas.GetLeft(g.Label);
|
||||
double top = Canvas.GetTop(g.Label) + 22;
|
||||
|
||||
_thtEditLabel = new TextBlock
|
||||
{
|
||||
Text = "VoidLimit(%):",
|
||||
FontSize = 11, Foreground = Brushes.White,
|
||||
Background = new SolidColorBrush(Color.FromArgb(180, 0, 0, 0)),
|
||||
Padding = new Thickness(3, 1, 3, 1)
|
||||
};
|
||||
Canvas.SetLeft(_thtEditLabel, left);
|
||||
Canvas.SetTop(_thtEditLabel, top);
|
||||
_measureOverlay.Children.Add(_thtEditLabel);
|
||||
|
||||
_thtEditBox = new TextBox
|
||||
{
|
||||
Text = g.VoidLimit.ToString("F1"),
|
||||
Width = 60, Height = 22, FontSize = 12,
|
||||
Background = Brushes.White, BorderBrush = Brushes.Orange, BorderThickness = new Thickness(2),
|
||||
Padding = new Thickness(2, 0, 2, 0)
|
||||
};
|
||||
Canvas.SetLeft(_thtEditBox, left + 85);
|
||||
Canvas.SetTop(_thtEditBox, top);
|
||||
_measureOverlay.Children.Add(_thtEditBox);
|
||||
_thtEditBox.Focus();
|
||||
_thtEditBox.SelectAll();
|
||||
|
||||
_thtEditBox.KeyDown += (s, ev) =>
|
||||
{
|
||||
if (ev.Key == System.Windows.Input.Key.Enter)
|
||||
{
|
||||
if (double.TryParse(_thtEditBox.Text, out double val))
|
||||
{
|
||||
g.VoidLimit = System.Math.Clamp(val, 0, 100);
|
||||
g.UpdateLabel();
|
||||
RaiseMeasureCompleted(g.Ball?.Center ?? default, g.Ball?.Center ?? default, g.VoidRate, MeasureCount, "BgaVoid");
|
||||
}
|
||||
RemoveTHTEditor();
|
||||
}
|
||||
else if (ev.Key == System.Windows.Input.Key.Escape) RemoveTHTEditor();
|
||||
};
|
||||
_thtEditBox.LostFocus += (s, ev) => RemoveTHTEditor();
|
||||
}
|
||||
|
||||
// ── 共用:圆点创建、定位、拖拽、删除 ──
|
||||
|
||||
private Ellipse CreateMDot(Brush fill)
|
||||
@@ -741,6 +899,22 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
if (g.E4BH == dot) { _mDraggingOwner = g; _mDraggingRole = "E4B"; break; }
|
||||
}
|
||||
}
|
||||
// 查找 BGA 组
|
||||
if (_mDraggingOwner == null)
|
||||
{
|
||||
foreach (var g in _bgaGroups)
|
||||
{
|
||||
if (g.Ball?.CenterDot == dot) { _mDraggingOwner = g; _mDraggingRole = "BallCenter"; break; }
|
||||
if (g.Ball?.EdgeDot == dot) { _mDraggingOwner = g; _mDraggingRole = "BallEdge"; break; }
|
||||
bool found = false;
|
||||
for (int vi = 0; vi < g.Voids.Count; vi++)
|
||||
{
|
||||
if (g.Voids[vi].CenterDot == dot) { _mDraggingOwner = g; _mDraggingRole = $"V{vi}Center"; found = true; break; }
|
||||
if (g.Voids[vi].EdgeDot == dot) { _mDraggingOwner = g; _mDraggingRole = $"V{vi}Edge"; found = true; break; }
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
}
|
||||
if (_mDraggingOwner != null) { _mDraggingDot = dot; dot.CaptureMouse(); e.Handled = true; }
|
||||
}
|
||||
|
||||
@@ -791,6 +965,35 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
frg.UpdateVisuals();
|
||||
RaiseMeasureCompleted(frg.E3, frg.E4, frg.FillRate, MeasureCount, "FillRate");
|
||||
}
|
||||
else if (_mDraggingOwner is Models.BgaVoidGroup bgag)
|
||||
{
|
||||
if (_mDraggingRole == "BallCenter" && bgag.Ball != null)
|
||||
{
|
||||
bgag.Ball.Center = pos; bgag.Ball.UpdateVisuals();
|
||||
}
|
||||
else if (_mDraggingRole == "BallEdge" && bgag.Ball != null)
|
||||
{
|
||||
bgag.Ball.Radius = Math.Max(5, Models.FillRateGroup.Dist(pos, bgag.Ball.Center));
|
||||
bgag.Ball.UpdateVisuals();
|
||||
}
|
||||
else if (_mDraggingRole.StartsWith("V") && _mDraggingRole.Length > 1)
|
||||
{
|
||||
// 解析 V{index}Center 或 V{index}Edge
|
||||
string rest = _mDraggingRole.Substring(1);
|
||||
bool isEdge = rest.EndsWith("Edge");
|
||||
string idxStr = isEdge ? rest.Replace("Edge", "") : rest.Replace("Center", "");
|
||||
if (int.TryParse(idxStr, out int vi) && vi >= 0 && vi < bgag.Voids.Count)
|
||||
{
|
||||
if (isEdge)
|
||||
bgag.Voids[vi].Radius = Math.Max(5, Models.FillRateGroup.Dist(pos, bgag.Voids[vi].Center));
|
||||
else
|
||||
bgag.Voids[vi].Center = pos;
|
||||
bgag.Voids[vi].UpdateVisuals();
|
||||
}
|
||||
}
|
||||
bgag.UpdateLabel();
|
||||
RaiseMeasureCompleted(bgag.Ball?.Center ?? default, bgag.Ball?.Center ?? default, bgag.VoidRate, MeasureCount, "BgaVoid");
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -858,6 +1061,27 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
e.Handled = true; return;
|
||||
}
|
||||
}
|
||||
// BGA 删除
|
||||
foreach (var g in _bgaGroups)
|
||||
{
|
||||
bool match = g.Ball?.CenterDot == dot || g.Ball?.EdgeDot == dot;
|
||||
if (!match)
|
||||
{
|
||||
foreach (var v in g.Voids)
|
||||
{
|
||||
if (v.CenterDot == dot || v.EdgeDot == dot) { match = true; break; }
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
foreach (var el in g.AllElements)
|
||||
_measureOverlay.Children.Remove(el);
|
||||
_bgaGroups.Remove(g);
|
||||
RenumberAll();
|
||||
RaiseMeasureStatusChanged($"已删除测量 | 剩余 {MeasureCount} 条");
|
||||
e.Handled = true; return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 重编号 ──
|
||||
@@ -885,6 +1109,11 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
_frGroups[i].Index = i + 1;
|
||||
_frGroups[i].UpdateVisuals();
|
||||
}
|
||||
for (int i = 0; i < _bgaGroups.Count; i++)
|
||||
{
|
||||
_bgaGroups[i].Index = i + 1;
|
||||
_bgaGroups[i].UpdateLabel();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 填锡率阈值编辑 ──
|
||||
@@ -1164,11 +1393,27 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
|
||||
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// BGA 模式下右键切换气泡/焊球
|
||||
if (CurrentMeasureMode == Models.MeasureMode.BgaVoid && _bgaCurrent != null)
|
||||
{
|
||||
HandleBgaRightClick();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
// 右键点击完成多边形
|
||||
OnRightClick();
|
||||
// 不设 e.Handled,让 ContextMenu 正常弹出
|
||||
}
|
||||
|
||||
private void Canvas_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// BGA 模式下阻止 ContextMenu 弹出
|
||||
if (CurrentMeasureMode == Models.MeasureMode.BgaVoid && _bgaCurrent != null)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ROI_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 选择ROI
|
||||
|
||||
Reference in New Issue
Block a user